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);
}
}
}