using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Mapster; using Microsoft.AspNetCore.Mvc; using NCC.Common.Enum; using NCC.Common.Extension; using NCC.Extend.Entitys.Dto.LqXhHyhk; using NCC.Extend.Entitys.Dto.LqXhJksyj; using NCC.Extend.Entitys.Dto.LqXhPxmx; using NCC.Extend.Entitys.Enum; using NCC.Extend.Entitys.lq_kd_kdjlb; using NCC.Extend.Entitys.lq_khxx; using NCC.Extend.Entitys.lq_person_times_record; using NCC.Extend.Entitys.lq_xh_hyhk; using NCC.Extend.Entitys.lq_xh_jksyj; using NCC.Extend.Entitys.lq_xh_kjbsyj; using NCC.Extend.Entitys.lq_xh_pxmx; using NCC.Extend.Entitys.lq_xmzl; using NCC.Extend.Entitys.lq_yyjl; using NCC.Extend.Helpers; using NCC.FriendlyException; using SqlSugar; using Yitter.IdGenerator; namespace NCC.Extend.LqXhHyhk { /// /// 耗卡与预约联动(拆分 partial) /// public partial class LqXhHyhkService { /// /// 校验预约并准备新建耗卡(可选校验「同一预约是否已有有效耗卡」) /// private async Task LoadAndValidateAppointmentForNewConsumeAsync( LqXhHyhkCrInput input, LqXhHyhkEntity entity, bool checkDuplicateEffective) { var yyjl = await _db.Queryable().FirstAsync(x => x.Id == input.appointmentId); if (yyjl == null) throw NCCException.Oh("预约记录不存在"); if (yyjl.Gk != entity.Hy) throw NCCException.Oh("预约与耗卡会员不一致"); if (yyjl.Djmd != entity.Md) throw NCCException.Oh("预约与耗卡门店不一致"); if (!yyjl.Yysj.HasValue) throw NCCException.Oh("预约时间为空,无法转消耗"); if (checkDuplicateEffective) { var dup = await _db.Queryable() .AnyAsync(x => x.AppointmentId == input.appointmentId && x.IsEffective == StatusEnum.有效.GetHashCode()); if (dup) throw NCCException.Oh("该预约已存在有效耗卡"); } if (!LqYyjlStatusHelper.AllowsTransferToConsume(yyjl.F_Status)) throw NCCException.Oh("当前预约状态不允许转耗卡"); return yyjl; } /// /// 重开单:校验预约与当前有效耗卡一致,且预约处于「已转耗卡」或「已完成」。重开单不会变更预约状态(不回退、不前推), /// 仅将 指向新开耗卡(见 applyAppointmentTransferredUpdate:false 分支)。 /// private static void ValidateYyjlForReopen(LqYyjlEntity yyjl, LqXhHyhkEntity oldCard) { if (yyjl.Gk != oldCard.Hy) throw NCCException.Oh("预约与耗卡会员不一致"); if (yyjl.Djmd != oldCard.Md) throw NCCException.Oh("预约与耗卡门店不一致"); var normalized = LqYyjlStatusHelper.NormalizeStored(yyjl.F_Status); if (normalized != LqYyjlStatusTexts.TransferredToConsume && normalized != LqYyjlStatusTexts.Completed) throw NCCException.Oh("当前预约状态不允许重开单"); if (!yyjl.Yysj.HasValue) throw NCCException.Oh("预约时间为空,无法转消耗"); } /// /// 在同一事务内执行耗卡主表与子表插入,并按约定回写预约 /// private async Task PerformConsumeInsertCoreAsync( LqXhHyhkEntity entity, LqXhHyhkCrInput input, DateTime lineHksj, LqYyjlEntity appointmentYyjl, bool applyAppointmentTransferredUpdate) { var memberInfo = await _db.Queryable().Where(w => w.Id == entity.Hy).FirstAsync(); if (memberInfo.Khlx == MemberTypeEnum.线索.GetHashCode().ToString()) memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString(); if (entity.Xfje > 0) memberInfo.LastConsumeTime = entity.Hksj; await _db.Updateable(memberInfo).ExecuteCommandAsync(); var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync(); var allPxmxEntities = new List(); var allJksyjEntities = new List(); var allKjbsyjEntities = new List(); var allPersonTimesRecordEntities = new List(); if (input.lqXhPxmxList != null && input.lqXhPxmxList.Any()) { foreach (var item in input.lqXhPxmxList) { var lqXhPxmxEntity = new LqXhPxmxEntity { Id = YitIdHelper.NextId().ToString(), ConsumeInfoId = newEntity.Id, BillingItemId = item.billingItemId, CreateTIme = DateTime.Now, Yjsj = lineHksj, MemberId = entity.Hy, OriginalProjectNumber = item.projectNumber ?? 0, OvertimeProjectNumber = (decimal)(entity.OvertimeCoefficient * (item.projectNumber ?? 0)), ProjectNumber = (decimal)( (item.projectNumber ?? 0) + (entity.OvertimeCoefficient * (item.projectNumber ?? 0))), TotalPrice = (decimal)(item.pxjg * (item.projectNumber ?? 1)), Px = item.px, Pxmc = item.pxmc, Pxjg = item.pxjg, SourceType = item.sourceType, IsEffective = StatusEnum.有效.GetHashCode(), ItemCategory = await _db.Queryable().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(), PerformanceType = await _db.Queryable().Where(x => x.Id == item.px).Select(x => x.Fl3).FirstAsync() ?? "", BeautyType = await _db.Queryable().Where(x => x.Id == item.px).Select(x => x.BeautyType).FirstAsync(), }; allPxmxEntities.Add(lqXhPxmxEntity); if (item.lqXhJksyjList != null && item.lqXhJksyjList.Any()) { foreach (var ijks_tem in item.lqXhJksyjList) { allJksyjEntities.Add( new LqXhJksyjEntity { Id = YitIdHelper.NextId().ToString(), Glkdbh = newEntity.Id, Jks = ijks_tem.jks, Jksxm = ijks_tem.jksxm, Jkszh = ijks_tem.jkszh, Jksyj = ijks_tem.jksyj, Yjsj = lineHksj, CreateTime = DateTime.Now, JsjId = ijks_tem.jsjId, Kdpxid = lqXhPxmxEntity.Id, OriginalLaborCost = ijks_tem.laborCost, OvertimeLaborCost = (decimal)(entity.OvertimeCoefficient * (ijks_tem.laborCost ?? 0)), LaborCost = (decimal)( (ijks_tem.laborCost ?? 0) + (entity.OvertimeCoefficient * (ijks_tem.laborCost ?? 0))), OriginalKdpxNumber = ijks_tem.kdpxNumber, OvertimeKdpxNumber = (decimal)(entity.OvertimeCoefficient * (ijks_tem.kdpxNumber ?? 0)), KdpxNumber = (decimal)( (ijks_tem.kdpxNumber ?? 0) + (entity.OvertimeCoefficient * (ijks_tem.kdpxNumber ?? 0))) + (ijks_tem.accompaniedProjectNumber ?? 0), IsAccompanied = ijks_tem.isAccompanied, AccompaniedProjectNumber = ijks_tem.accompaniedProjectNumber, IsEffective = StatusEnum.有效.GetHashCode(), ItemCategory = lqXhPxmxEntity.ItemCategory, ItemId = lqXhPxmxEntity.Px, StoreId = entity.Md, ItemName = lqXhPxmxEntity.Pxmc, PerformanceType = lqXhPxmxEntity.PerformanceType, BeautyType = lqXhPxmxEntity.BeautyType, } ); } } if (item.lqXhKjbsyjList != null && item.lqXhKjbsyjList.Any()) { foreach (var ikjbs_tem in item.lqXhKjbsyjList) { allKjbsyjEntities.Add( new LqXhKjbsyjEntity { Id = YitIdHelper.NextId().ToString(), Glkdbh = newEntity.Id, Kjbls = ikjbs_tem.kjbls, Kjblsxm = ikjbs_tem.kjblsxm, Kjblszh = ikjbs_tem.kjblszh, Kjblsyj = ikjbs_tem.kjblsyj, Yjsj = lineHksj, CreateTime = DateTime.Now, Hkpxid = lqXhPxmxEntity.Id, OriginalHdpxNumber = ikjbs_tem.hdpxNumber, OvertimeHdpxNumber = 0, HdpxNumber = ikjbs_tem.hdpxNumber, OriginalLaborCost = ikjbs_tem.laborCost, OvertimeLaborCost = 0, LaborCost = ikjbs_tem.laborCost, IsEffective = StatusEnum.有效.GetHashCode(), ItemCategory = lqXhPxmxEntity.ItemCategory, ItemId = lqXhPxmxEntity.Px, StoreId = entity.Md, ItemName = lqXhPxmxEntity.Pxmc, PerformanceType = lqXhPxmxEntity.PerformanceType, BeautyType = lqXhPxmxEntity.BeautyType, } ); } } } } var billingRecord = await _db.Queryable() .Where(x => x.Kdhy == entity.Hy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.Sfyj > 0) .AnyAsync(); var NoTallJksyjEntities = allJksyjEntities .Where(x => x.Jks != null && x.Jks != "" && (x.Jksxm == null || !x.Jksxm.Contains("T区"))) .ToList(); var jksCount = NoTallJksyjEntities.GroupBy(x => x.Jks).Count(); foreach (var item in NoTallJksyjEntities.Select(x => x.Jks).Distinct()) { allPersonTimesRecordEntities.Add( new LqPersonTimesRecordEntity { Id = YitIdHelper.NextId().ToString(), BusinessId = newEntity.Id, BusinessType = "耗卡", PersonType = "健康师", PersonId = item, PersonName = NoTallJksyjEntities.Where(x => x.Jks == item).Select(x => x.Jksxm).First(), MemberId = entity.Hy, MemberName = memberInfo.Khmc, WorkDate = lineHksj, WorkMonth = lineHksj.ToDate().ToString("yyyyMM"), Quantity = 1 / (decimal)jksCount, CreateTime = DateTime.Now, IsEffective = StatusEnum.有效.GetHashCode(), HasBilling = billingRecord ? 1 : 0, } ); } var NoTallKjbsyjEntities = allKjbsyjEntities .Where(x => x.Kjbls != null && x.Kjbls != "" && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))) .ToList(); var kjbCount = NoTallKjbsyjEntities.GroupBy(x => x.Kjbls).Count(); foreach (var item in NoTallKjbsyjEntities.Select(x => x.Kjbls).Distinct()) { allPersonTimesRecordEntities.Add( new LqPersonTimesRecordEntity { Id = YitIdHelper.NextId().ToString(), BusinessId = newEntity.Id, BusinessType = "耗卡", PersonType = "科技部老师", PersonId = item, PersonName = NoTallKjbsyjEntities.Where(x => x.Kjbls == item).Select(x => x.Kjblsxm).First(), MemberId = entity.Hy, MemberName = memberInfo.Khmc, WorkDate = lineHksj, WorkMonth = lineHksj.ToDate().ToString("yyyyMM"), Quantity = 1 / (decimal)kjbCount, CreateTime = DateTime.Now, IsEffective = StatusEnum.有效.GetHashCode(), HasBilling = billingRecord ? 1 : 0, } ); } if (allPersonTimesRecordEntities.Any()) await _db.Insertable(allPersonTimesRecordEntities).ExecuteCommandAsync(); if (allPxmxEntities.Any()) await _db.Insertable(allPxmxEntities).ExecuteCommandAsync(); if (allJksyjEntities.Any()) await _db.Insertable(allJksyjEntities).ExecuteCommandAsync(); if (allKjbsyjEntities.Any()) await _db.Insertable(allKjbsyjEntities).ExecuteCommandAsync(); if (appointmentYyjl != null) { if (applyAppointmentTransferredUpdate) { await _db.Updateable() .SetColumns(x => new LqYyjlEntity { ConsumeId = newEntity.Id, ServiceEndTime = DateTime.Now, F_Status = LqYyjlStatusTexts.TransferredToConsume, }) .Where(x => x.Id == appointmentYyjl.Id) .ExecuteCommandAsync(); } else { await _db.Updateable() .SetColumns(x => new LqYyjlEntity { ConsumeId = newEntity.Id }) .Where(x => x.Id == appointmentYyjl.Id) .ExecuteCommandAsync(); } } } /// /// 作废耗卡主档及子表、人次(不改变预约状态) /// private async Task InvalidateConsumeCardCascadeAsync(LqXhHyhkEntity card, string remarks) { card.IsEffective = StatusEnum.无效.GetHashCode(); card.UpdateTime = DateTime.Now; card.CancelRemark = remarks; await _db.Updateable(card).ExecuteCommandAsync(); await _db.Updateable() .SetColumns(it => new LqXhPxmxEntity { IsEffective = StatusEnum.无效.GetHashCode() }) .Where(w => w.ConsumeInfoId == card.Id) .ExecuteCommandAsync(); await _db.Updateable() .SetColumns(it => new LqXhJksyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }) .Where(w => w.Glkdbh == card.Id) .ExecuteCommandAsync(); await _db.Updateable() .SetColumns(it => new LqXhKjbsyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }) .Where(w => w.Glkdbh == card.Id) .ExecuteCommandAsync(); await _db.Updateable() .SetColumns(it => new LqPersonTimesRecordEntity { IsEffective = StatusEnum.无效.GetHashCode() }) .Where(w => w.BusinessId == card.Id) .ExecuteCommandAsync(); } /// /// 按预约重开耗卡:预约为「已转耗卡」或「已完成」时可调用;作废旧有效耗卡后按预约时间新开一单, /// 仅更新预约 ,不改变 。 /// /// 预约主键 /// 与新建耗卡一致(服务端忽略客户端 hksj,以预约时间为准) /// 新开耗卡主档 [HttpPost("ReopenForAppointment/{appointmentId}")] public async Task ReopenForAppointment(string appointmentId, [FromBody] LqXhHyhkCrInput input) { input ??= new LqXhHyhkCrInput(); input.appointmentId = appointmentId; var yyjl = await _db.Queryable().FirstAsync(x => x.Id == appointmentId); if (yyjl == null) throw NCCException.Oh("预约记录不存在"); if (LqYyjlStatusHelper.NormalizeStored(yyjl.F_Status) == LqYyjlStatusTexts.Cancelled) throw NCCException.Oh("当前预约状态不允许重开单"); var oldCard = await _db.Queryable() .Where(x => x.AppointmentId == appointmentId && x.IsEffective == StatusEnum.有效.GetHashCode()) .OrderBy(x => x.CreateTime, OrderByType.Desc) .FirstAsync(); if (oldCard == null) throw NCCException.Oh("不存在可重开的耗卡"); await ConsumeEditableDaysHelper.EnsureConsumeWithinEditableWindowAsync(_db, oldCard); ValidateYyjlForReopen(yyjl, oldCard); var entity = input.Adapt(); entity.Id = YitIdHelper.NextId().ToString(); entity.Hy = string.IsNullOrWhiteSpace(entity.Hy) ? oldCard.Hy : entity.Hy; entity.Md = string.IsNullOrWhiteSpace(entity.Md) ? oldCard.Md : entity.Md; entity.Czry = _userManager.UserId; var memberRow = await _db.Queryable().Where(w => w.Id == entity.Hy).FirstAsync(); if (memberRow == null) throw NCCException.Oh(ErrorCode.COM1005, "会员不存在"); entity.MemberPhone = memberRow.Sjh; entity.CreateTime = DateTime.Now; entity.IsEffective = StatusEnum.有效.GetHashCode(); entity.UpdateTime = DateTime.Now; entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0; entity.OriginalSgfy = input.sgfy; entity.AppointmentId = appointmentId; var jksOriginalLaborCostSum = input.lqXhPxmxList? .SelectMany(p => p.lqXhJksyjList ?? Enumerable.Empty()) .Sum(j => j.laborCost ?? 0) ?? 0; entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * entity.OvertimeCoefficient); entity.Sgfy = entity.OriginalSgfy + entity.OvertimeSgfy; var lineHksj = yyjl.Yysj!.Value; entity.Hksj = lineHksj; entity.RoomId = yyjl.RoomId; entity.RoomName = yyjl.RoomName; try { _db.BeginTran(); await InvalidateConsumeCardCascadeAsync(oldCard, "重开单作废"); await PerformConsumeInsertCoreAsync(entity, input, lineHksj, yyjl, applyAppointmentTransferredUpdate: false); _db.CommitTran(); } catch (Exception ex) { _db.RollbackTran(); throw NCCException.Oh(ErrorCode.COM1000, $"重开单失败: {ex.Message}"); } return await _db.Queryable().FirstAsync(x => x.Id == entity.Id); } } }