diff --git a/server/CoreCms.Net.Model/ViewModels/SQ/SQReservationsDto.cs b/server/CoreCms.Net.Model/ViewModels/SQ/SQReservationsDto.cs index 07176e4..fd7c130 100644 --- a/server/CoreCms.Net.Model/ViewModels/SQ/SQReservationsDto.cs +++ b/server/CoreCms.Net.Model/ViewModels/SQ/SQReservationsDto.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using AutoMapper; using CoreCms.Net.Model.Entities; @@ -458,4 +458,98 @@ namespace CoreCms.Net.Model.ViewModels.SQ #endregion + #region 鸽子费审核相关DTO + + /// + /// 鸽子费审核列表项 DTO + /// + public class PigeonFeeAuditListDto + { + /// + /// 参与者ID + /// + public int participant_id { get; set; } + + /// + /// 预约ID + /// + public int reservation_id { get; set; } + + /// + /// 预约标题 + /// + public string reservation_title { get; set; } + + /// + /// 用户ID + /// + public int user_id { get; set; } + + /// + /// 用户昵称 + /// + public string nick_name { get; set; } + + /// + /// 手机号 + /// + public string mobile { get; set; } + + /// + /// 鸽子费金额 + /// + public decimal deposit_fee { get; set; } + + /// + /// 预约开始时间 + /// + public DateTime start_time { get; set; } + + /// + /// 预约结束时间 + /// + public DateTime end_time { get; set; } + + /// + /// 标记时间(发起者签到时间) + /// + public DateTime? mark_time { get; set; } + + /// + /// 赴约状态:0=未处理,1=已赴约,2=未赴约待审核,3=未赴约已审核 + /// + public int is_arrive { get; set; } + + /// + /// 房间名称 + /// + public string room_name { get; set; } + } + + /// + /// 鸽子费审核请求 DTO + /// + public class PigeonFeeAuditRequestDto + { + /// + /// 参与者ID + /// + [Required(ErrorMessage = "请输入参与者ID")] + public int participant_id { get; set; } + + /// + /// 预约ID + /// + [Required(ErrorMessage = "请输入预约ID")] + public int reservation_id { get; set; } + + /// + /// 原因(审核未通过时使用) + /// + [StringLength(255, ErrorMessage = "原因不能超过255字")] + public string reason { get; set; } + } + + #endregion + } diff --git a/server/CoreCms.Net.Web.Admin/Controllers/SQ/SQReservationsController.cs b/server/CoreCms.Net.Web.Admin/Controllers/SQ/SQReservationsController.cs index 0317cf2..0bbea5c 100644 --- a/server/CoreCms.Net.Web.Admin/Controllers/SQ/SQReservationsController.cs +++ b/server/CoreCms.Net.Web.Admin/Controllers/SQ/SQReservationsController.cs @@ -1,4 +1,4 @@ -/*********************************************************************** +/*********************************************************************** * Project: CoreCms * ProjectName: 核心内容管理系统 * Web: https://www.corecms.net @@ -35,6 +35,7 @@ using CoreCms.Net.Services; using System.Collections.Generic; using AutoMapper; using CoreCms.Net.Model.ViewModels.SQ; +using CoreCms.Net.IRepository.UnitOfWork; namespace CoreCms.Net.Web.Admin.Controllers { @@ -56,19 +57,24 @@ namespace CoreCms.Net.Web.Admin.Controllers private readonly ISQReservationParticipantsServices _SQReservationParticipantsServices; private readonly IMapper _mapper; private readonly ICoreCmsUserServices _userServices; + private readonly ISQEarningsServices _sQEarningsServices; + private readonly ISQMessageServices _sQMessageServices; + private readonly IUnitOfWork _unitOfWork; + /// /// 构造函数 /// public SQReservationsController(IWebHostEnvironment webHostEnvironment , ISQReservationsServices SQReservationsServices - , ISQRoomsServices SQRoomsServices - , ISysDictionaryServices sysDictionaryServices + , ISQRoomsServices SQRoomsServices + , ISysDictionaryServices sysDictionaryServices , ISysDictionaryDataServices sysDictionaryDataServices , ISQReservationParticipantsServices sQReservationParticipantsServices , IMapper mapper - , -ICoreCmsUserServices userServices - + , ICoreCmsUserServices userServices + , ISQEarningsServices sQEarningsServices + , ISQMessageServices sQMessageServices + , IUnitOfWork unitOfWork ) { _webHostEnvironment = webHostEnvironment; @@ -79,6 +85,9 @@ ICoreCmsUserServices userServices _SQReservationParticipantsServices = sQReservationParticipantsServices; _mapper = mapper; _userServices = userServices; + _sQEarningsServices = sQEarningsServices; + _sQMessageServices = sQMessageServices; + _unitOfWork = unitOfWork; } #region 获取列表============================================================ @@ -1566,5 +1575,339 @@ ICoreCmsUserServices userServices #endregion + #region 鸽子费审核功能====================================================== + + /// + /// 获取鸽子费待审核列表 + /// + /// + [HttpPost] + [Description("获取鸽子费待审核列表")] + public async Task GetPigeonFeeAuditList() + { + var jm = new AdminUiCallBack(); + var pageCurrent = Request.Form["page"].FirstOrDefault().ObjectToInt(1); + var pageSize = Request.Form["limit"].FirstOrDefault().ObjectToInt(30); + + // 筛选条件 + var reservationId = Request.Form["reservation_id"].FirstOrDefault().ObjectToInt(0); + var userId = Request.Form["user_id"].FirstOrDefault().ObjectToInt(0); + var keyword = Request.Form["keyword"].FirstOrDefault(); // 用户昵称或手机号 + + // 预约时间筛选 + var startTimeBegin = Request.Form["start_time_begin"].FirstOrDefault(); + var startTimeEnd = Request.Form["start_time_end"].FirstOrDefault(); + + // 标记时间筛选 + var markTimeBegin = Request.Form["mark_time_begin"].FirstOrDefault(); + var markTimeEnd = Request.Form["mark_time_end"].FirstOrDefault(); + + // 构建SQL查询 - 查询 is_arrive=2(未赴约待审核)且 status=0(未退出)且预约有鸽子费的参与者 + var sql = @" + SELECT + p.id AS participant_id, + p.reservation_id, + r.title AS reservation_title, + p.user_id, + u.nickName AS nick_name, + u.mobile, + ISNULL(r.deposit_fee, 0) AS deposit_fee, + r.start_time, + r.end_time, + r.room_name, + (SELECT TOP 1 check_reservation FROM SQReservationParticipants WHERE reservation_id = p.reservation_id AND role = 1) AS mark_time, + p.is_arrive + FROM SQReservationParticipants p + INNER JOIN SQReservations r ON p.reservation_id = r.id + INNER JOIN CoreCmsUser u ON p.user_id = u.id + WHERE p.is_arrive = 2 + AND p.status = 0 + AND ISNULL(r.deposit_fee, 0) > 0"; + + // 添加筛选条件 + if (reservationId > 0) + { + sql += $" AND p.reservation_id = {reservationId}"; + } + if (userId > 0) + { + sql += $" AND p.user_id = {userId}"; + } + if (!string.IsNullOrEmpty(keyword)) + { + sql += $" AND (u.nickName LIKE '%{keyword}%' OR u.mobile LIKE '%{keyword}%')"; + } + if (!string.IsNullOrEmpty(startTimeBegin)) + { + var dt = startTimeBegin.ObjectToDate(); + sql += $" AND r.start_time >= '{dt:yyyy-MM-dd HH:mm:ss}'"; + } + if (!string.IsNullOrEmpty(startTimeEnd)) + { + var dt = startTimeEnd.ObjectToDate(); + sql += $" AND r.start_time <= '{dt:yyyy-MM-dd HH:mm:ss}'"; + } + if (!string.IsNullOrEmpty(markTimeBegin)) + { + var dt = markTimeBegin.ObjectToDate(); + sql += $" AND (SELECT TOP 1 check_reservation FROM SQReservationParticipants WHERE reservation_id = p.reservation_id AND role = 1) >= '{dt:yyyy-MM-dd HH:mm:ss}'"; + } + if (!string.IsNullOrEmpty(markTimeEnd)) + { + var dt = markTimeEnd.ObjectToDate(); + sql += $" AND (SELECT TOP 1 check_reservation FROM SQReservationParticipants WHERE reservation_id = p.reservation_id AND role = 1) <= '{dt:yyyy-MM-dd HH:mm:ss}'"; + } + + // 获取总数 + var countSql = $"SELECT COUNT(1) FROM ({sql}) AS t"; + var totalCount = await _unitOfWork.GetDbClient().Ado.GetIntAsync(countSql); + + // 分页排序 + sql += $" ORDER BY (SELECT TOP 1 check_reservation FROM SQReservationParticipants WHERE reservation_id = p.reservation_id AND role = 1) DESC"; + sql += $" OFFSET {(pageCurrent - 1) * pageSize} ROWS FETCH NEXT {pageSize} ROWS ONLY"; + + var list = await _unitOfWork.GetDbClient().Ado.SqlQueryAsync(sql); + + jm.data = list; + jm.code = 0; + jm.count = totalCount; + jm.msg = "数据调用成功!"; + return jm; + } + + /// + /// 审核通过 - 扣除鸽子费并分配给已赴约用户 + /// + /// 审核请求参数 + /// + [HttpPost] + [Description("鸽子费审核通过")] + public async Task ApprovePigeonFee([FromBody] PigeonFeeAuditRequestDto request) + { + var jm = new AdminUiCallBack(); + + if (request == null || request.participant_id <= 0 || request.reservation_id <= 0) + { + jm.msg = "参数错误"; + return jm; + } + + // 获取当前登录管理员ID + var operatorId = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "id")?.Value.ObjectToInt(0) ?? 0; + + // 查询参与者信息 + var participant = await _SQReservationParticipantsServices.QueryByClauseAsync( + p => p.id == request.participant_id && p.reservation_id == request.reservation_id); + if (participant == null) + { + jm.msg = "参与者记录不存在"; + return jm; + } + + // 检查状态是否为待审核 + if (participant.is_arrive != 2) + { + jm.msg = "该用户不是待审核状态"; + return jm; + } + + // 查询预约信息 + var reservation = await _SQReservationsServices.QueryByClauseAsync(r => r.id == request.reservation_id); + if (reservation == null) + { + jm.msg = "预约不存在"; + return jm; + } + + // 获取鸽子费金额 + var depositFee = reservation.deposit_fee ?? 0; + if (depositFee <= 0) + { + jm.msg = "该预约没有鸽子费"; + return jm; + } + + // 获取未赴约用户信息(用于消息通知) + var absentUser = await _userServices.QueryByIdAsync(participant.user_id); + var absentUserName = absentUser?.nickName ?? $"用户{participant.user_id}"; + + // 获取发起者签到时间 + var initiatorParticipant = await _SQReservationParticipantsServices.QueryByClauseAsync( + p => p.reservation_id == request.reservation_id && p.role == 1); + var checkTime = initiatorParticipant?.check_reservation; + + // 查询符合条件的已赴约用户(排除发起者和未赴约用户本人) + // 条件:is_arrive=1, status=0, join_time < 签到时间, role != 1(排除发起者) + var eligibleParticipantsSql = $@" + SELECT p.*, u.nickName, u.mobile + FROM SQReservationParticipants p + INNER JOIN CoreCmsUser u ON p.user_id = u.id + WHERE p.reservation_id = {request.reservation_id} + AND p.is_arrive = 1 + AND p.status = 0 + AND p.role != 1"; + + // 如果有签到时间,则只选择签到之前加入的参与者 + if (checkTime.HasValue) + { + eligibleParticipantsSql += $" AND p.join_time < '{checkTime.Value:yyyy-MM-dd HH:mm:ss}'"; + } + + var eligibleParticipants = await _unitOfWork.GetDbClient().Ado.SqlQueryAsync(eligibleParticipantsSql); + var participantCount = eligibleParticipants?.Count ?? 0; + + try + { + _unitOfWork.BeginTran(); + + // 1. 更新未赴约用户状态为已审核 + await _SQReservationParticipantsServices.UpdateAsync( + it => new SQReservationParticipants + { + is_arrive = 3 // 未赴约,已审核 + }, + it => it.id == request.participant_id); + + // 2. 如果有符合条件的已赴约用户,分配鸽子费 + if (participantCount > 0) + { + // 计算每人分得金额(保留两位小数,四舍五入) + var amountPerPerson = Math.Round(depositFee / participantCount, 2, MidpointRounding.AwayFromZero); + + foreach (var ep in eligibleParticipants) + { + int epUserId = (int)ep.user_id; + string epNickName = ep.nickName ?? $"用户{epUserId}"; + + // 为每个用户创建收益记录 + var earningsResult = await _sQEarningsServices.AddEarningsAsync( + userId: epUserId, + reservationId: request.reservation_id, + roomId: reservation.room_id, + roomNumber: reservation.room_id.ToString(), + roomName: reservation.room_name, + roomFee: 0, // 鸽子费不涉及房费 + earnings: amountPerPerson, + type: 2, // 鸽子费类型 + remark: "未赴约用户鸽子费分配", + operatorId: operatorId); + + // 发送消息给分到钱的用户 + await _sQMessageServices.SendSystemNoticeAsync( + epUserId, + "鸽子费分配通知", + $"{absentUserName} 未能按时参加预约,您收到鸽子费{amountPerPerson}元", + relatedType: 1, // 组局 + relatedId: request.reservation_id); + } + } + + // 3. 发送消息给未赴约用户 + await _sQMessageServices.SendSystemNoticeAsync( + participant.user_id, + "鸽子费扣除通知", + $"您未按时参加预约,鸽子费已扣除{depositFee}元。", + relatedType: 1, // 组局 + relatedId: request.reservation_id); + + _unitOfWork.CommitTran(); + + jm.code = 0; + jm.msg = participantCount > 0 + ? $"审核通过,鸽子费{depositFee}元已分配给{participantCount}位已赴约用户" + : "审核通过,但没有符合条件的已赴约用户分配鸽子费"; + } + catch (Exception ex) + { + _unitOfWork.RollbackTran(); + jm.msg = "审核失败:" + ex.Message; + } + + return jm; + } + + /// + /// 审核未通过 - 退还鸽子费 + /// + /// 审核请求参数 + /// + [HttpPost] + [Description("鸽子费审核未通过")] + public async Task RejectPigeonFee([FromBody] PigeonFeeAuditRequestDto request) + { + var jm = new AdminUiCallBack(); + + if (request == null || request.participant_id <= 0 || request.reservation_id <= 0) + { + jm.msg = "参数错误"; + return jm; + } + + // 查询参与者信息 + var participant = await _SQReservationParticipantsServices.QueryByClauseAsync( + p => p.id == request.participant_id && p.reservation_id == request.reservation_id); + if (participant == null) + { + jm.msg = "参与者记录不存在"; + return jm; + } + + // 检查状态是否为待审核 + if (participant.is_arrive != 2) + { + jm.msg = "该用户不是待审核状态"; + return jm; + } + + // 查询预约信息 + var reservation = await _SQReservationsServices.QueryByClauseAsync(r => r.id == request.reservation_id); + if (reservation == null) + { + jm.msg = "预约不存在"; + return jm; + } + + try + { + _unitOfWork.BeginTran(); + + // 1. 更新参与者状态 + // is_arrive = 1(已赴约) + // status = 0(正常,未退出) + // is_refund = 3(发起退款,由退款任务处理) + await _SQReservationParticipantsServices.UpdateAsync( + it => new SQReservationParticipants + { + is_arrive = 1, // 已赴约 + status = 0, // 正常 + is_refund = 3 // 发起退款 + }, + it => it.id == request.participant_id); + + // 2. 发送消息给用户 + await _sQMessageServices.SendSystemNoticeAsync( + participant.user_id, + "鸽子费审核结果", + "您的鸽子费已原路退回。", + relatedType: 1, // 组局 + relatedId: request.reservation_id); + + _unitOfWork.CommitTran(); + + jm.code = 0; + jm.msg = "审核未通过,鸽子费将原路退回"; + } + catch (Exception ex) + { + _unitOfWork.RollbackTran(); + jm.msg = "审核失败:" + ex.Message; + } + + return jm; + } + + #endregion + + } } diff --git a/server/CoreCms.Net.Web.Admin/Doc.xml b/server/CoreCms.Net.Web.Admin/Doc.xml index 1cb613e..2bce487 100644 --- a/server/CoreCms.Net.Web.Admin/Doc.xml +++ b/server/CoreCms.Net.Web.Admin/Doc.xml @@ -4125,7 +4125,7 @@ 预约表 - + 构造函数 @@ -4235,6 +4235,26 @@ + + + 获取鸽子费待审核列表 + + + + + + 审核通过 - 扣除鸽子费并分配给已赴约用户 + + 审核请求参数 + + + + + 审核未通过 - 退还鸽子费 + + 审核请求参数 + + diff --git a/server/CoreCms.Net.Web.Admin/wwwroot/views/sq/sqreservations/pigeon-fee-audit.html b/server/CoreCms.Net.Web.Admin/wwwroot/views/sq/sqreservations/pigeon-fee-audit.html new file mode 100644 index 0000000..33c3a64 --- /dev/null +++ b/server/CoreCms.Net.Web.Admin/wwwroot/views/sq/sqreservations/pigeon-fee-audit.html @@ -0,0 +1,259 @@ +鸽子费审核 + + +
+
+ +
+
+ +
+ 审核未按时赴约用户的鸽子费处理,审核通过则扣除并分配给已赴约用户,审核未通过则退还鸽子费 +
+ + +
+
+
+ + + + + + + + diff --git a/server/鸽子费审核功能需求文档.md b/server/鸽子费审核功能需求文档.md index 071b3b5..baffe8b 100644 --- a/server/鸽子费审核功能需求文档.md +++ b/server/鸽子费审核功能需求文档.md @@ -160,7 +160,6 @@ ### 5.1 页面路径 - **建议路径**:`/views/sq/sqreservations/pigeon-fee-audit.html` -- **或**:在现有的预约管理页面中新增一个标签页 ### 5.2 功能按钮 - **审核通过**:点击后弹出确认框,确认后执行审核通过流程 diff --git a/uniapp/mahjong_group b/uniapp/mahjong_group index a58b4e4..316d1e2 160000 --- a/uniapp/mahjong_group +++ b/uniapp/mahjong_group @@ -1 +1 @@ -Subproject commit a58b4e431b60ff83fa9e66cb4d014417e5c423d9 +Subproject commit 316d1e2b5d7013d0b9f7507ac2f15ead5ff4d703