diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Customer/CustomerAccountController.cs index 15b12f08fb976203c99649a16fc9ca15a5d37dfb..dbe5e2fc807ad5c04fc23c01b39d0dac64433a98 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 072e3e382737d5361171c53886fd092e019cc701..6eecfe0ea94dc52456de341cbe55b13a46d4bbb8 100644 --- a/EOM.TSHotelManagement.API/Controllers/Business/News/NewsController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Business/News/NewsController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; namespace EOM.TSHotelManagement.WebApi { + [ApiExplorerSettings(GroupName = "v1_Mobile")] public class NewsController : ControllerBase { private readonly INewsService newsService; diff --git a/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomController.cs b/EOM.TSHotelManagement.API/Controllers/Business/Room/RoomController.cs index e2530ce6a7ba2d238c7d13ffd0ef9c743916b1f4..c9adfd3f5a1c755ecfd09a11ce544a5261f3c12c 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 a74afedc607ebe1e71b6bd03e94706add1117b67..d8e0890d87286165fe031088edde3ab528edeb35 100644 --- a/EOM.TSHotelManagement.API/Controllers/Employee/History/EmployeeHistoryController.cs +++ b/EOM.TSHotelManagement.API/Controllers/Employee/History/EmployeeHistoryController.cs @@ -29,6 +29,18 @@ namespace EOM.TSHotelManagement.WebApi.Controllers return workerHistoryService.AddHistoryByEmployeeId(workerHistory); } + /// + /// 根据工号更新员工履历 + /// + /// + /// + [RequirePermission("staffmanagement.update")] + [HttpPost] + public BaseResponse UpdateHistoryByEmployeeId([FromBody] UpdateEmployeeHistoryInputDto workerHistory) + { + return workerHistoryService.UpdateHistoryByEmployeeId(workerHistory); + } + /// /// 根据工号查询履历信息 /// @@ -43,4 +55,3 @@ namespace EOM.TSHotelManagement.WebApi.Controllers } } - diff --git a/EOM.TSHotelManagement.API/Extensions/ApplicationExtensions.cs b/EOM.TSHotelManagement.API/Extensions/ApplicationExtensions.cs index c08e4fc2e2b03de26bf0cfca34b853419c3cb650..66972cbf3538235d344f9e49aa5a589a1bc59bfa 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 0000000000000000000000000000000000000000..53cdafe471a6a34d7b008cd369cefbff6cec5e24 --- /dev/null +++ b/EOM.TSHotelManagement.API/Extensions/ClientApiGroupConvention.cs @@ -0,0 +1,110 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using System; +using System.Collections.Generic; + +namespace EOM.TSHotelManagement.WebApi +{ + internal static class ClientApiGroups + { + public const string Web = "v1_Web"; + public const string Desktop = "v1_Desktop"; + public const string Mobile = "v1_Mobile"; + + public static readonly string[] All = new[] { Web, Desktop, Mobile }; + } + + internal sealed class ClientApiGroupConvention : IApplicationModelConvention + { + private static readonly HashSet MobileControllers = new(StringComparer.OrdinalIgnoreCase) + { + "CustomerAccount", + "News" + }; + + private static readonly HashSet DesktopEndpoints = new(StringComparer.OrdinalIgnoreCase) + { + "/Base/DeleteRewardPunishmentType", + "/Base/InsertRewardPunishmentType", + "/Base/SelectCustoTypeByTypeId", + "/Base/SelectDept", + "/Base/SelectDeptAllCanUse", + "/Base/SelectEducation", + "/Base/SelectGenderTypeAll", + "/Base/SelectNation", + "/Base/SelectPassPortTypeByTypeId", + "/Base/SelectPosition", + "/Base/SelectReserTypeAll", + "/Base/SelectRewardPunishmentTypeAll", + "/Base/SelectRewardPunishmentTypeAllCanUse", + "/Base/SelectRewardPunishmentTypeByTypeId", + "/Base/UpdateRewardPunishmentType", + "/Customer/UpdCustomerTypeByCustoNo", + "/CustomerAccount/Login", + "/CustomerAccount/Register", + "/Employee/UpdateEmployeeAccountPassword", + "/EmployeeCheck/SelectWorkerCheckDaySumByEmployeeId", + "/EmployeePhoto/DeleteWorkerPhoto", + "/EmployeePhoto/EmployeePhoto", + "/EmployeePhoto/UpdateWorkerPhoto", + "/EnergyManagement/InsertEnergyManagementInfo", + "/Login/RefreshCSRFToken", + "/NavBar/AddNavBar", + "/NavBar/DeleteNavBar", + "/NavBar/NavBarList", + "/NavBar/UpdateNavBar", + "/News/AddNews", + "/News/DeleteNews", + "/News/News", + "/News/SelectNews", + "/News/UpdateNews", + "/Notice/InsertNotice", + "/Notice/SelectNoticeAll", + "/Notice/SelectNoticeByNoticeNo", + "/PromotionContent/SelectPromotionContents", + "/RewardPunishment/AddRewardPunishment", + "/Role/ReadRolePermissions", + "/Room/DayByRoomNo", + "/Room/SelectCanUseRoomAllByRoomState", + "/Room/SelectFixingRoomAllByRoomState", + "/Room/SelectNotClearRoomAllByRoomState", + "/Room/SelectNotUseRoomAllByRoomState", + "/Room/SelectReservedRoomAllByRoomState", + "/Room/SelectRoomByRoomNo", + "/Room/SelectRoomByRoomPrice", + "/Room/SelectRoomByRoomState", + "/Room/SelectRoomByTypeName", + "/Room/UpdateRoomInfoWithReser", + "/Room/UpdateRoomStateByRoomNo", + "/RoomType/SelectRoomTypeByRoomNo", + "/Sellthing/SelectSellThingByNameAndPrice", + "/Spend/SelectSpendByRoomNo", + "/Spend/SeletHistorySpendInfoAll", + "/Spend/SumConsumptionAmount", + "/Utility/AddLog", + "/Utility/DeleteRequestlogByRange", + "/VipRule/SelectVipRule" + }; + + public void Apply(ApplicationModel application) + { + foreach (var controller in application.Controllers) + { + foreach (var action in controller.Actions) + { + action.ApiExplorer.GroupName = ResolveGroupName(controller.ControllerName, action.ActionName); + } + } + } + + private static string ResolveGroupName(string controllerName, string actionName) + { + if (MobileControllers.Contains(controllerName)) + { + return ClientApiGroups.Mobile; + } + + var endpoint = $"/{controllerName}/{actionName}"; + return DesktopEndpoints.Contains(endpoint) ? ClientApiGroups.Desktop : ClientApiGroups.Web; + } + } +} diff --git a/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs b/EOM.TSHotelManagement.API/Extensions/ServiceExtensions.cs index 083b0c1df00d49dafd5e73096c3a2b17af0fb160..0a79b8505ae07b69ce598ab0dd73fbf3c32c0429 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 0000000000000000000000000000000000000000..d357abfb45134b2dfd84cb4b64283caa417bdcad --- /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 6451a26239b4044e41a01f000c87605face54576..2cdb089f351132575d1bc839f4e74228d5f2dac9 100644 --- a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/CreateEnergyManagementInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/CreateEnergyManagementInputDto.cs @@ -4,29 +4,31 @@ namespace EOM.TSHotelManagement.Contract { public class CreateEnergyManagementInputDto : BaseInputDto { - [Required(ErrorMessage = "信息编号为必填字段"), MaxLength(128, ErrorMessage = "信息编号长度不超过128字符")] + [Required(ErrorMessage = "InformationNumber is required."), MaxLength(128, ErrorMessage = "InformationNumber max length is 128.")] public string InformationNumber { get; set; } - [Required(ErrorMessage = "房间编号为必填字段"), MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + + [MaxLength(128, ErrorMessage = "RoomNumber max length is 128.")] public string RoomNumber { get; set; } - [Required(ErrorMessage = "客户编号为必填字段"), MaxLength(128, ErrorMessage = "客户编号长度不超过128字符")] + [Required(ErrorMessage = "CustomerNumber is required."), MaxLength(128, ErrorMessage = "CustomerNumber max length is 128.")] public string CustomerNumber { get; set; } - [Required(ErrorMessage = "开始日期为必填字段")] + [Required(ErrorMessage = "StartDate is required.")] public DateTime StartDate { get; set; } - [Required(ErrorMessage = "结束日期为必填字段")] + [Required(ErrorMessage = "EndDate is required.")] public DateTime EndDate { get; set; } - [Required(ErrorMessage = "电费为必填字段")] + [Required(ErrorMessage = "PowerUsage is required.")] public decimal PowerUsage { get; set; } - [Required(ErrorMessage = "水费为必填字段")] + [Required(ErrorMessage = "WaterUsage is required.")] public decimal WaterUsage { get; set; } - [Required(ErrorMessage = "记录员为必填字段"), MaxLength(150, ErrorMessage = "记录员长度不超过150字符")] + [Required(ErrorMessage = "Recorder is required."), MaxLength(150, ErrorMessage = "Recorder max length is 150.")] public string Recorder { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementInputDto.cs b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementInputDto.cs index 8cb9fe003ba4aa336ca546dfcfdd0fdc293339e4..a23f97f8f944882237e5299fdcbb8f7f48b20d18 100644 --- a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementInputDto.cs @@ -5,11 +5,11 @@ namespace EOM.TSHotelManagement.Contract public class ReadEnergyManagementInputDto : ListInputDto { public string? CustomerNumber { get; set; } + public int? RoomId { get; set; } public string? RoomNumber { get; set; } - public DateOnly? StartDate { get; set; } + public DateTime? StartDate { get; set; } - public DateOnly? EndDate { get; set; } + public DateTime? EndDate { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementOutputDto.cs index 9f179e31f8ea4f12a7400411346e2d5278c64f14..7774867b51aa32215937b64ef9677572cf196331 100644 --- a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/ReadEnergyManagementOutputDto.cs @@ -6,8 +6,12 @@ namespace EOM.TSHotelManagement.Contract { public int? Id { get; set; } public string InformationId { get; set; } + public int? RoomId { get; set; } [UIDisplay("房间号")] public string RoomNumber { get; set; } + public string RoomArea { get; set; } + public int? RoomFloor { get; set; } + public string RoomLocator { get; set; } [UIDisplay("客户编号")] public string CustomerNumber { get; set; } [UIDisplay("开始日期")] diff --git a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/UpdateEnergyManagementInputDto.cs b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/UpdateEnergyManagementInputDto.cs index 9dce1db215ab5ad6009e83e7592e133f828033a9..7d6029bba8ce05488f6ee9ed747e4bb43ae7f1fb 100644 --- a/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/UpdateEnergyManagementInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/EnergyManagement/Dto/UpdateEnergyManagementInputDto.cs @@ -4,29 +4,31 @@ namespace EOM.TSHotelManagement.Contract { public class UpdateEnergyManagementInputDto : BaseInputDto { - [Required(ErrorMessage = "信息编号为必填字段"), MaxLength(128, ErrorMessage = "信息编号长度不超过128字符")] + [Required(ErrorMessage = "InformationId is required."), MaxLength(128, ErrorMessage = "InformationId max length is 128.")] public string InformationId { get; set; } - [Required(ErrorMessage = "房间编号为必填字段"), MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + + [MaxLength(128, ErrorMessage = "RoomNumber max length is 128.")] public string RoomNumber { get; set; } - [Required(ErrorMessage = "客户编号为必填字段"), MaxLength(128, ErrorMessage = "客户编号长度不超过128字符")] + [Required(ErrorMessage = "CustomerNumber is required."), MaxLength(128, ErrorMessage = "CustomerNumber max length is 128.")] public string CustomerNumber { get; set; } - [Required(ErrorMessage = "开始日期为必填字段")] + [Required(ErrorMessage = "StartDate is required.")] public DateTime StartDate { get; set; } - [Required(ErrorMessage = "结束日期为必填字段")] + [Required(ErrorMessage = "EndDate is required.")] public DateTime EndDate { get; set; } - [Required(ErrorMessage = "电费为必填字段")] + [Required(ErrorMessage = "PowerUsage is required.")] public decimal PowerUsage { get; set; } - [Required(ErrorMessage = "水费为必填字段")] + [Required(ErrorMessage = "WaterUsage is required.")] public decimal WaterUsage { get; set; } - [Required(ErrorMessage = "记录员为必填字段"), MaxLength(150, ErrorMessage = "记录员长度不超过150字符")] + [Required(ErrorMessage = "Recorder is required."), MaxLength(150, ErrorMessage = "Recorder max length is 150.")] public string Recorder { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/CreateReserInputDto.cs index 26d2e7f4a9f1978747a374e965f149a351b131ec..cf8287698a6e81285bad1dd9be67a802085073ea 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 7dae6616f9f7e46254e94eccbfce1ba62da926bc..0466e2b3afa9d8ceb5dc4d92dd640721e32bc925 100644 --- a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserInputDto.cs @@ -2,6 +2,7 @@ { public class ReadReserInputDto : ListInputDto { + public int? RoomId { get; set; } public string? ReservationId { get; set; } public string? CustomerName { get; set; } public string? ReservationPhoneNumber { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserOutputDto.cs index 9ce46d1a2a45e14b643d3a85d706870597cd0aba..8816cc452455d3918d6ad04f8965d93c62ab8d8a 100644 --- a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/ReadReserOutputDto.cs @@ -5,6 +5,7 @@ namespace EOM.TSHotelManagement.Contract public class ReadReserOutputDto : BaseOutputDto { public int? Id { get; set; } + public int? RoomId { get; set; } [UIDisplay("预约编号")] public string ReservationId { get; set; } @@ -18,6 +19,12 @@ namespace EOM.TSHotelManagement.Contract [UIDisplay("预定房号")] public string ReservationRoomNumber { get; set; } + public string RoomArea { get; set; } + + public int? RoomFloor { get; set; } + + public string RoomLocator { get; set; } + [UIDisplay("预约渠道")] public string ReservationChannel { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/UpdateReserInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/UpdateReserInputDto.cs index c0ed9a9a3a598a617d36a9ae9179cf3b33cb3ee9..cb69d7b6dcb051d38d7f1f3a07121014051be6b6 100644 --- a/EOM.TSHotelManagement.Contract/Business/Reser/Dto/UpdateReserInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Reser/Dto/UpdateReserInputDto.cs @@ -4,26 +4,28 @@ namespace EOM.TSHotelManagement.Contract { public class UpdateReserInputDto : BaseInputDto { - [Required(ErrorMessage = "预约编号为必填字段"), MaxLength(128, ErrorMessage = "预约编号长度不超过128字符")] + [Required(ErrorMessage = "ReservationId is required."), MaxLength(128, ErrorMessage = "ReservationId max length is 128.")] public string ReservationId { get; set; } - [Required(ErrorMessage = "客户名称为必填字段"), MaxLength(200, ErrorMessage = "客户名称长度不超过200字符")] + [Required(ErrorMessage = "CustomerName is required."), MaxLength(200, ErrorMessage = "CustomerName max length is 200.")] public string CustomerName { get; set; } - [Required(ErrorMessage = "预约电话为必填字段"), MaxLength(256, ErrorMessage = "预约电话长度不超过256字符")] + [Required(ErrorMessage = "ReservationPhoneNumber is required."), MaxLength(256, ErrorMessage = "ReservationPhoneNumber max length is 256.")] public string ReservationPhoneNumber { get; set; } - [Required(ErrorMessage = "预约房号为必填字段"), MaxLength(128, ErrorMessage = "预约房号长度不超过128字符")] + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + + [MaxLength(128, ErrorMessage = "ReservationRoomNumber max length is 128.")] public string ReservationRoomNumber { get; set; } - [Required(ErrorMessage = "预约渠道为必填字段"), MaxLength(50, ErrorMessage = "预约渠道长度不超过50字符")] + [Required(ErrorMessage = "ReservationChannel is required."), MaxLength(50, ErrorMessage = "ReservationChannel max length is 50.")] public string ReservationChannel { get; set; } - [Required(ErrorMessage = "预约起始日期为必填字段")] + [Required(ErrorMessage = "ReservationStartDate is required.")] public DateTime ReservationStartDate { get; set; } - [Required(ErrorMessage = "预约结束日期为必填字段")] + [Required(ErrorMessage = "ReservationEndDate is required.")] public DateTime ReservationEndDate { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckinRoomByReservationDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckinRoomByReservationDto.cs index e24472de331b2f980f01a0f289d6918e192ea500..2c634c673cce4594924ea0c985af124896536962 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckinRoomByReservationDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckinRoomByReservationDto.cs @@ -30,8 +30,14 @@ namespace EOM.TSHotelManagement.Contract [Required(ErrorMessage = "客户类型为必填字段")] public int CustomerType { get; set; } - [Required(ErrorMessage = "房间编号为必填字段"), MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] + [Required(ErrorMessage = "房间ID为必填字段")] + public int? RoomId { get; set; } + + [MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] public string RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } + public string? PricingCode { get; set; } [Required(ErrorMessage = "预约编号为必填字段"), MaxLength(128, ErrorMessage = "预约编号长度不超过128字符")] public string ReservationId { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckoutRoomDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckoutRoomDto.cs index 96c653ad346ef7bb4e778ef74431485577deddd1..8453d241677c8f8c27d659c0b1d17f59db99b4f7 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckoutRoomDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/CheckoutRoomDto.cs @@ -4,8 +4,12 @@ namespace EOM.TSHotelManagement.Contract { public class CheckoutRoomDto : BaseInputDto { - [Required(ErrorMessage = "房间编号为必填字段"), MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] + [Required(ErrorMessage = "房间ID为必填字段")] + public int? RoomId { get; set; } + [MaxLength(128, ErrorMessage = "房间编号长度不超过128字符")] public string RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } [Required(ErrorMessage = "客户编号为必填字段"), MaxLength(128, ErrorMessage = "客户编号长度不超过128字符")] public string CustomerNumber { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/CreateRoomInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/CreateRoomInputDto.cs index 12adeb5d4a25cc79da95c0ce9643bd8eaa514d91..5fa473af0b5972489fc22ff907b14e2368718304 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/CreateRoomInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/CreateRoomInputDto.cs @@ -3,6 +3,8 @@ namespace EOM.TSHotelManagement.Contract public class CreateRoomInputDto : BaseInputDto { public string RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } public int RoomTypeId { get; set; } public string CustomerNumber { get; set; } public DateTime? LastCheckInTime { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomInputDto.cs index 508d6bd9f7a1255d94663920693be5c5b2de65f1..d7fc93592edf41701db11a057acb3fd58b104a01 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomInputDto.cs @@ -2,11 +2,15 @@ { public class ReadRoomInputDto : ListInputDto { + public int? Id { get; set; } public string? RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } public int? RoomTypeId { get; set; } public int? RoomStateId { get; set; } public string? RoomName { get; set; } - public DateOnly? LastCheckInTime { get; set; } + public DateTime? LastCheckInTime { get; set; } public string? CustomerNumber { get; set; } + public string? PricingCode { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomOutputDto.cs index 281357c378f2867495054a346331bd25d5dbe6ff..1d7a3daaf9318d62dc285300bc6c055b15c17ae6 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomOutputDto.cs @@ -4,6 +4,9 @@ { public int? Id { get; set; } public string RoomNumber { get; set; } + public string RoomArea { get; set; } + public int? RoomFloor { get; set; } + public string RoomLocator { get; set; } public string RoomName { get; set; } public int RoomTypeId { get; set; } public string CustomerNumber { get; set; } @@ -14,6 +17,18 @@ public string RoomState { get; set; } public decimal RoomRent { get; set; } public decimal RoomDeposit { get; set; } + public decimal StandardRoomRent { get; set; } + public decimal StandardRoomDeposit { get; set; } + public decimal AppliedRoomRent { get; set; } + public decimal AppliedRoomDeposit { get; set; } + public decimal EffectiveRoomRent { get; set; } + public decimal EffectiveRoomDeposit { get; set; } + public string EffectivePricingCode { get; set; } + public string EffectivePricingName { get; set; } + public string PricingCode { get; set; } + public string PricingName { get; set; } + public int? PricingStayHours { get; set; } + public bool IsPricingTimedOut { get; set; } public string RoomLocation { get; set; } public int StayDays { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomPricingOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomPricingOutputDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..3175e3a56b9a372e1e17ec6444ebcf493008ea9e --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/ReadRoomPricingOutputDto.cs @@ -0,0 +1,21 @@ +namespace EOM.TSHotelManagement.Contract +{ + public class ReadRoomPricingOutputDto + { + public int? RoomId { get; set; } + public string RoomNumber { get; set; } + public string RoomLocator { get; set; } + public int RoomTypeId { get; set; } + public string RoomTypeName { get; set; } + public string CurrentPricingCode { get; set; } + public string CurrentPricingName { get; set; } + public int? PricingStayHours { get; set; } + public bool IsPricingTimedOut { get; set; } + public string EffectivePricingCode { get; set; } + public string EffectivePricingName { get; set; } + public DateTime? LastCheckInTime { get; set; } + public decimal EffectiveRoomRent { get; set; } + public decimal EffectiveRoomDeposit { get; set; } + public List PricingItems { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/UpdateRoomInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/UpdateRoomInputDto.cs index e5caef7e1d6f9d9a07e155daeebad80d03f1add9..a9e31421a16b23bec9ae8e8c6631b6425d24b782 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/UpdateRoomInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/Room/UpdateRoomInputDto.cs @@ -2,16 +2,18 @@ namespace EOM.TSHotelManagement.Contract { public class UpdateRoomInputDto : BaseInputDto { + public int? Id { get; set; } public string RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } public int RoomTypeId { get; set; } public string CustomerNumber { get; set; } - public DateOnly? LastCheckInTime { get; set; } - public DateOnly? LastCheckOutTime { get; set; } + public DateTime? LastCheckInTime { get; set; } + public DateTime? LastCheckOutTime { get; set; } public int RoomStateId { get; set; } public decimal RoomRent { get; set; } public decimal RoomDeposit { get; set; } public string RoomLocation { get; set; } + public string? PricingCode { get; set; } } } - - diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/CreateRoomTypeInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/CreateRoomTypeInputDto.cs index 0b43976024543e180d25f61d873144f58f68c1a9..51c33bbb8852371316121381dc5d5e9ddfc21b15 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/CreateRoomTypeInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/CreateRoomTypeInputDto.cs @@ -6,6 +6,7 @@ namespace EOM.TSHotelManagement.Contract public string RoomTypeName { get; set; } public decimal RoomRent { get; set; } public decimal RoomDeposit { get; set; } + public List? PricingItems { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeInputDto.cs index 4e21263967831d60211f736d4bd5f86152196d59..43293503ec9b2f5c2e03cba13468ab9d91445a9a 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeInputDto.cs @@ -2,7 +2,10 @@ { public class ReadRoomTypeInputDto : ListInputDto { + public int? Id { get; set; } public string? RoomNumber { get; set; } + public string? RoomArea { get; set; } + public int? RoomFloor { get; set; } public int? RoomTypeId { get; set; } public string? RoomTypeName { get; set; } public decimal? RoomRent { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeOutputDto.cs index cdf0356495ad72c55e8e470202dbb3b5dbe18eca..cb727342c5b781d3f8a2af56bfd2a13721ba4e61 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/ReadRoomTypeOutputDto.cs @@ -7,6 +7,7 @@ public string RoomTypeName { get; set; } public decimal RoomRent { get; set; } public decimal RoomDeposit { get; set; } + public List? PricingItems { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/RoomTypePricingItemDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/RoomTypePricingItemDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..d30fb97ad7780e90daa5af8b255c095d3dee4051 --- /dev/null +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/RoomTypePricingItemDto.cs @@ -0,0 +1,13 @@ +namespace EOM.TSHotelManagement.Contract +{ + public class RoomTypePricingItemDto + { + public string PricingCode { get; set; } + public string PricingName { get; set; } + public decimal RoomRent { get; set; } + public decimal RoomDeposit { get; set; } + public int? StayHours { get; set; } + public int Sort { get; set; } + public bool IsDefault { get; set; } + } +} diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/UpdateRoomTypeInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/UpdateRoomTypeInputDto.cs index 075b67d52ee28a1eff8fca9775595e66e1a6581a..18896fdc1b2b8a816099ded111b4051e80e95f3d 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/UpdateRoomTypeInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/RoomType/UpdateRoomTypeInputDto.cs @@ -6,6 +6,7 @@ namespace EOM.TSHotelManagement.Contract public string RoomTypeName { get; set; } public decimal RoomRent { get; set; } public decimal RoomDeposit { get; set; } + public List? PricingItems { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Business/Room/Dto/TransferRoomDto.cs b/EOM.TSHotelManagement.Contract/Business/Room/Dto/TransferRoomDto.cs index 4a4d3d5e921f624a85239ba2ca62dd7c69ce4c6b..52519aaf178bf0c10b17701b2d57e78d84165e3b 100644 --- a/EOM.TSHotelManagement.Contract/Business/Room/Dto/TransferRoomDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Room/Dto/TransferRoomDto.cs @@ -4,11 +4,19 @@ namespace EOM.TSHotelManagement.Contract { public class TransferRoomDto : BaseInputDto { - [Required(ErrorMessage = "源房间编号为必填字段"), MaxLength(128, ErrorMessage = "源房间编号长度不超过128字符")] + [Required(ErrorMessage = "源房间ID为必填字段")] + public int? OriginalRoomId { get; set; } + [MaxLength(128, ErrorMessage = "源房间编号长度不超过128字符")] public string OriginalRoomNumber { get; set; } + public string? OriginalRoomArea { get; set; } + public int? OriginalRoomFloor { get; set; } - [Required(ErrorMessage = "目标房间编号为必填字段"), MaxLength(128, ErrorMessage = "目标房间编号长度不超过128字符")] + [Required(ErrorMessage = "目标房间ID为必填字段")] + public int? TargetRoomId { get; set; } + [MaxLength(128, ErrorMessage = "目标房间编号长度不超过128字符")] public string TargetRoomNumber { get; set; } + public string? TargetRoomArea { get; set; } + public int? TargetRoomFloor { get; set; } [Required(ErrorMessage = "客户编号为必填字段"), MaxLength(128, ErrorMessage = "客户编号长度不超过128字符")] public string CustomerNumber { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/AddCustomerSpendInputDto.cs index 5894dd36fa29fcaff839c5d6ccc21c742030304c..e6cc2886f358766d90e30ea48caebe96144e1a96 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 c9fd6f01d4a8bfbc8cdc71060b7092f9150d3f40..a1eb1d84a7eace3304798800ec8ce9c3ad80fc0c 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/CreateSpendInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/CreateSpendInputDto.cs @@ -4,28 +4,31 @@ namespace EOM.TSHotelManagement.Contract { public class CreateSpendInputDto : BaseInputDto { - [Required(ErrorMessage = "商品编号为必填字段")] + [Required(ErrorMessage = "ProductNumber is required.")] public string ProductNumber { get; set; } - [Required(ErrorMessage = "消费编号为必填字段")] + [Required(ErrorMessage = "SpendNumber is required.")] public string SpendNumber { get; set; } + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + public string RoomNumber { get; set; } public string CustomerNumber { get; set; } public string ProductName { get; set; } - [Required(ErrorMessage = "消费数量为必填字段")] + [Required(ErrorMessage = "ConsumptionQuantity is required.")] public int ConsumptionQuantity { get; set; } - [Required(ErrorMessage = "商品单价为必填字段")] + [Required(ErrorMessage = "ProductPrice is required.")] public decimal ProductPrice { get; set; } - [Required(ErrorMessage = "消费金额为必填字段")] + [Required(ErrorMessage = "ConsumptionAmount is required.")] public decimal ConsumptionAmount { get; set; } - [Required(ErrorMessage = "消费时间为必填字段")] + [Required(ErrorMessage = "ConsumptionTime is required.")] public DateTime ConsumptionTime { get; set; } public string SettlementStatus { get; set; } @@ -33,4 +36,3 @@ namespace EOM.TSHotelManagement.Contract public string ConsumptionType { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendInputDto.cs index 9a87c94aa711a29ef0aecf03e45056e76a18e52f..74677518c61a5f3659aad9fb0df08521eef4f37d 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendInputDto.cs @@ -6,6 +6,8 @@ namespace EOM.TSHotelManagement.Contract { public string? SpendNumber { get; set; } + public int? RoomId { get; set; } + public string? RoomNumber { get; set; } public string? CustomerNumber { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendOutputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendOutputDto.cs index 4c92a75eca0d47cf8f376d5238e65f042ece7a07..801a9f55ee14ba08e81f30c3880ef9938e796ba1 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/ReadSpendOutputDto.cs @@ -5,6 +5,7 @@ namespace EOM.TSHotelManagement.Contract public class ReadSpendOutputDto : BaseOutputDto { public int? Id { get; set; } + public int? RoomId { get; set; } [UIDisplay("消费编号", false, false)] public string SpendNumber { get; set; } @@ -12,6 +13,12 @@ namespace EOM.TSHotelManagement.Contract [UIDisplay("房间号")] public string RoomNumber { get; set; } + public string RoomArea { get; set; } + + public int? RoomFloor { get; set; } + + public string RoomLocator { get; set; } + [UIDisplay("客户编号")] public string CustomerNumber { get; set; } diff --git a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UpdateSpendInputDto.cs b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UpdateSpendInputDto.cs index 60413f8c7123718244b33a4a8701020f0e115227..f75fd45d1b048b851a9da3ce5d4a73adc905f34b 100644 --- a/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UpdateSpendInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Business/Spend/Dto/Spend/UpdateSpendInputDto.cs @@ -4,9 +4,12 @@ namespace EOM.TSHotelManagement.Contract { public class UpdateSpendInputDto : BaseInputDto { - [Required(ErrorMessage = "消费编号为必填字段")] + [Required(ErrorMessage = "SpendNumber is required.")] public string SpendNumber { get; set; } + [Required(ErrorMessage = "RoomId is required.")] + public int? RoomId { get; set; } + public string RoomNumber { get; set; } public string OriginalRoomNumber { get; set; } @@ -15,16 +18,16 @@ namespace EOM.TSHotelManagement.Contract public string ProductName { get; set; } - [Required(ErrorMessage = "消费数量为必填字段")] + [Required(ErrorMessage = "ConsumptionQuantity is required.")] public int ConsumptionQuantity { get; set; } - [Required(ErrorMessage = "商品单价为必填字段")] + [Required(ErrorMessage = "ProductPrice is required.")] public decimal ProductPrice { get; set; } - [Required(ErrorMessage = "消费金额为必填字段")] + [Required(ErrorMessage = "ConsumptionAmount is required.")] public decimal ConsumptionAmount { get; set; } - [Required(ErrorMessage = "消费时间为必填字段")] + [Required(ErrorMessage = "ConsumptionTime is required.")] public DateTime ConsumptionTime { get; set; } public string SettlementStatus { get; set; } @@ -32,4 +35,3 @@ namespace EOM.TSHotelManagement.Contract public string ConsumptionType { get; set; } } } - diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/ReadEmployeeCheckOutputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/ReadEmployeeCheckOutputDto.cs index 0b2ec8c961e0950f551f59a6a7316e3f99ec8f95..792fc39358179468f2d02f7330878db16e90946a 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/ReadEmployeeCheckOutputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeCheck/ReadEmployeeCheckOutputDto.cs @@ -1,18 +1,26 @@ -namespace EOM.TSHotelManagement.Contract +using SqlSugar; + +namespace EOM.TSHotelManagement.Contract { public class ReadEmployeeCheckOutputDto : BaseOutputDto { public string EmployeeId { get; set; } public DateTime CheckTime { get; set; } - public string CheckStatus { get; set; } + public int CheckStatus { get; set; } public string CheckMethod { get; set; } + public string CheckMethodDescription { get; set; } public bool MorningChecked { get; set; } public bool EveningChecked { get; set; } public int CheckDay { get; set; } + + /// + /// 打卡状态描述 (Check-in/Check-out Status Description) + /// + public string CheckStatusDescription { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/CreateEmployeeHistoryInputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/CreateEmployeeHistoryInputDto.cs index 5a6f6f41aeaa919356bfb281ea6771a14a21ea62..ff36650e80aa40fbc77bca23bb6e7020ded08d50 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/CreateEmployeeHistoryInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/CreateEmployeeHistoryInputDto.cs @@ -4,19 +4,36 @@ namespace EOM.TSHotelManagement.Contract { public class CreateEmployeeHistoryInputDto : BaseInputDto { + [Required(ErrorMessage = "履历编号为必填字段")] + public string HistoryNumber { get; set; } + [Required(ErrorMessage = "员工工号为必填字段")] [MaxLength(128, ErrorMessage = "员工工号长度不超过128字符")] public string EmployeeId { get; set; } - [Required(ErrorMessage = "变更日期为必填字段")] - public DateTime ChangeDate { get; set; } + /// + /// 开始时间 (Start Date) + /// + [Required(ErrorMessage = "开始时间为必填字段")] + public DateOnly StartDate { get; set; } + + /// + /// 结束时间 (End Date) + /// + [Required(ErrorMessage = "结束时间为必填字段")] + public DateOnly EndDate { get; set; } - [Required(ErrorMessage = "变更类型为必填字段")] - [MaxLength(128, ErrorMessage = "变更类型长度不超过128字符")] - public string ChangeType { get; set; } + /// + /// 职位 (Position) + /// + [Required(ErrorMessage = "职位为必填字段")] + public string Position { get; set; } - [MaxLength(500, ErrorMessage = "变更描述长度不超过500字符")] - public string ChangeDescription { get; set; } + /// + /// 公司 (Company) + /// + [Required(ErrorMessage = "公司为必填字段")] + public string Company { get; set; } } } diff --git a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/UpdateEmployeeHistoryInputDto.cs b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/UpdateEmployeeHistoryInputDto.cs index 84c425bae4ed757533781703ae5ede34a3fb41c5..94db020929c974a80133c432192bee436b861e6c 100644 --- a/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/UpdateEmployeeHistoryInputDto.cs +++ b/EOM.TSHotelManagement.Contract/Employee/Dto/EmployeeHistory/UpdateEmployeeHistoryInputDto.cs @@ -1,24 +1,33 @@ +using SqlSugar; using System.ComponentModel.DataAnnotations; namespace EOM.TSHotelManagement.Contract { public class UpdateEmployeeHistoryInputDto : BaseInputDto { - [Required(ErrorMessage = "履历ID为必填字段")] - public int HistoryId { get; set; } + /// + /// 开始时间 (Start Date) + /// + [Required(ErrorMessage = "开始时间为必填字段")] + public DateOnly StartDate { get; set; } - [Required(ErrorMessage = "员工工号为必填字段")] - [MaxLength(128, ErrorMessage = "员工工号长度不超过128字符")] - public string EmployeeId { get; set; } + /// + /// 结束时间 (End Date) + /// + [Required(ErrorMessage = "结束时间为必填字段")] + public DateOnly EndDate { get; set; } - [Required(ErrorMessage = "变更日期为必填字段")] - public DateTime ChangeDate { get; set; } + /// + /// 职位 (Position) + /// + [Required(ErrorMessage = "职位为必填字段")] + public string Position { get; set; } - [MaxLength(128, ErrorMessage = "变更类型长度不超过128字符")] - public string ChangeType { get; set; } - - [MaxLength(500, ErrorMessage = "变更描述长度不超过500字符")] - public string ChangeDescription { get; set; } + /// + /// 公司 (Company) + /// + [Required(ErrorMessage = "公司为必填字段")] + public string Company { get; set; } } } diff --git a/EOM.TSHotelManagement.Domain/Business/EnergyManagement/EnergyManagement.cs b/EOM.TSHotelManagement.Domain/Business/EnergyManagement/EnergyManagement.cs index d12bc48c69e7e07e23b67da888e1da1c22c44c5a..32c2891b152540086c9921fdc643eea053a01ba6 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 8e27af87ea71260c87651b6d5054f8c7ee0f27ad..93bbf613e0c20fdf48cfb24dafa52e88562a6564 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 7be37c6e624e17fdae5ff89e60edec81a8eacdb3..00ca7b29d672b4f06f719a6d7047a7855d6381e3 100644 --- a/EOM.TSHotelManagement.Domain/Business/Room/Room.cs +++ b/EOM.TSHotelManagement.Domain/Business/Room/Room.cs @@ -1,177 +1,165 @@ -/* - * MIT License - *Copyright (c) 2021 易开元(Easy-Open-Meta) - - *Permission is hereby granted, free of charge, to any person obtaining a copy - *of this software and associated documentation files (the "Software"), to deal - *in the Software without restriction, including without limitation the rights - *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - *copies of the Software, and to permit persons to whom the Software is - *furnished to do so, subject to the following conditions: - - *The above copyright notice and this permission notice shall be included in all - *copies or substantial portions of the Software. - - *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - *SOFTWARE. - * - *模块说明:房间类 - */ - using SqlSugar; using System; namespace EOM.TSHotelManagement.Domain { - /// - /// 酒店房间信息表 (Hotel Room Information) - /// - [SugarTable("room", "酒店房间信息表 (Hotel Room Information)")] + [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 95c2f323044eb380385badc0933cb123f7a0efe1..3812a5fdf697030f5d3cda3d46084214ed864523 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 c1ecda6804a78fcd96f5867105bb8ee53bf1b187..412fd44f67714fb1a25427564a45457ea731eff6 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 89e567b4c5828bb498270861ff004e211e8a3ee4..dcb79fc40a002070896d275d8fad23ed6c8386a7 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 9da800693db7de1afb4d654449ae384c2a40442f..f9e2bd6ddbf0cc08d5f1d2a55e26b054aa77ee16 100644 --- a/EOM.TSHotelManagement.Infrastructure/Constant/CodeConstantBase.cs +++ b/EOM.TSHotelManagement.Infrastructure/Constant/CodeConstantBase.cs @@ -5,7 +5,7 @@ public string Code { get; } public string Description { get; } - private static List _constants = new List(); + private static readonly List _constants = new List(); protected CodeConstantBase(string code, string description) { @@ -16,25 +16,64 @@ public static IEnumerable GetAll() { + EnsureInitialized(); return _constants; } public static string GetDescriptionByCode(string code) { - var constant = _constants.SingleOrDefault(c => c.Code == code); - return constant?.Description ?? string.Empty; + return GetConstantByCode(code)?.Description ?? string.Empty; } public static string GetCodeByDescription(string description) { - var constant = _constants.SingleOrDefault(c => c.Description == description); - return constant?.Code ?? string.Empty; + return GetConstantByDescription(description)?.Code ?? string.Empty; } public static T? GetConstantByCode(string code) { - var constant = _constants.FirstOrDefault(c => c.Code == code); - return constant ?? null; + EnsureInitialized(); + var normalizedCode = NormalizeValue(code); + if (string.IsNullOrWhiteSpace(normalizedCode)) + { + return null; + } + + return _constants.FirstOrDefault(c => string.Equals(c.Code, normalizedCode, StringComparison.OrdinalIgnoreCase)); + } + + public static T? GetConstantByDescription(string description) + { + EnsureInitialized(); + var normalizedDescription = NormalizeValue(description); + if (string.IsNullOrWhiteSpace(normalizedDescription)) + { + return null; + } + + return _constants.FirstOrDefault(c => string.Equals(c.Description, normalizedDescription, StringComparison.OrdinalIgnoreCase)); + } + + public static bool TryGetDescriptionByCode(string code, out string description) + { + description = GetDescriptionByCode(code); + return !string.IsNullOrWhiteSpace(description); + } + + public static bool TryGetCodeByDescription(string description, out string code) + { + code = GetCodeByDescription(description); + return !string.IsNullOrWhiteSpace(code); + } + + private static string NormalizeValue(string value) + { + return value?.Trim() ?? string.Empty; + } + + private static void EnsureInitialized() + { + System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); } } } diff --git a/EOM.TSHotelManagement.Migration/EntityBuilder.cs b/EOM.TSHotelManagement.Migration/EntityBuilder.cs index 4393fadbfd22cc145ac44375a555168ea9f5f46f..01aa42a37649c36baab01464ba31d1677b828c79 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 09b7d43600ff470f018fc574968e52da1ceac2b2..f01101fa88ee3801ce6269b956050edc636778b8 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 23f86b865482ce4689831064b43c8fb9ff0a6de5..6b2b8e939f6569eb37f357feb34445224930e620 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 8930b2aaae0c475e82529c219a3596e723caed43..9ec1546ecefdec36ee2ac646cb84581184d7ad68 100644 --- a/EOM.TSHotelManagement.Service/Business/Room/IRoomService.cs +++ b/EOM.TSHotelManagement.Service/Business/Room/IRoomService.cs @@ -123,6 +123,13 @@ namespace EOM.TSHotelManagement.Service object SelectRoomByRoomPrice(ReadRoomInputDto readRoomInputDto); #endregion + /// + /// 查询房间可用计价项 + /// + /// + /// + SingleOutputDto SelectRoomPricingOptions(ReadRoomInputDto readRoomInputDto); + #region 查询脏房数量 /// /// 查询脏房数量 @@ -205,4 +212,4 @@ namespace EOM.TSHotelManagement.Service #endregion } -} \ No newline at end of file +} diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomLocatorHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..68679c3e7635415ff073237a859cb66891844873 --- /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 0000000000000000000000000000000000000000..210b1385b1537eddb62f63829dbb0726e4e99d85 --- /dev/null +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomPricingEvaluation.cs @@ -0,0 +1,14 @@ +namespace EOM.TSHotelManagement.Service +{ + internal sealed class RoomPricingEvaluation + { + public string SelectedPricingCode { get; init; } + public string SelectedPricingName { get; init; } + public string EffectivePricingCode { get; init; } + public string EffectivePricingName { get; init; } + public decimal EffectiveRoomRent { get; init; } + public decimal EffectiveRoomDeposit { get; init; } + public int? PricingStayHours { get; init; } + public bool IsPricingTimedOut { get; init; } + } +} diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomPricingHelper.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomPricingHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..bbb8b3dadd025d43fa4849c281e3a1df5cbd5a02 --- /dev/null +++ b/EOM.TSHotelManagement.Service/Business/Room/RoomPricingHelper.cs @@ -0,0 +1,117 @@ +using EOM.TSHotelManagement.Contract; +using System.Text.Json; + +namespace EOM.TSHotelManagement.Service +{ + internal static class RoomPricingHelper + { + public const string DefaultPricingCode = "STANDARD"; + public const string DefaultPricingName = "标准价"; + + public static List BuildPricingItems(decimal roomRent, decimal roomDeposit, string pricingItemsJson) + { + var items = new List + { + CreateDefaultPricingItem(roomRent, roomDeposit) + }; + + items.AddRange(DeserializeAdditionalPricingItems(pricingItemsJson)); + return items; + } + + public static RoomTypePricingItemDto CreateDefaultPricingItem(decimal roomRent, decimal roomDeposit) + { + return new RoomTypePricingItemDto + { + PricingCode = DefaultPricingCode, + PricingName = DefaultPricingName, + RoomRent = roomRent, + RoomDeposit = roomDeposit, + Sort = 0, + IsDefault = true + }; + } + + public static string SerializeAdditionalPricingItems(IEnumerable? pricingItems) + { + var normalized = NormalizeAdditionalPricingItems(pricingItems).ToList(); + return normalized.Count == 0 ? "[]" : JsonSerializer.Serialize(normalized); + } + + public static RoomTypePricingItemDto? ResolvePricingItem(decimal roomRent, decimal roomDeposit, string pricingItemsJson, string? pricingCode) + { + var normalizedCode = NormalizePricingCode(pricingCode); + var items = BuildPricingItems(roomRent, roomDeposit, pricingItemsJson); + if (string.IsNullOrWhiteSpace(normalizedCode)) + { + return items.FirstOrDefault(); + } + + return items.FirstOrDefault(a => string.Equals(a.PricingCode, normalizedCode, StringComparison.OrdinalIgnoreCase)); + } + + public static string NormalizePricingCode(string? pricingCode) + { + return string.IsNullOrWhiteSpace(pricingCode) + ? string.Empty + : pricingCode.Trim().ToUpperInvariant(); + } + + private static List DeserializeAdditionalPricingItems(string pricingItemsJson) + { + if (string.IsNullOrWhiteSpace(pricingItemsJson)) + { + return new List(); + } + + try + { + var items = JsonSerializer.Deserialize>(pricingItemsJson) ?? new List(); + return NormalizeAdditionalPricingItems(items).ToList(); + } + catch + { + return new List(); + } + } + + private static IEnumerable NormalizeAdditionalPricingItems(IEnumerable? pricingItems) + { + if (pricingItems == null) + { + yield break; + } + + var seenCodes = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var item in pricingItems.OrderBy(a => a?.Sort ?? 0)) + { + if (item == null) + { + continue; + } + + var code = NormalizePricingCode(item.PricingCode); + if (string.IsNullOrWhiteSpace(code) || string.Equals(code, DefaultPricingCode, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (!seenCodes.Add(code)) + { + continue; + } + + yield return new RoomTypePricingItemDto + { + PricingCode = code, + PricingName = string.IsNullOrWhiteSpace(item.PricingName) ? code : item.PricingName.Trim(), + RoomRent = item.RoomRent, + RoomDeposit = item.RoomDeposit, + StayHours = item.StayHours > 0 ? item.StayHours : null, + Sort = item.Sort, + IsDefault = false + }; + } + } + } +} diff --git a/EOM.TSHotelManagement.Service/Business/Room/RoomReferenceHelper.cs b/EOM.TSHotelManagement.Service/Business/Room/RoomReferenceHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..0bd758ea59d5b0200b216d7442b732202485be26 --- /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 9fff45ffaa8117693ccb770a0eb4df63ee9bccfc..7694296aa3b52d83cedb392a40b3567e07878dba 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 ebfadc02d561d37aa2b48284f76c78baa580dc97..a304bee6714ee97ddae71ae82587b46726fea40a 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 65eb0d6d93d043e47fabe15416986735d8afcd5b..c0d1fa0dfd2d936beea74a38c65f6f73118f15c4 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 5248f942f9867c04a2aa69d867770b0da66beeed..180c573e6839a458a3fdddff8a8f6effa7995620 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 13f6eae0731c9650a26275aa45bccb46ba202ec6..4d697110d48155c3d1f8e8c6fb8ba8322f81d5ea 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 de93a8e636030ef69bebf78d696f0c0bda16e1e8..15edd025bed9941dc3d86e6a5783c4e1046e9eeb 100644 --- a/EOM.TSHotelManagement.Service/Employee/History/EmployeeHistoryService.cs +++ b/EOM.TSHotelManagement.Service/Employee/History/EmployeeHistoryService.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -65,6 +65,33 @@ namespace EOM.TSHotelManagement.Service return new BaseResponse(); } + /// + /// 根据工号更新员工履历 + /// + /// + /// + public BaseResponse UpdateHistoryByEmployeeId(UpdateEmployeeHistoryInputDto workerHistory) + { + try + { + var existingHistory = workerHistoryRepository.GetSingle(a => a.Id == workerHistory.Id); + if (existingHistory == null) + { + return new BaseResponse { Message = LocalizationHelper.GetLocalizedString("Cannot found Employee History", "找不到员工履历"), Code = BusinessStatusCode.NotFound }; + } + existingHistory.StartDate = workerHistory.StartDate; + existingHistory.EndDate = workerHistory.EndDate; + existingHistory.Company = workerHistory.Company; + existingHistory.Position = workerHistory.Position; + workerHistoryRepository.Update(existingHistory); + } + catch (Exception ex) + { + return new BaseResponse { Message = LocalizationHelper.GetLocalizedString(ex.Message, ex.Message), Code = BusinessStatusCode.InternalServerError }; + } + return new BaseResponse(); + } + /// /// 根据工号查询履历信息 /// diff --git a/EOM.TSHotelManagement.Service/Employee/History/IEmployeeHistoryService.cs b/EOM.TSHotelManagement.Service/Employee/History/IEmployeeHistoryService.cs index bbcc6f50b8ad1ef55b9b15ee594c6df1e9b3f3b2..6f3fced5e4679dcde729ef24824d3d591762607e 100644 --- a/EOM.TSHotelManagement.Service/Employee/History/IEmployeeHistoryService.cs +++ b/EOM.TSHotelManagement.Service/Employee/History/IEmployeeHistoryService.cs @@ -1,4 +1,4 @@ -/* +/* * MIT License *Copyright (c) 2021 易开元(Easy-Open-Meta) @@ -37,6 +37,13 @@ namespace EOM.TSHotelManagement.Service /// BaseResponse AddHistoryByEmployeeId(CreateEmployeeHistoryInputDto workerHistory); + /// + /// 根据工号更新员工履历 + /// + /// + /// + BaseResponse UpdateHistoryByEmployeeId(UpdateEmployeeHistoryInputDto workerHistory); + /// /// 根据工号查询履历信息 /// @@ -44,4 +51,4 @@ namespace EOM.TSHotelManagement.Service /// ListOutputDto SelectHistoryByEmployeeId(ReadEmployeeHistoryInputDto wid); } -} \ No newline at end of file +}