LqXhHyhkService.AppointmentConsume.cs 20.6 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
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
{
    /// <summary>
    /// 耗卡与预约联动(拆分 partial)
    /// </summary>
    public partial class LqXhHyhkService
    {
        /// <summary>
        /// 校验预约并准备新建耗卡(可选校验「同一预约是否已有有效耗卡」)
        /// </summary>
        private async Task<LqYyjlEntity> LoadAndValidateAppointmentForNewConsumeAsync(
            LqXhHyhkCrInput input,
            LqXhHyhkEntity entity,
            bool checkDuplicateEffective)
        {
            var yyjl = await _db.Queryable<LqYyjlEntity>().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<LqXhHyhkEntity>()
                    .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;
        }

        /// <summary>
        /// 重开单:校验预约与当前有效耗卡一致,且预约处于「已转耗卡」或「已完成」。重开单不会变更预约状态(不回退、不前推),
        /// 仅将 <see cref="LqYyjlEntity.ConsumeId"/> 指向新开耗卡(见 <see cref="PerformConsumeInsertCoreAsync"/> 中 <c>applyAppointmentTransferredUpdate:false</c> 分支)。
        /// </summary>
        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("预约时间为空,无法转消耗");
        }

        /// <summary>
        /// 在同一事务内执行耗卡主表与子表插入,并按约定回写预约
        /// </summary>
        private async Task PerformConsumeInsertCoreAsync(
            LqXhHyhkEntity entity,
            LqXhHyhkCrInput input,
            DateTime lineHksj,
            LqYyjlEntity appointmentYyjl,
            bool applyAppointmentTransferredUpdate)
        {
            var memberInfo = await _db.Queryable<LqKhxxEntity>().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<LqXhPxmxEntity>();
            var allJksyjEntities = new List<LqXhJksyjEntity>();
            var allKjbsyjEntities = new List<LqXhKjbsyjEntity>();
            var allPersonTimesRecordEntities = new List<LqPersonTimesRecordEntity>();

            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<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(),
                        PerformanceType = await _db.Queryable<LqXmzlEntity>().Where(x => x.Id == item.px).Select(x => x.Fl3).FirstAsync() ?? "",
                        BeautyType = await _db.Queryable<LqXmzlEntity>().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<LqKdKdjlbEntity>()
                .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<LqYyjlEntity>()
                        .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<LqYyjlEntity>()
                        .SetColumns(x => new LqYyjlEntity { ConsumeId = newEntity.Id })
                        .Where(x => x.Id == appointmentYyjl.Id)
                        .ExecuteCommandAsync();
                }
            }
        }

        /// <summary>
        /// 作废耗卡主档及子表、人次(不改变预约状态)
        /// </summary>
        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<LqXhPxmxEntity>()
                .SetColumns(it => new LqXhPxmxEntity { IsEffective = StatusEnum.无效.GetHashCode() })
                .Where(w => w.ConsumeInfoId == card.Id)
                .ExecuteCommandAsync();
            await _db.Updateable<LqXhJksyjEntity>()
                .SetColumns(it => new LqXhJksyjEntity { IsEffective = StatusEnum.无效.GetHashCode() })
                .Where(w => w.Glkdbh == card.Id)
                .ExecuteCommandAsync();
            await _db.Updateable<LqXhKjbsyjEntity>()
                .SetColumns(it => new LqXhKjbsyjEntity { IsEffective = StatusEnum.无效.GetHashCode() })
                .Where(w => w.Glkdbh == card.Id)
                .ExecuteCommandAsync();
            await _db.Updateable<LqPersonTimesRecordEntity>()
                .SetColumns(it => new LqPersonTimesRecordEntity { IsEffective = StatusEnum.无效.GetHashCode() })
                .Where(w => w.BusinessId == card.Id)
                .ExecuteCommandAsync();
        }

        /// <summary>
        /// 按预约重开耗卡:预约为「已转耗卡」或「已完成」时可调用;作废旧有效耗卡后按预约时间新开一单,
        /// 仅更新预约 <see cref="LqYyjlEntity.ConsumeId"/>,不改变 <see cref="LqYyjlEntity.F_Status"/>。
        /// </summary>
        /// <param name="appointmentId">预约主键</param>
        /// <param name="input">与新建耗卡一致(服务端忽略客户端 hksj,以预约时间为准)</param>
        /// <returns>新开耗卡主档</returns>
        [HttpPost("ReopenForAppointment/{appointmentId}")]
        public async Task<dynamic> ReopenForAppointment(string appointmentId, [FromBody] LqXhHyhkCrInput input)
        {
            input ??= new LqXhHyhkCrInput();
            input.appointmentId = appointmentId;

            var yyjl = await _db.Queryable<LqYyjlEntity>().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<LqXhHyhkEntity>()
                .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<LqXhHyhkEntity>();
            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<LqKhxxEntity>().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<LqXhJksyjCrInput>())
                .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<LqXhHyhkEntity>().FirstAsync(x => x.Id == entity.Id);
        }
    }
}