using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NCC.ClayObject;
using NCC.Common.Configuration;
using NCC.Common.Core.Manager;
using NCC.Common.Enum;
using NCC.Common.Extension;
using NCC.Common.Filter;
using NCC.Common.Helper;
using NCC.Common.Model.NPOI;
using NCC.DataEncryption;
using NCC.Dependency;
using NCC.DynamicApiController;
using NCC.Extend.Entitys.Dto.LqPersonTimesRecord;
using NCC.Extend.Entitys.Dto.LqXhHyhk;
using NCC.Extend.Entitys.Dto.LqXhJksyj;
using NCC.Extend.Entitys.Dto.LqXhKjbsyj;
using NCC.Extend.Entitys.Dto.LqXhPxmx;
using NCC.Extend.Entitys.Dto.LqYyjl;
using NCC.Extend.Entitys.Enum;
using NCC.Extend.Entitys.lq_kd_kdjlb;
using NCC.Extend.Entitys.lq_kd_pxmx;
using NCC.Extend.Entitys.lq_khxx;
using NCC.Extend.Entitys.lq_person_times_record;
using NCC.Extend.Entitys.lq_xh_feedback;
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_mdxx;
using NCC.Extend.Interfaces.LqXhHyhk;
using NCC.FriendlyException;
using NCC.JsonSerialization;
using SqlSugar;
using Yitter.IdGenerator;
namespace NCC.Extend.LqXhHyhk
{
///
/// 耗卡记录表服务
///
[ApiDescriptionSettings(Tag = "绿纤耗卡记录表服务", Name = "LqXhHyhk", Order = 200, Groups = new[] { "Default" })]
[Route("api/Extend/[controller]")]
public class LqXhHyhkService : ILqXhHyhkService, IDynamicApiController, ITransient
{
private readonly ISqlSugarRepository _lqXhHyhkRepository;
private readonly ISqlSugarRepository _lqXhJksyjRepository;
private readonly ISqlSugarRepository _lqXhKjbsyjRepository;
private readonly ISqlSugarRepository _lqXhPxmxRepository;
private readonly SqlSugarScope _db;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
///
/// 初始化一个类型的新实例
///
public LqXhHyhkService(
ISqlSugarRepository lqXhHyhkRepository,
ISqlSugarRepository lqXhJksyjRepository,
ISqlSugarRepository lqXhKjbsyjRepository,
ISqlSugarRepository lqXhPxmxRepository,
IUserManager userManager,
ILogger logger
)
{
_lqXhHyhkRepository = lqXhHyhkRepository;
_db = _lqXhHyhkRepository.Context;
_lqXhJksyjRepository = lqXhJksyjRepository;
_lqXhKjbsyjRepository = lqXhKjbsyjRepository;
_lqXhPxmxRepository = lqXhPxmxRepository;
_userManager = userManager;
_logger = logger;
}
#region 获取会员耗卡
///
/// 获取会员耗卡
///
///
/// 获取耗卡记录及其关联的品项明细、健康师业绩、科技部老师业绩信息
/// 按照耗卡的完整格式返回数据,不包含汇总信息
///
/// 返回数据结构:
/// - 主表信息:耗卡基础信息、门店信息、会员信息等
/// - 品项明细列表:每个品项包含完整的项目信息(项目次数、是否有效、来源类型等)
/// - 健康师业绩列表:按品项关联的健康师业绩信息
/// - 科技部老师业绩列表:按品项关联的科技部老师业绩信息
///
/// 耗卡记录主键ID
/// 耗卡记录完整信息
/// 查询成功
/// 耗卡记录不存在
/// 服务器内部错误
[HttpGet("{id}")]
public async Task GetInfo(string id)
{
try
{
// 1. 查询主表信息
var entity = await _db.Queryable().FirstAsync(p => p.Id == id);
if (entity == null)
{
throw NCCException.Oh(ErrorCode.COM1005, "耗卡记录不存在");
}
var output = entity.Adapt();
//获取会员信息
var MemberInfo = await _db.Queryable().FirstAsync(p => p.Id == entity.Hy);
output.hymc = MemberInfo.Khmc;
output.memberPhone = MemberInfo.Sjh;
// 2. 查询品项明细列表
var lqXhPxmxList = await _db.Queryable().Where(w => w.ConsumeInfoId == entity.Id).ToListAsync();
// 3. 查询健康师业绩列表
var lqXhJksyjList = await _db.Queryable().Where(w => w.Glkdbh == entity.Id).ToListAsync();
// 4. 查询科技部老师业绩列表
var lqXhKjbsyjList = await _db.Queryable().Where(w => w.Glkdbh == entity.Id).ToListAsync();
// 获取人次记录列表
var personTimesRecordList = await _db.Queryable().Where(x => x.BusinessId == entity.Id && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
// 5. 构建品项明细输出,每个品项关联对应的业绩信息
var pxmxOutputList = new List();
foreach (var pxmx in lqXhPxmxList)
{
var pxmxOutput = new LqXhPxmxInfoOutput
{
id = pxmx.Id,
consumeInfoId = pxmx.ConsumeInfoId,
billingItemId = pxmx.BillingItemId,
px = pxmx.Px,
pxmc = pxmx.Pxmc,
pxjg = pxmx.Pxjg,
memberId = pxmx.MemberId,
createTime = pxmx.CreateTIme,
projectNumber = pxmx.ProjectNumber,
sourceType = pxmx.SourceType,
totalPrice = pxmx.TotalPrice,
isEffective = pxmx.IsEffective,
originalProjectNumber = pxmx.OriginalProjectNumber,
overtimeProjectNumber = pxmx.OvertimeProjectNumber
};
// 关联该品项的健康师业绩
var jksyjForPx = lqXhJksyjList.Where(j => j.Kdpxid == pxmx.Id).ToList();
pxmxOutput.lqXhJksyjList = jksyjForPx.Adapt>();
// 关联该品项的科技部老师业绩
var kjbsyjForPx = lqXhKjbsyjList.Where(k => k.Hkpxid == pxmx.Id).ToList();
pxmxOutput.lqXhKjbsyjList = kjbsyjForPx.Adapt>();
pxmxOutputList.Add(pxmxOutput);
}
// 6. 设置输出结果
output.lqXhPxmxList = pxmxOutputList;
// 7. 设置全局业绩列表(用于兼容性,但主要使用品项关联的业绩)
output.lqXhJksyjList = lqXhJksyjList.Adapt>();
output.lqXhKjbsyjList = lqXhKjbsyjList.Adapt>();
return output;
}
catch (Exception ex)
{
_logger.LogError(ex, "获取耗卡记录失败,ID:{Id}", id);
throw NCCException.Oh(ErrorCode.COM1000, "获取耗卡记录失败");
}
}
#endregion
#region 获取会员耗卡列表
///
/// 获取会员耗卡列表
///
///
/// 根据多种条件查询会员耗卡记录列表,支持分页、排序、筛选等功能
///
/// 示例请求:
/// GET /api/Extend/LqXhHyhk/GetList?currentPage=1&pageSize=10&jksId=健康师ID&startTime=2025-01-01&endTime=2025-01-31
///
/// 传入参数说明:
/// - currentPage: 当前页码(必填,默认1)
/// - pageSize: 每页数量(必填,默认10)
/// - sidx: 排序字段(可选,默认"id")
/// - sort: 排序方式(可选,ASC/DESC,默认DESC)
/// - keyword: 关键字搜索(可选,搜索会员名称、账号、手机号)
/// - id: 耗卡编号(可选,模糊匹配)
/// - md: 门店ID(可选,精确匹配)
/// - mdbh: 门店编号(可选,模糊匹配)
/// - mdmc: 门店名称(可选,模糊匹配)
/// - hy: 会员ID(可选,精确匹配)
/// - hyzh: 会员账号(可选,模糊匹配)
/// - hymc: 会员名称(可选,模糊匹配)
/// - gklx: 顾客类型(可选,精确匹配)
/// - sfykjb: 是否有科技部(可选,精确匹配)
/// - hksj: 耗卡时间(可选,格式:开始时间,结束时间,如:2025-01-01,2025-01-31)
/// - czry: 操作人员ID(可选,精确匹配)
/// - isEffective: 是否有效(可选,0=全部,1=有效,2=无效,默认0)
/// - jksId: 健康师ID(可选,传入后只返回该健康师参与的耗卡记录)
/// - kjblsId: 科技部老师ID(可选,传入后只返回该老师参与的耗卡记录)
///
/// 返回结果说明:
/// - code: 响应状态码(200=成功)
/// - msg: 响应消息
/// - data: 分页数据对象
/// - list: 耗卡记录列表,每个记录包含:
/// - id: 耗卡编号
/// - md: 门店ID
/// - mdbh: 门店编号
/// - mdmc: 门店名称
/// - hy: 会员ID
/// - hyzh: 会员账号
/// - hymc: 会员名称
/// - memberPhone: 会员手机号
/// - gklx: 顾客类型
/// - xfje: 消费金额
/// - sgfy: 手工费用
/// - sfykjb: 是否有科技部
/// - hksj: 耗卡时间
/// - czry: 操作人员ID
/// - isEffective: 是否有效
/// - signatureFile: 签名文件
/// - overtimeCoefficient: 加班系数(NULL或0表示非加班单,大于0表示加班单)
/// - originalSgfy: 原始手工费(用户输入的原始值)
/// - overtimeSgfy: 加班手工费(加班计算后的增量值)
/// - ConsumeDetails: 耗卡明细列表(品项明细),每个明细包含:
/// - id: 明细编号
/// - consumeInfoId: 耗卡记录ID
/// - billingItemId: 开单品项明细表ID
/// - px: 品项编号
/// - pxmc: 品项名称
/// - pxjg: 品项价格
/// - memberId: 会员ID
/// - createTime: 创建时间
/// - projectNumber: 项目次数
/// - originalProjectNumber: 原始项目次数
/// - overtimeProjectNumber: 加班项目次数
/// - sourceType: 来源类型(开卡/赠送/其他)
/// - totalPrice: 合计金额(品项价格 × 项目次数)
/// - isEffective: 是否有效
/// - lqXhJksyjList: 健康师业绩列表,每个业绩包含:
/// - id: 业绩编号
/// - glkdbh: 关联耗卡编号
/// - jks: 健康师ID
/// - jksxm: 健康师姓名
/// - jkszh: 健康师账号
/// - jksyj: 健康师业绩
/// - yjsj: 业绩时间
/// - jsjId: 金三角ID
/// - kdpxid: 耗卡品项ID
/// - laborCost: 手工费
/// - kdpxNumber: 耗卡品项次数
/// - originalKdpxNumber: 原始耗卡品项次数
/// - overtimeKdpxNumber: 加班耗卡品项次数
/// - originalLaborCost: 原始手工费
/// - overtimeLaborCost: 加班手工费
/// - isAccompanied: 是否陪同(0=否,1=是)
/// - accompaniedProjectNumber: 陪同项目数
/// - memberId: 会员ID
/// - memberName: 会员名称
/// - lqXhKjbsyjList: 科技部老师业绩列表,每个业绩包含:
/// - id: 业绩编号
/// - glkdbh: 关联耗卡编号
/// - kjbls: 科技部老师ID
/// - kjblsxm: 科技部老师姓名
/// - kjblszh: 科技部老师账号
/// - kjblsyj: 科技部老师业绩
/// - yjsj: 业绩时间
/// - hkpxid: 耗卡品项ID
/// - laborCost: 手工费
/// - hdpxNumber: 耗卡品项次数
/// - originalHdpxNumber: 原始耗卡品项次数
/// - overtimeHdpxNumber: 加班耗卡品项次数
/// - originalLaborCost: 原始手工费
/// - overtimeLaborCost: 加班手工费
/// - pagination: 分页信息
/// - total: 总记录数
/// - pageSize: 每页数量
/// - currentPage: 当前页码
/// - totalPages: 总页数
///
/// 查询参数
/// 分页的耗卡记录列表,包含耗卡基本信息、耗卡明细、健康师业绩、科技部老师业绩
/// 成功返回耗卡列表
/// 参数错误
/// 服务器内部错误
[HttpGet("")]
public async Task GetList([FromQuery] LqXhHyhkListQueryInput input)
{
var sidx = input.sidx == null ? "id" : input.sidx;
var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
List queryHksj = input.hksj != null ? input.hksj.Split(',').ToObeject>() : null;
DateTime? startHksj = queryHksj != null ? Ext.GetDateTime(queryHksj.First()) : null;
DateTime? endHksj = queryHksj != null ? Ext.GetDateTime(queryHksj.Last()) : null;
// 根据是否传入健康师ID或科技部老师ID来决定查询方式
ISugarQueryable query = null;
// 如果两个都传入了,需要同时JOIN两个表
if (!string.IsNullOrEmpty(input.jksId) && !string.IsNullOrEmpty(input.kjblsId))
{
query = _db.Queryable((jksyj, kjbsyj, hyhk) => jksyj.Glkdbh == hyhk.Id && kjbsyj.Glkdbh == hyhk.Id)
.Where((jksyj, kjbsyj, hyhk) => jksyj.Jkszh == input.jksId && jksyj.IsEffective == StatusEnum.有效.GetHashCode())
.Where((jksyj, kjbsyj, hyhk) => kjbsyj.Kjblszh == input.kjblsId && kjbsyj.IsEffective == StatusEnum.有效.GetHashCode())
.Select((jksyj, kjbsyj, hyhk) => hyhk)
.Distinct()
.MergeTable(); // 将多表结果集变成单表,后续可以使用it别名
}
// 如果只传入了健康师ID,需要通过JOIN健康师业绩表来过滤
else if (!string.IsNullOrEmpty(input.jksId))
{
query = _db.Queryable(
(jksyj, hyhk) => jksyj.Glkdbh == hyhk.Id)
.Where((jksyj, hyhk) => jksyj.Jkszh == input.jksId && jksyj.IsEffective == StatusEnum.有效.GetHashCode())
.Select((jksyj, hyhk) => hyhk)
.Distinct()
.MergeTable(); // 将多表结果集变成单表,后续可以使用it别名
}
// 如果只传入了科技部老师ID,需要通过JOIN科技部老师业绩表来过滤
else if (!string.IsNullOrEmpty(input.kjblsId))
{
query = _db.Queryable(
(kjbsyj, hyhk) => kjbsyj.Glkdbh == hyhk.Id)
.Where((kjbsyj, hyhk) => kjbsyj.Kjblszh == input.kjblsId && kjbsyj.IsEffective == StatusEnum.有效.GetHashCode())
.Select((kjbsyj, hyhk) => hyhk)
.Distinct()
.MergeTable(); // 将多表结果集变成单表,后续可以使用it别名
}
// 如果都没有传入,直接查询耗卡表
else
{
query = _db.Queryable();
}
var data = await query
.WhereIF(!string.IsNullOrEmpty(input.keyword), p => p.Hymc.Contains(input.keyword) || p.Hyzh.Contains(input.keyword) || p.MemberPhone.Contains(input.keyword))
.WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
.WhereIF(!string.IsNullOrEmpty(input.md), p => p.Md.Equals(input.md))
.WhereIF(!string.IsNullOrEmpty(input.mdbh), p => p.Mdbh.Contains(input.mdbh))
.WhereIF(!string.IsNullOrEmpty(input.mdmc), p => p.Mdmc.Contains(input.mdmc))
.WhereIF(!string.IsNullOrEmpty(input.hy), p => p.Hy.Equals(input.hy))
.WhereIF(!string.IsNullOrEmpty(input.hyzh), p => p.Hyzh.Contains(input.hyzh))
.WhereIF(!string.IsNullOrEmpty(input.hymc), p => p.Hymc.Contains(input.hymc))
.WhereIF(!string.IsNullOrEmpty(input.gklx), p => p.Gklx.Equals(input.gklx))
.WhereIF(!string.IsNullOrEmpty(input.sfykjb), p => p.Sfykjb.Equals(input.sfykjb))
.WhereIF(queryHksj != null, p => p.Hksj >= new DateTime(startHksj.ToDate().Year, startHksj.ToDate().Month, startHksj.ToDate().Day, 0, 0, 0))
.WhereIF(queryHksj != null, p => p.Hksj <= new DateTime(endHksj.ToDate().Year, endHksj.ToDate().Month, endHksj.ToDate().Day, 23, 59, 59))
.WhereIF(!string.IsNullOrEmpty(input.czry), p => p.Czry.Equals(input.czry))
.WhereIF(input.isEffective != 0, p => p.IsEffective == input.isEffective)
.Select(it => new LqXhHyhkListOutput
{
id = it.Id,
md = it.Md,
mdbh = it.Mdbh,
mdmc = it.Mdmc,
hy = it.Hy,
hyzh = it.Hyzh,
hymc = SqlFunc.Subqueryable().Where(w => w.Id == it.Hy).Select(w => w.Khmc),
gklx = it.Gklx,
xfje = SqlFunc.ToString(it.Xfje),
sgfy = SqlFunc.ToString(it.Sgfy),
sfykjb = it.Sfykjb,
hksj = it.Hksj,
czry = it.Czry,
memberPhone = SqlFunc.Subqueryable().Where(w => w.Id == it.Hy).Select(w => w.Sjh),
isEffective = it.IsEffective,
signatureFile = it.SignatureFile,
remark = it.Remark,
overtimeCoefficient = it.OvertimeCoefficient,
originalSgfy = it.OriginalSgfy,
overtimeSgfy = it.OvertimeSgfy,
appointmentId = it.AppointmentId,
})
.MergeTable()
.OrderBy($"{sidx} {sort}")
.ToPagedListAsync(input.currentPage, input.pageSize);
// 获取当前页的耗卡记录ID列表
var consumeIds = data.list.Select(x => x.id).ToList();
// 批量查询耗卡明细
var consumeDetails = new List();
if (consumeIds.Any())
{
consumeDetails = await _db.Queryable()
.Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
.Select(x => new LqXhPxmxInfoOutput
{
id = x.Id,
consumeInfoId = x.ConsumeInfoId,
billingItemId = x.BillingItemId,
px = x.Px,
pxmc = x.Pxmc,
pxjg = x.Pxjg,
memberId = x.MemberId,
createTime = x.CreateTIme,
projectNumber = x.ProjectNumber,
originalProjectNumber = x.OriginalProjectNumber,
overtimeProjectNumber = x.OvertimeProjectNumber,
sourceType = x.SourceType,
totalPrice = x.TotalPrice,
isEffective = x.IsEffective,
})
.ToListAsync();
}
// 按耗卡记录ID分组耗卡明细
var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
.ToDictionary(g => g.Key, g => g.ToList());
// 批量查询健康师业绩(性能优化:一次性查询所有耗卡的健康师业绩)
var jksyjList = new List();
if (consumeIds.Any())
{
// 先查询业绩数据
var jksyjEntities = await _db.Queryable()
.Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode())
.ToListAsync();
// 批量查询耗卡记录获取会员ID
var hyhkList = await _db.Queryable()
.Where(x => consumeIds.Contains(x.Id))
.Select(x => new { x.Id, x.Hy })
.ToListAsync();
var hyhkDict = hyhkList.ToDictionary(x => x.Id, x => x.Hy);
// 批量查询会员信息
var memberIds = hyhkDict.Values.Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
var memberList = new List<(string Id, string Khmc)>();
if (memberIds.Any())
{
var memberData = await _db.Queryable()
.Where(x => memberIds.Contains(x.Id))
.Select(x => new { x.Id, x.Khmc })
.ToListAsync();
memberList = memberData.Select(x => (x.Id, x.Khmc)).ToList();
}
var memberDict = memberList.ToDictionary(x => x.Id, x => x.Khmc);
// 转换为输出DTO
jksyjList = jksyjEntities.Select(x => new LqXhJksyjInfoOutput
{
id = x.Id,
glkdbh = x.Glkdbh,
jks = x.Jks,
jksxm = x.Jksxm,
jkszh = x.Jkszh,
jksyj = x.Jksyj?.ToString() ?? "0",
yjsj = x.Yjsj,
jsjId = x.JsjId,
kdpxid = x.Kdpxid,
laborCost = x.LaborCost,
kdpxNumber = x.KdpxNumber,
originalKdpxNumber = x.OriginalKdpxNumber,
overtimeKdpxNumber = x.OvertimeKdpxNumber,
originalLaborCost = x.OriginalLaborCost,
overtimeLaborCost = x.OvertimeLaborCost,
isAccompanied = x.IsAccompanied,
accompaniedProjectNumber = x.AccompaniedProjectNumber,
memberId = hyhkDict.ContainsKey(x.Glkdbh) ? hyhkDict[x.Glkdbh] : null,
memberName = hyhkDict.ContainsKey(x.Glkdbh) && memberDict.ContainsKey(hyhkDict[x.Glkdbh])
? memberDict[hyhkDict[x.Glkdbh]]
: null,
}).ToList();
}
// 批量查询科技部老师业绩(性能优化:一次性查询所有耗卡的科技部老师业绩)
var kjbsyjList = new List();
if (consumeIds.Any())
{
kjbsyjList = await _db.Queryable()
.Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode())
.Select(x => new LqXhKjbsyjInfoOutput
{
id = x.Id,
glkdbh = x.Glkdbh,
kjbls = x.Kjbls,
kjblsxm = x.Kjblsxm,
kjblszh = x.Kjblszh,
kjblsyj = SqlFunc.ToString(x.Kjblsyj),
yjsj = x.Yjsj,
hkpxid = x.Hkpxid,
laborCost = x.LaborCost,
hdpxNumber = x.HdpxNumber,
originalHdpxNumber = x.OriginalHdpxNumber,
overtimeHdpxNumber = x.OvertimeHdpxNumber,
originalLaborCost = x.OriginalLaborCost,
overtimeLaborCost = x.OvertimeLaborCost,
})
.ToListAsync();
}
// 按耗卡记录ID分组健康师业绩
var jksyjGrouped = jksyjList.GroupBy(x => x.glkdbh)
.ToDictionary(g => g.Key, g => g.ToList());
// 按耗卡记录ID分组科技部老师业绩
var kjbsyjGrouped = kjbsyjList.GroupBy(x => x.glkdbh)
.ToDictionary(g => g.Key, g => g.ToList());
// 为每个耗卡记录分配耗卡明细、健康师业绩和科技部老师业绩
foreach (var item in data.list)
{
item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
? consumeDetailsGrouped[item.id]
: new List();
item.lqXhJksyjList = jksyjGrouped.ContainsKey(item.id)
? jksyjGrouped[item.id]
: new List();
item.lqXhKjbsyjList = kjbsyjGrouped.ContainsKey(item.id)
? kjbsyjGrouped[item.id]
: new List();
}
return PageResult.SqlSugarPageResult(data);
}
///
/// 获取无耗卡日志的耗卡记录列表
///
///
/// 查询已发生耗卡但尚未填写耗卡日志(服务日志)的记录,支持按耗卡日期、会员姓名、电话等条件筛选
///
/// 示例请求:
/// GET /api/Extend/LqXhHyhk/Actions/GetNoFeedbackList?currentPage=1&pageSize=10&hksj=2025-01-01,2025-01-31&hymc=张三&memberPhone=138
///
/// 参数说明:
/// - hksj: 耗卡日期范围(格式:开始时间,结束时间,如:2025-01-01,2025-01-31)
/// - hymc: 会员姓名(模糊匹配)
/// - memberPhone: 会员电话(模糊匹配)
/// - keyword: 关键字(可选,搜索会员名称、账号、手机号)
/// - md: 门店ID(可选)
/// - currentPage: 当前页码
/// - pageSize: 每页数量
/// - sidx: 排序字段(默认hksj)
/// - sort: 排序方式(asc/desc,默认desc)
///
/// 返回说明:
/// - 返回格式与GetList接口相同,包含耗卡基本信息、耗卡明细、健康师业绩、科技部老师业绩
///
/// 查询参数
/// 无耗卡日志的耗卡记录列表(分页)
/// 查询成功
/// 参数错误
/// 服务器内部错误
[HttpGet("Actions/GetNoFeedbackList")]
public async Task GetNoFeedbackList([FromQuery] LqXhHyhkNoFeedbackListQueryInput input)
{
var sidx = input.sidx == null ? "hksj" : input.sidx;
var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
DateTime? startHksj = null;
DateTime? endHksj = null;
if (!string.IsNullOrEmpty(input.hksj))
{
var parts = input.hksj.Split(',');
if (parts.Length >= 1 && DateTime.TryParse(parts[0].Trim(), out var start))
startHksj = start;
if (parts.Length >= 2 && DateTime.TryParse(parts[1].Trim(), out var end))
endHksj = end;
}
var queryHksj = startHksj != null || endHksj != null;
// 耗卡表 LEFT JOIN 耗卡反馈表,筛选 feedback.Id 为 NULL 的记录(即无耗卡日志)
var query = _db.Queryable((hyhk, feedback) => new JoinQueryInfos(
JoinType.Left, hyhk.Id == feedback.ConsumeId))
.Where((hyhk, feedback) => feedback.Id == null)
.Where((hyhk, feedback) => hyhk.IsEffective == StatusEnum.有效.GetHashCode())
.WhereIF(!string.IsNullOrEmpty(input.keyword), (hyhk, feedback) => hyhk.Hymc.Contains(input.keyword) || hyhk.Hyzh.Contains(input.keyword) || hyhk.MemberPhone.Contains(input.keyword))
.WhereIF(!string.IsNullOrEmpty(input.md), (hyhk, feedback) => hyhk.Md == input.md)
.WhereIF(!string.IsNullOrEmpty(input.hymc), (hyhk, feedback) => hyhk.Hymc.Contains(input.hymc))
.WhereIF(!string.IsNullOrEmpty(input.memberPhone), (hyhk, feedback) => hyhk.MemberPhone.Contains(input.memberPhone))
.WhereIF(queryHksj && startHksj.HasValue, (hyhk, feedback) => hyhk.Hksj >= new DateTime(startHksj.Value.Year, startHksj.Value.Month, startHksj.Value.Day, 0, 0, 0))
.WhereIF(queryHksj && endHksj.HasValue, (hyhk, feedback) => hyhk.Hksj <= new DateTime(endHksj.Value.Year, endHksj.Value.Month, endHksj.Value.Day, 23, 59, 59))
.Select((hyhk, feedback) => new LqXhHyhkListOutput
{
id = hyhk.Id,
md = hyhk.Md,
mdbh = hyhk.Mdbh,
mdmc = hyhk.Mdmc,
hy = hyhk.Hy,
hyzh = hyhk.Hyzh,
hymc = SqlFunc.Subqueryable().Where(w => w.Id == hyhk.Hy).Select(w => w.Khmc),
gklx = hyhk.Gklx,
xfje = SqlFunc.ToString(hyhk.Xfje),
sgfy = SqlFunc.ToString(hyhk.Sgfy),
sfykjb = hyhk.Sfykjb,
hksj = hyhk.Hksj,
czry = hyhk.Czry,
memberPhone = SqlFunc.Subqueryable().Where(w => w.Id == hyhk.Hy).Select(w => w.Sjh),
isEffective = hyhk.IsEffective,
signatureFile = hyhk.SignatureFile,
remark = hyhk.Remark,
overtimeCoefficient = hyhk.OvertimeCoefficient,
originalSgfy = hyhk.OriginalSgfy,
overtimeSgfy = hyhk.OvertimeSgfy,
appointmentId = hyhk.AppointmentId,
})
.MergeTable()
.OrderBy($"{sidx} {sort}")
.ToPagedListAsync(input.currentPage, input.pageSize);
var data = await query;
// 获取当前页的耗卡记录ID列表
var consumeIds = data.list.Select(x => x.id).ToList();
// 批量查询耗卡明细、健康师业绩、科技部老师业绩、人次记录
var consumeDetails = new List();
var jksyjList = new List();
var kjbsyjList = new List();
var personTimesRecordList = new List();
if (consumeIds.Any())
{
consumeDetails = await _db.Queryable()
.Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
.Select(x => new LqXhPxmxInfoOutput
{
id = x.Id,
consumeInfoId = x.ConsumeInfoId,
billingItemId = x.BillingItemId,
px = x.Px,
pxmc = x.Pxmc,
pxjg = x.Pxjg,
memberId = x.MemberId,
createTime = x.CreateTIme,
projectNumber = x.ProjectNumber,
originalProjectNumber = x.OriginalProjectNumber,
overtimeProjectNumber = x.OvertimeProjectNumber,
sourceType = x.SourceType,
totalPrice = x.TotalPrice,
isEffective = x.IsEffective,
})
.ToListAsync();
jksyjList = await _db.Queryable()
.Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode())
.Select(x => new LqXhJksyjInfoOutput
{
id = x.Id,
glkdbh = x.Glkdbh,
jks = x.Jks,
jkszh = x.Jkszh,
jksxm = x.Jksxm,
jksyj = SqlFunc.ToString(x.Jksyj),
yjsj = x.Yjsj,
jsjId = x.JsjId,
kdpxid = x.Kdpxid,
laborCost = x.LaborCost,
kdpxNumber = x.KdpxNumber,
originalKdpxNumber = x.OriginalKdpxNumber,
overtimeKdpxNumber = x.OvertimeKdpxNumber,
originalLaborCost = x.OriginalLaborCost,
overtimeLaborCost = x.OvertimeLaborCost,
isAccompanied = x.IsAccompanied,
accompaniedProjectNumber = x.AccompaniedProjectNumber,
})
.ToListAsync();
kjbsyjList = await _db.Queryable()
.Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode())
.Select(x => new LqXhKjbsyjInfoOutput
{
id = x.Id,
glkdbh = x.Glkdbh,
kjbls = x.Kjbls,
kjblszh = x.Kjblszh,
kjblsxm = x.Kjblsxm,
kjblsyj = SqlFunc.ToString(x.Kjblsyj),
yjsj = x.Yjsj,
hkpxid = x.Hkpxid,
laborCost = x.LaborCost,
hdpxNumber = x.HdpxNumber,
originalHdpxNumber = x.OriginalHdpxNumber,
overtimeHdpxNumber = x.OvertimeHdpxNumber,
originalLaborCost = x.OriginalLaborCost,
overtimeLaborCost = x.OvertimeLaborCost,
})
.ToListAsync();
personTimesRecordList = await _db.Queryable()
.Where(x => consumeIds.Contains(x.BusinessId) && x.IsEffective == StatusEnum.有效.GetHashCode())
.Select(x => new LqPersonTimesRecordListOutput
{
id = x.Id,
businessId = x.BusinessId,
businessType = x.BusinessType,
hasBilling = x.HasBilling,
personType = x.PersonType,
personId = x.PersonId,
personName = x.PersonName,
memberId = x.MemberId,
memberName = x.MemberName,
workDate = x.WorkDate,
workMonth = x.WorkMonth,
quantity = x.Quantity,
createTime = x.CreateTime,
isEffective = x.IsEffective,
})
.ToListAsync();
}
// 按耗卡记录ID分组
var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
.ToDictionary(g => g.Key, g => g.ToList());
var jksyjGrouped = jksyjList.GroupBy(x => x.glkdbh)
.ToDictionary(g => g.Key, g => g.ToList());
var kjbsyjGrouped = kjbsyjList.GroupBy(x => x.glkdbh)
.ToDictionary(g => g.Key, g => g.ToList());
var personTimesRecordGrouped = personTimesRecordList.GroupBy(x => x.businessId)
.ToDictionary(g => g.Key, g => g.ToList());
foreach (var item in data.list)
{
item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
? consumeDetailsGrouped[item.id]
: new List();
item.lqXhJksyjList = jksyjGrouped.ContainsKey(item.id)
? jksyjGrouped[item.id]
: new List();
item.lqXhKjbsyjList = kjbsyjGrouped.ContainsKey(item.id)
? kjbsyjGrouped[item.id]
: new List();
item.personTimesRecordList = personTimesRecordGrouped.ContainsKey(item.id)
? personTimesRecordGrouped[item.id]
: new List();
}
return PageResult.SqlSugarPageResult(data);
}
#endregion
#region 根据健康师ID获取耗卡列表
///
/// 根据健康师ID获取耗卡列表
///
///
/// 根据健康师ID查询该健康师参与的所有耗卡记录,支持时间周期查询和分页
///
/// 示例请求:
/// GET /api/Extend/LqXhHyhk/GetListByJksId?jksId=健康师ID&startTime=2025-01-01&endTime=2025-01-31¤tPage=1&pageSize=10
///
/// 参数说明:
/// - jksId: 健康师ID(必填)
/// - startTime: 开始时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
/// - endTime: 结束时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
/// - md: 门店ID(可选)
/// - isEffective: 是否有效(可选,0=全部,1=有效,2=无效,默认1)
/// - currentPage: 当前页码(必填)
/// - pageSize: 每页数量(必填)
///
/// 返回说明:
/// - 返回格式与GetList接口相同,包含耗卡基本信息及耗卡明细列表
///
/// 查询参数
/// 耗卡列表(分页)
/// 查询成功
/// 参数错误
/// 服务器内部错误
[HttpGet("GetListByJksId")]
public async Task GetListByJksId([FromQuery] LqXhHyhkListByJksQueryInput input)
{
try
{
if (string.IsNullOrEmpty(input.jksId))
{
throw NCCException.Oh("健康师ID不能为空");
}
var sidx = input.sidx == null ? "hksj" : input.sidx;
var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
// 解析时间范围
DateTime? startDate = null;
DateTime? endDate = null;
if (!string.IsNullOrEmpty(input.startTime))
{
startDate = Ext.GetDateTime(input.startTime);
}
if (!string.IsNullOrEmpty(input.endTime))
{
endDate = Ext.GetDateTime(input.endTime);
// 如果只传了日期,则设置为当天的23:59:59
if (endDate.HasValue && !input.endTime.Contains(":"))
{
endDate = new DateTime(endDate.Value.Year, endDate.Value.Month, endDate.Value.Day, 23, 59, 59);
}
}
// 通过健康师业绩表关联查询耗卡记录
var data = await _db.Queryable((jksyj, hyhk) => jksyj.Glkdbh == hyhk.Id)
.Where((jksyj, hyhk) => jksyj.Jkszh == input.jksId)
.WhereIF(input.isEffective != 0, (jksyj, hyhk) => jksyj.IsEffective == input.isEffective && hyhk.IsEffective == input.isEffective)
.WhereIF(input.isEffective == 0, (jksyj, hyhk) => jksyj.IsEffective == StatusEnum.有效.GetHashCode() && hyhk.IsEffective == StatusEnum.有效.GetHashCode())
.WhereIF(startDate.HasValue, (jksyj, hyhk) => hyhk.Hksj >= startDate.Value)
.WhereIF(endDate.HasValue, (jksyj, hyhk) => hyhk.Hksj <= endDate.Value)
.WhereIF(!string.IsNullOrEmpty(input.md), (jksyj, hyhk) => hyhk.Md == input.md)
.Select((jksyj, hyhk) => new LqXhHyhkListOutput
{
id = hyhk.Id,
md = hyhk.Md,
mdbh = hyhk.Mdbh,
mdmc = hyhk.Mdmc,
hy = hyhk.Hy,
hyzh = hyhk.Hyzh,
hymc = SqlFunc.Subqueryable().Where(w => w.Id == hyhk.Hy).Select(w => w.Khmc),
gklx = hyhk.Gklx,
xfje = SqlFunc.ToString(hyhk.Xfje),
sgfy = SqlFunc.ToString(hyhk.Sgfy),
sfykjb = hyhk.Sfykjb,
hksj = hyhk.Hksj,
czry = hyhk.Czry,
memberPhone = SqlFunc.Subqueryable().Where(w => w.Id == hyhk.Hy).Select(w => w.Sjh),
isEffective = hyhk.IsEffective,
signatureFile = hyhk.SignatureFile,
remark = hyhk.Remark,
overtimeCoefficient = hyhk.OvertimeCoefficient,
originalSgfy = hyhk.OriginalSgfy,
overtimeSgfy = hyhk.OvertimeSgfy,
})
.MergeTable()
.Distinct() // 去重,因为一个耗卡可能对应多个健康师业绩记录
.OrderBy($"{sidx} {sort}")
.ToPagedListAsync(input.currentPage, input.pageSize);
// 获取当前页的耗卡记录ID列表
var consumeIds = data.list.Select(x => x.id).ToList();
// 获取人次记录列表
var personTimesRecordList = await _db.Queryable().Where(x => consumeIds.Contains(x.BusinessId) && x.IsEffective == StatusEnum.有效.GetHashCode())
.Select(x => new LqPersonTimesRecordListOutput
{
id = x.Id,
businessId = x.BusinessId,
businessType = x.BusinessType,
personType = x.PersonType,
personId = x.PersonId,
personName = x.PersonName,
memberId = x.MemberId,
memberName = x.MemberName,
workDate = x.WorkDate,
workMonth = x.WorkMonth,
quantity = x.Quantity,
createTime = x.CreateTime,
isEffective = x.IsEffective,
})
.ToListAsync();
// 按耗卡记录ID分组人次记录
var personTimesRecordGrouped = personTimesRecordList.GroupBy(x => x.businessId).ToDictionary(g => g.Key, g => g.ToList());
// 为每个耗卡记录分配人次记录
foreach (var item in data.list)
{
item.personTimesRecordList = personTimesRecordGrouped.ContainsKey(item.id) ? personTimesRecordGrouped[item.id] : new List();
}
// 批量查询耗卡明细
var consumeDetails = new List();
if (consumeIds.Any())
{
consumeDetails = await _db.Queryable().Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
.Select(x => new LqXhPxmxInfoOutput
{
id = x.Id,
consumeInfoId = x.ConsumeInfoId,
billingItemId = x.BillingItemId,
px = x.Px,
pxmc = x.Pxmc,
pxjg = x.Pxjg,
memberId = x.MemberId,
createTime = x.CreateTIme,
projectNumber = x.ProjectNumber,
originalProjectNumber = x.OriginalProjectNumber,
overtimeProjectNumber = x.OvertimeProjectNumber,
sourceType = x.SourceType,
totalPrice = x.TotalPrice,
isEffective = x.IsEffective,
})
.ToListAsync();
}
// 按耗卡记录ID分组耗卡明细
var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId).ToDictionary(g => g.Key, g => g.ToList());
// 为每个耗卡记录分配耗卡明细
foreach (var item in data.list)
{
item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id) ? consumeDetailsGrouped[item.id] : new List();
}
return PageResult.SqlSugarPageResult(data);
}
catch (Exception ex)
{
_logger.LogError(ex, $"根据健康师ID获取耗卡列表失败 - 健康师ID: {input?.jksId}, 开始时间: {input?.startTime}, 结束时间: {input?.endTime}");
throw NCCException.Oh($"根据健康师ID获取耗卡列表失败: {ex.Message}");
}
}
#endregion
#region 根据科技部老师ID获取耗卡列表
///
/// 根据科技部老师ID获取耗卡列表
///
///
/// 根据科技部老师ID查询该老师参与的所有耗卡记录,支持时间周期查询和分页
///
/// 示例请求:
/// GET /api/Extend/LqXhHyhk/GetListByKjbId?kjblsId=科技部老师ID&startTime=2025-01-01&endTime=2025-01-31¤tPage=1&pageSize=10
///
/// 参数说明:
/// - kjblsId: 科技部老师ID(必填)
/// - startTime: 开始时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
/// - endTime: 结束时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
/// - md: 门店ID(可选)
/// - isEffective: 是否有效(可选,0=全部,1=有效,2=无效,默认1)
/// - currentPage: 当前页码(必填)
/// - pageSize: 每页数量(必填)
///
/// 返回说明:
/// - 返回格式与GetList接口相同,包含耗卡基本信息及耗卡明细列表
///
/// 查询参数
/// 耗卡列表(分页)
/// 查询成功
/// 参数错误
/// 服务器内部错误
[HttpGet("GetListByKjbId")]
public async Task GetListByKjbId([FromQuery] LqXhHyhkListByKjbQueryInput input)
{
try
{
if (string.IsNullOrEmpty(input.kjblsId))
{
throw NCCException.Oh("科技部老师ID不能为空");
}
var sidx = input.sidx == null ? "hksj" : input.sidx;
var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
// 解析时间范围
DateTime? startDate = null;
DateTime? endDate = null;
if (!string.IsNullOrEmpty(input.startTime))
{
startDate = Ext.GetDateTime(input.startTime);
}
if (!string.IsNullOrEmpty(input.endTime))
{
endDate = Ext.GetDateTime(input.endTime);
// 如果只传了日期,则设置为当天的23:59:59
if (endDate.HasValue && !input.endTime.Contains(":"))
{
endDate = new DateTime(endDate.Value.Year, endDate.Value.Month, endDate.Value.Day, 23, 59, 59);
}
}
// 通过科技部老师业绩表关联查询耗卡记录
var data = await _db.Queryable(
(kjbsyj, hyhk) => kjbsyj.Glkdbh == hyhk.Id)
.Where((kjbsyj, hyhk) => kjbsyj.Kjblszh == input.kjblsId)
.WhereIF(input.isEffective != 0, (kjbsyj, hyhk) => kjbsyj.IsEffective == input.isEffective && hyhk.IsEffective == input.isEffective)
.WhereIF(input.isEffective == 0, (kjbsyj, hyhk) => kjbsyj.IsEffective == StatusEnum.有效.GetHashCode() && hyhk.IsEffective == StatusEnum.有效.GetHashCode())
.WhereIF(startDate.HasValue, (kjbsyj, hyhk) => hyhk.Hksj >= startDate.Value)
.WhereIF(endDate.HasValue, (kjbsyj, hyhk) => hyhk.Hksj <= endDate.Value)
.WhereIF(!string.IsNullOrEmpty(input.md), (kjbsyj, hyhk) => hyhk.Md == input.md)
.Select((kjbsyj, hyhk) => new LqXhHyhkListOutput
{
id = hyhk.Id,
md = hyhk.Md,
mdbh = hyhk.Mdbh,
mdmc = hyhk.Mdmc,
hy = hyhk.Hy,
hyzh = hyhk.Hyzh,
hymc = SqlFunc.Subqueryable().Where(w => w.Id == hyhk.Hy).Select(w => w.Khmc),
gklx = hyhk.Gklx,
xfje = SqlFunc.ToString(hyhk.Xfje),
sgfy = SqlFunc.ToString(hyhk.Sgfy),
sfykjb = hyhk.Sfykjb,
hksj = hyhk.Hksj,
czry = hyhk.Czry,
memberPhone = SqlFunc.Subqueryable().Where(w => w.Id == hyhk.Hy).Select(w => w.Sjh),
isEffective = hyhk.IsEffective,
signatureFile = hyhk.SignatureFile,
remark = hyhk.Remark,
overtimeCoefficient = hyhk.OvertimeCoefficient,
originalSgfy = hyhk.OriginalSgfy,
overtimeSgfy = hyhk.OvertimeSgfy,
})
.MergeTable()
.Distinct() // 去重,因为一个耗卡可能对应多个科技部老师业绩记录
.OrderBy($"{sidx} {sort}")
.ToPagedListAsync(input.currentPage, input.pageSize);
// 获取当前页的耗卡记录ID列表
var consumeIds = data.list.Select(x => x.id).ToList();
// 批量查询耗卡明细
var consumeDetails = new List();
if (consumeIds.Any())
{
consumeDetails = await _db.Queryable()
.Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
.Select(x => new LqXhPxmxInfoOutput
{
id = x.Id,
consumeInfoId = x.ConsumeInfoId,
billingItemId = x.BillingItemId,
px = x.Px,
pxmc = x.Pxmc,
pxjg = x.Pxjg,
memberId = x.MemberId,
createTime = x.CreateTIme,
projectNumber = x.ProjectNumber,
originalProjectNumber = x.OriginalProjectNumber,
overtimeProjectNumber = x.OvertimeProjectNumber,
sourceType = x.SourceType,
totalPrice = x.TotalPrice,
isEffective = x.IsEffective,
})
.ToListAsync();
}
// 按耗卡记录ID分组耗卡明细
var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
.ToDictionary(g => g.Key, g => g.ToList());
// 为每个耗卡记录分配耗卡明细
foreach (var item in data.list)
{
item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
? consumeDetailsGrouped[item.id]
: new List();
}
return PageResult.SqlSugarPageResult(data);
}
catch (Exception ex)
{
_logger.LogError(ex, $"根据科技部老师ID获取耗卡列表失败 - 科技部老师ID: {input?.kjblsId}, 开始时间: {input?.startTime}, 结束时间: {input?.endTime}");
throw NCCException.Oh($"根据科技部老师ID获取耗卡列表失败: {ex.Message}");
}
}
#endregion
#region 新建会员耗卡
///
/// 新建会员耗卡
///
///
/// 创建会员耗卡记录及其关联的品项明细、健康师业绩、科技部老师业绩信息
///
/// 示例请求:
/// ```json
/// {
/// "hyid": "会员ID",
/// "hkmd": "耗卡门店",
/// "hksj": "2025-01-11",
/// "lqXhPxmxList": [
/// {
/// "px": "品项编号",
/// "pxmc": "品项名称",
/// "pxjg": 100.00,
/// "projectNumber": 1,
/// "xfzs": "否",
/// "lqXhJksyjList": [
/// {
/// "jks": "健康师",
/// "jksxm": "健康师姓名",
/// "jksyj": "100"
/// }
/// ]
/// }
/// ]
/// }
/// ```
///
/// 参数说明:
/// - hyid: 会员ID
/// - hkmd: 耗卡门店
/// - lqXhPxmxList: 耗卡品项明细列表
///
/// 会员耗卡创建参数
/// 无返回值
/// 创建成功
/// 参数错误或数据验证失败
/// 服务器内部错误
[HttpPost("")]
public async Task Create([FromBody] LqXhHyhkCrInput input)
{
var userInfo = await _userManager.GetUserInfo();
//首先判断当前时间是否是加班时间
var entity = input.Adapt();
entity.Id = YitIdHelper.NextId().ToString();
entity.Czry = _userManager.UserId;
entity.MemberPhone = _db.Queryable().Where(w => w.Id == entity.Hy).First().Sjh;
entity.CreateTime = DateTime.Now;
entity.IsEffective = StatusEnum.有效.GetHashCode();
entity.UpdateTime = DateTime.Now;
entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0;
entity.OriginalSgfy = input.sgfy;
entity.AppointmentId = input.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;
try
{
// 开启事务
_db.BeginTran();
// 查询会员信息
var memberInfo = await _db.Queryable().Where(w => w.Id == entity.Hy).FirstAsync();
//如果会员类型是线索,那么就更新为新客
if (memberInfo.Khlx == MemberTypeEnum.线索.GetHashCode().ToString())
{
memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString();
}
// 只要有耗卡记录且消费金额大于0,就更新最后消费时间(用于沉睡天数计算)
// 使用耗卡时间而不是当前时间,确保准确性
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 = input.hksj,
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 = input.hksj,
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 = input.hksj,
CreateTime = DateTime.Now,
Hkpxid = lqXhPxmxEntity.Id,
// OriginalHdpxNumber = ikjbs_tem.hdpxNumber,
// OvertimeHdpxNumber = (decimal)(entity.OvertimeCoefficient * (ikjbs_tem.hdpxNumber ?? 0)),
// HdpxNumber = (decimal)((ikjbs_tem.hdpxNumber ?? 0) + (entity.OvertimeCoefficient * (ikjbs_tem.hdpxNumber ?? 0))),
OriginalHdpxNumber = ikjbs_tem.hdpxNumber,
OvertimeHdpxNumber = 0,
HdpxNumber = ikjbs_tem.hdpxNumber,
// OriginalLaborCost = ikjbs_tem.laborCost,
// OvertimeLaborCost = (decimal)(entity.OvertimeCoefficient * (ikjbs_tem.laborCost ?? 0)),
// LaborCost = (decimal)((ikjbs_tem.laborCost ?? 0) + (entity.OvertimeCoefficient * (ikjbs_tem.laborCost ?? 0))),
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();
// 剔除所有T区健康师(姓名中包含"T区"的健康师)
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())
{
LqPersonTimesRecordEntity personTimesRecordEntity = 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 = input.hksj,
WorkMonth = input.hksj.ToDate().ToString("yyyyMM"),
Quantity = 1 / (decimal)jksCount,
CreateTime = DateTime.Now,
IsEffective = StatusEnum.有效.GetHashCode(),
HasBilling = billingRecord ? 1 : 0,
};
allPersonTimesRecordEntities.Add(personTimesRecordEntity);
}
//剔除所有T区科技部老师(姓名中包含"T区"的科技部老师)
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())
{
LqPersonTimesRecordEntity personTimesRecordEntity = 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 = input.hksj,
WorkMonth = input.hksj.ToDate().ToString("yyyyMM"),
Quantity = 1 / (decimal)kjbCount,
CreateTime = DateTime.Now,
IsEffective = StatusEnum.有效.GetHashCode(),
HasBilling = billingRecord ? 1 : 0,
};
allPersonTimesRecordEntities.Add(personTimesRecordEntity);
}
// 批量插入人次记录
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();
}
// 提交事务
_db.CommitTran();
}
catch (Exception ex)
{
// 回滚事务
_db.RollbackTran();
Console.WriteLine($"新建会员耗卡失败: {ex.Message}");
Console.WriteLine($"堆栈跟踪: {ex.StackTrace}");
throw NCCException.Oh(ErrorCode.COM1000, $"新建会员耗卡失败: {ex.Message}");
}
}
#endregion
#region 获取会员耗卡无分页列表
///
/// 获取会员耗卡无分页列表
///
/// 请求参数
///
[NonAction]
public async Task GetNoPagingList([FromQuery] LqXhHyhkListQueryInput input)
{
var sidx = input.sidx == null ? "id" : input.sidx;
List queryHksj = input.hksj != null ? input.hksj.Split(',').ToObeject>() : null;
DateTime? startHksj = queryHksj != null ? Ext.GetDateTime(queryHksj.First()) : null;
DateTime? endHksj = queryHksj != null ? Ext.GetDateTime(queryHksj.Last()) : null;
var data = await _db.Queryable()
.WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
.WhereIF(!string.IsNullOrEmpty(input.md), p => p.Md.Equals(input.md))
.WhereIF(!string.IsNullOrEmpty(input.mdbh), p => p.Mdbh.Contains(input.mdbh))
.WhereIF(!string.IsNullOrEmpty(input.mdmc), p => p.Mdmc.Contains(input.mdmc))
.WhereIF(!string.IsNullOrEmpty(input.hy), p => p.Hy.Equals(input.hy))
.WhereIF(!string.IsNullOrEmpty(input.hyzh), p => p.Hyzh.Contains(input.hyzh))
.WhereIF(!string.IsNullOrEmpty(input.hymc), p => p.Hymc.Contains(input.hymc))
.WhereIF(!string.IsNullOrEmpty(input.gklx), p => p.Gklx.Equals(input.gklx))
.WhereIF(!string.IsNullOrEmpty(input.sfykjb), p => p.Sfykjb.Equals(input.sfykjb))
.WhereIF(queryHksj != null, p => p.Hksj >= new DateTime(startHksj.ToDate().Year, startHksj.ToDate().Month, startHksj.ToDate().Day, 0, 0, 0))
.WhereIF(queryHksj != null, p => p.Hksj <= new DateTime(endHksj.ToDate().Year, endHksj.ToDate().Month, endHksj.ToDate().Day, 23, 59, 59))
.WhereIF(!string.IsNullOrEmpty(input.czry), p => p.Czry.Equals(input.czry))
.Select(it => new LqXhHyhkListOutput
{
id = it.Id,
md = it.Md,
mdbh = it.Mdbh,
mdmc = it.Mdmc,
hy = it.Hy,
hyzh = it.Hyzh,
hymc = SqlFunc.Subqueryable().Where(w => w.Id == it.Hy).Select(w => w.Khmc),
memberPhone = SqlFunc.Subqueryable().Where(w => w.Id == it.Hy).Select(w => w.Sjh),
gklx = it.Gklx,
xfje = SqlFunc.ToString(it.Xfje),
sgfy = SqlFunc.ToString(it.Sgfy),
sfykjb = it.Sfykjb,
hksj = it.Hksj,
czry = it.Czry,
remark = it.Remark,
})
.MergeTable()
.OrderBy(sidx + " " + input.sort)
.ToListAsync();
return data;
}
#endregion
#region 导出会员耗卡
///
/// 导出会员耗卡
///
/// 请求参数
///
[HttpGet("Actions/Export")]
public async Task Export([FromQuery] LqXhHyhkListQueryInput input)
{
var userInfo = await _userManager.GetUserInfo();
var exportData = new List();
if (input.dataType == 0)
{
var data = Clay.Object(await this.GetList(input));
exportData = data.Solidify>().list;
}
else
{
exportData = await this.GetNoPagingList(input);
}
List paramList =
"[{\"value\":\"耗卡编号\",\"field\":\"id\"},{\"value\":\"门店\",\"field\":\"md\"},{\"value\":\"门店编号\",\"field\":\"mdbh\"},{\"value\":\"门店名称\",\"field\":\"mdmc\"},{\"value\":\"会员\",\"field\":\"hy\"},{\"value\":\"会员账号\",\"field\":\"hyzh\"},{\"value\":\"会员名称\",\"field\":\"hymc\"},{\"value\":\"顾客类型\",\"field\":\"gklx\"},{\"value\":\"消费金额\",\"field\":\"xfje\"},{\"value\":\"手工费用\",\"field\":\"sgfy\"},{\"value\":\"是否有科技部\",\"field\":\"sfykjb\"},{\"value\":\"耗卡时间\",\"field\":\"hksj\"},{\"value\":\"操作人员\",\"field\":\"czry\"},]".ToList();
ExcelConfig excelconfig = new ExcelConfig();
excelconfig.FileName = "会员耗卡.xls";
excelconfig.HeadFont = "微软雅黑";
excelconfig.HeadPoint = 10;
excelconfig.IsAllSizeColumn = true;
excelconfig.ColumnModel = new List();
List selectKeyList = input.selectKey.Split(',').ToList();
foreach (var item in selectKeyList)
{
var isExist = paramList.Find(p => p.field == item);
if (isExist != null)
{
excelconfig.ColumnModel.Add(new ExcelColumnModel() { Column = isExist.field, ExcelColumn = isExist.value });
}
}
var addPath = FileVariable.TemporaryFilePath + excelconfig.FileName;
ExcelExportHelper.Export(exportData, excelconfig, addPath);
var fileName = _userManager.UserId + "|" + addPath + "|xls";
var output = new { name = excelconfig.FileName, url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC") };
return output;
}
#endregion
#region 批量删除会员耗卡
///
/// 批量删除会员耗卡
///
/// 主键数组
///
[HttpPost("batchRemove")]
public async Task BatchRemove([FromBody] List ids)
{
var entitys = await _db.Queryable().In(it => it.Id, ids).ToListAsync();
if (entitys.Count > 0)
{
try
{
//开启事务
_db.BeginTran();
//批量删除会员耗卡
await _db.Deleteable().In(d => d.Id, ids).ExecuteCommandAsync();
//清空子表数据
await _db.Deleteable().In(u => u.Glkdbh, ids).ExecuteCommandAsync();
//清空子表数据
await _db.Deleteable().In(u => u.Glkdbh, ids).ExecuteCommandAsync();
//清空子表数据
await _db.Deleteable().In(u => u.ConsumeInfoId, ids).ExecuteCommandAsync();
//关闭事务
_db.CommitTran();
}
catch (Exception)
{
//回滚事务
_db.RollbackTran();
throw NCCException.Oh(ErrorCode.COM1002);
}
}
}
#endregion
#region 更新会员耗卡
///
/// 更新会员耗卡
///
/// 主键
/// 参数
///
[HttpPut("{id}")]
public async Task Update(string id, [FromBody] LqXhHyhkUpInput input)
{
var entity = input.Adapt();
try
{
//开启事务
_db.BeginTran();
//查询记录
var LqXhHyhkInfo = await _db.Queryable().Where(p => p.Id == id && p.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync();
if (LqXhHyhkInfo == null)
{
throw NCCException.Oh(ErrorCode.COM1005, "耗卡记录不存在或已作废");
}
entity.UpdateTime = DateTime.Now;
entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0;
entity.OriginalSgfy = input.sgfy;
// 加班手工费 = 健康师原始手工费之和 × 加班系数(科技部不参与加班)
var jksOriginalLaborCostSum = input.lqXhPxmxList?
.SelectMany(p => p.lqXhJksyjList ?? Enumerable.Empty())
.Sum(j => j.laborCost ?? 0) ?? 0;
entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * (entity.OvertimeCoefficient ?? 0));
entity.Sgfy = entity.OriginalSgfy + entity.OvertimeSgfy;
entity.AppointmentId = input.appointmentId;
//更新会员耗卡记录
await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
var memberInfo = await _db.Queryable().Where(w => w.Id == entity.Hy).FirstAsync();
//如果会员类型是线索,那么就更新为新客
if (memberInfo.Khlx == MemberTypeEnum.线索.GetHashCode().ToString())
{
memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString();
}
// 如果消费金额大于0,更新最后消费时间(用于沉睡天数计算)
if (entity.Xfje > 0)
{
memberInfo.LastConsumeTime = entity.Hksj;
}
//保存会员信息
await _db.Updateable(memberInfo).ExecuteCommandAsync();
//清空原有数据
await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync();
await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync();
await _db.Deleteable().Where(u => u.ConsumeInfoId == id).ExecuteCommandAsync();
await _db.Deleteable().Where(u => u.BusinessId == id).ExecuteCommandAsync();
// 收集所有需要插入的实体,然后批量插入
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 = input.id,
BillingItemId = item.billingItemId,
CreateTIme = DateTime.Now,
Yjsj = input.hksj,
MemberId = entity.Hy,
TotalPrice = item.pxjg * (item.projectNumber ?? 1),
Px = item.px,
Pxmc = item.pxmc,
Pxjg = item.pxjg,
SourceType = item.sourceType,
IsEffective = StatusEnum.有效.GetHashCode(),
OriginalProjectNumber = item.projectNumber ?? 0,
OvertimeProjectNumber = (decimal)(entity.OvertimeCoefficient * (item.projectNumber ?? 0)),
ProjectNumber = (decimal)((item.projectNumber ?? 0) + (entity.OvertimeCoefficient * (item.projectNumber ?? 0))),
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 = input.id,
Jks = ijks_tem.jks,
Jksxm = ijks_tem.jksxm,
Jkszh = ijks_tem.jkszh,
Jksyj = ijks_tem.jksyj,
Yjsj = input.hksj,
CreateTime = DateTime.Now,
JsjId = ijks_tem.jsjId,
Kdpxid = lqXhPxmxEntity.Id,
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),
OvertimeLaborCost = (decimal)(entity.OvertimeCoefficient * (ijks_tem.laborCost ?? 0)),
LaborCost = ijks_tem.laborCost + (entity.OvertimeCoefficient * (ijks_tem.laborCost ?? 0)),
OriginalLaborCost = ijks_tem.laborCost,
IsEffective = StatusEnum.有效.GetHashCode(),
IsAccompanied = ijks_tem.isAccompanied,
AccompaniedProjectNumber = ijks_tem.accompaniedProjectNumber,
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 = input.id,
Kjbls = ikjbs_tem.kjbls,
Kjblsxm = ikjbs_tem.kjblsxm,
Kjblszh = ikjbs_tem.kjblszh,
Kjblsyj = ikjbs_tem.kjblsyj,
Yjsj = input.hksj,
CreateTime = DateTime.Now,
Hkpxid = lqXhPxmxEntity.Id,
// OriginalHdpxNumber = ikjbs_tem.hdpxNumber,
// OvertimeHdpxNumber = (decimal)(entity.OvertimeCoefficient * (ikjbs_tem.hdpxNumber ?? 0)),
// HdpxNumber = (decimal)((ikjbs_tem.hdpxNumber ?? 0) + (entity.OvertimeCoefficient * (ikjbs_tem.hdpxNumber ?? 0))),
// OriginalLaborCost = ikjbs_tem.laborCost,
// OvertimeLaborCost = (decimal)(entity.OvertimeCoefficient * (ikjbs_tem.laborCost ?? 0)),
// LaborCost = (decimal)((ikjbs_tem.laborCost ?? 0) + (entity.OvertimeCoefficient * (ikjbs_tem.laborCost ?? 0))),
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();
//剔除所有T区健康师(姓名中包含"T区"的健康师)
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())
{
LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity
{
Id = YitIdHelper.NextId().ToString(),
BusinessId = input.id,
BusinessType = "耗卡",
PersonType = "健康师",
PersonId = item,
PersonName = NoTallJksyjEntities.Where(x => x.Jks == item).Select(x => x.Jksxm).First(),
MemberId = entity.Hy,
MemberName = memberInfo.Khmc,
WorkDate = input.hksj,
WorkMonth = input.hksj.ToDate().ToString("yyyyMM"),
Quantity = 1 / (decimal)jksCount,
CreateTime = DateTime.Now,
IsEffective = StatusEnum.有效.GetHashCode(),
HasBilling = billingRecord ? 1 : 0,
};
allPersonTimesRecordEntities.Add(personTimesRecordEntity);
}
//剔除所有T区科技部老师(姓名中包含"T区"的科技部老师)
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())
{
LqPersonTimesRecordEntity personTimesRecordEntity = new LqPersonTimesRecordEntity
{
Id = YitIdHelper.NextId().ToString(),
BusinessId = input.id,
BusinessType = "耗卡",
PersonType = "科技部老师",
PersonId = item,
PersonName = NoTallKjbsyjEntities.Where(x => x.Kjbls == item).Select(x => x.Kjblsxm).First(),
MemberId = entity.Hy,
MemberName = memberInfo.Khmc,
WorkDate = input.hksj,
WorkMonth = input.hksj.ToDate().ToString("yyyyMM"),
Quantity = 1 / (decimal)kjbCount,
CreateTime = DateTime.Now,
IsEffective = StatusEnum.有效.GetHashCode(),
HasBilling = billingRecord ? 1 : 0,
};
allPersonTimesRecordEntities.Add(personTimesRecordEntity);
}
// 批量插入人次记录
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();
}
// 提交事务
_db.CommitTran();
}
catch (Exception)
{
//回滚事务
_db.RollbackTran();
throw NCCException.Oh(ErrorCode.COM1001);
}
}
#endregion
#region 更新耗卡备注
///
/// 更新耗卡备注
///
///
/// 专门用于更新耗卡记录的备注信息
///
/// 示例请求:
/// PUT /api/Extend/LqXhHyhk/{id}/UpdateRemark
///
/// 请求体:
/// ```json
/// {
/// "remark": "备注信息(最大2000字符)"
/// }
/// ```
///
/// 耗卡记录ID
/// 备注信息
/// 无返回值
/// 更新成功
/// 参数错误
/// 耗卡记录不存在
/// 服务器错误
[HttpPut("{id}/UpdateRemark")]
public async Task UpdateRemark(string id, [FromBody] LqXhHyhkUpdateRemarkInput input)
{
try
{
// 查询记录
var entity = await _db.Queryable()
.Where(p => p.Id == id && p.IsEffective == StatusEnum.有效.GetHashCode())
.FirstAsync();
if (entity == null)
{
throw NCCException.Oh(ErrorCode.COM1005, "耗卡记录不存在或已作废");
}
// 更新备注
entity.Remark = input.remark;
entity.UpdateTime = DateTime.Now;
// 保存更新
await _db.Updateable(entity)
.UpdateColumns(it => new { it.Remark, it.UpdateTime })
.ExecuteCommandAsync();
_logger.LogInformation($"更新耗卡备注成功,ID:{id}");
}
catch (Exception ex)
{
_logger.LogError(ex, "更新耗卡备注失败,ID:{Id}", id);
throw;
}
}
#endregion
#region 删除会员耗卡
///
/// 删除会员耗卡
///
///
[HttpDelete("{id}")]
public async Task Delete(string id)
{
var entity = await _db.Queryable().FirstAsync(p => p.Id == id);
_ = entity ?? throw NCCException.Oh(ErrorCode.COM1005);
try
{
//开启事务
_db.BeginTran();
//删除会员耗卡记录
await _db.Deleteable().Where(d => d.Id == id).ExecuteCommandAsync();
//清空子表数据
await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync();
//清空子表数据
await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync();
//清空子表数据
await _db.Deleteable().Where(u => u.ConsumeInfoId == id).ExecuteCommandAsync();
//清空子表数据
await _db.Deleteable().Where(u => u.BusinessId == id).ExecuteCommandAsync();
//关闭事务
_db.CommitTran();
}
catch (Exception)
{
//回滚事务
_db.RollbackTran();
throw NCCException.Oh(ErrorCode.COM1002);
}
}
#endregion
#region 修改加班系数
///
/// 修改消耗单的加班系数
///
///
/// 只修改加班系数,自动重新计算所有相关的加班字段
///
/// 计算逻辑:
/// 1. 主表(lq_xh_hyhk):
/// - 加班手工费 = 健康师原始手工费之和 × 新加班系数(科技部不参与加班)
/// - 最终手工费 = 原始手工费 + 加班手工费
///
/// 2. 品项明细表(lq_xh_pxmx):
/// - 加班项目次数 = 原始项目次数 × 新加班系数
/// - 最终项目次数 = 原始项目次数 + 加班项目次数
///
/// 3. 健康师业绩表(lq_xh_jksyj):
/// - 加班耗卡品项次数 = 原始耗卡品项次数 × 新加班系数
/// - 最终耗卡品项次数 = 原始耗卡品项次数 + 加班耗卡品项次数 + 陪同项目次数
/// - 加班手工费 = 原始手工费 × 新加班系数
/// - 最终手工费 = 原始手工费 + 加班手工费
///
/// 示例请求:
/// ```json
/// {
/// "overtimeCoefficient": 0.5
/// }
/// ```
///
/// 耗卡编号
/// 参数
/// 无返回值
/// 修改成功
/// 参数错误或数据验证失败
/// 服务器内部错误
[HttpPut("{id}/overtime-coefficient")]
public async Task UpdateOvertimeCoefficient(string id, [FromBody] LqXhHyhkUpdateOvertimeInput input)
{
try
{
// 开启事务
_db.BeginTran();
// 1. 查询主表记录
var entity = await _db.Queryable()
.Where(p => p.Id == id && p.IsEffective == StatusEnum.有效.GetHashCode())
.FirstAsync();
if (entity == null)
{
throw NCCException.Oh(ErrorCode.COM1005, "耗卡记录不存在或已作废");
}
// 验证原始手工费是否存在
if (entity.OriginalSgfy == null || entity.OriginalSgfy == 0)
{
// 如果原始手工费为空,使用当前的手工费作为原始值
if (entity.Sgfy != null && entity.Sgfy > 0)
{
entity.OriginalSgfy = entity.Sgfy;
}
else
{
throw NCCException.Oh("原始手工费不存在,无法修改加班系数");
}
}
// 2. 查询健康师业绩,计算主表加班手工费(科技部不参与加班)
var jksyjList = await _db.Queryable()
.Where(x => x.Glkdbh == id && x.IsEffective == StatusEnum.有效.GetHashCode())
.ToListAsync();
var jksOriginalLaborCostSum = jksyjList.Sum(j =>
{
var original = j.OriginalLaborCost ?? 0;
if (original == 0 && (j.LaborCost ?? 0) > 0)
{
original = (j.LaborCost ?? 0) - (j.OvertimeLaborCost ?? 0);
if (original < 0) original = 0;
}
return original;
});
// 3. 更新主表加班系数和相关字段
var newCoefficient = input.overtimeCoefficient ?? 0;
var originalSgfy = entity.OriginalSgfy ?? 0;
entity.OvertimeCoefficient = newCoefficient;
entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * newCoefficient);
entity.Sgfy = originalSgfy + (entity.OvertimeSgfy ?? 0);
entity.UpdateTime = DateTime.Now;
await _db.Updateable(entity)
.UpdateColumns(x => new { x.OvertimeCoefficient, x.OvertimeSgfy, x.Sgfy, x.UpdateTime })
.ExecuteCommandAsync();
// 4. 查询所有品项明细,更新加班字段
var pxmxList = await _db.Queryable()
.Where(x => x.ConsumeInfoId == id && x.IsEffective == StatusEnum.有效.GetHashCode())
.ToListAsync();
foreach (var pxmx in pxmxList)
{
// 如果原始项目次数为空,使用当前项目次数作为原始值
if (pxmx.OriginalProjectNumber == null || pxmx.OriginalProjectNumber == 0)
{
if (pxmx.ProjectNumber != null && pxmx.ProjectNumber > 0)
{
pxmx.OriginalProjectNumber = pxmx.ProjectNumber;
}
else
{
pxmx.OriginalProjectNumber = 0;
}
}
var originalProjectNumber = pxmx.OriginalProjectNumber ?? 0;
pxmx.OvertimeProjectNumber = (decimal)(originalProjectNumber * newCoefficient);
pxmx.ProjectNumber = originalProjectNumber + (pxmx.OvertimeProjectNumber ?? 0);
await _db.Updateable(pxmx)
.UpdateColumns(x => new { x.OvertimeProjectNumber, x.ProjectNumber })
.ExecuteCommandAsync();
}
// 5. 更新健康师业绩加班字段(jksyjList 已在步骤2查询)
foreach (var jksyj in jksyjList)
{
// 如果原始耗卡品项次数为空,使用当前值作为原始值
if (jksyj.OriginalKdpxNumber == null || jksyj.OriginalKdpxNumber == 0)
{
// 从最终值中减去陪同项目次数和加班值,得到原始值
var currentKdpxNumber = jksyj.KdpxNumber ?? 0;
var accompaniedNumber = jksyj.AccompaniedProjectNumber ?? 0;
var currentOvertime = jksyj.OvertimeKdpxNumber ?? 0;
jksyj.OriginalKdpxNumber = currentKdpxNumber - accompaniedNumber - currentOvertime;
if (jksyj.OriginalKdpxNumber < 0)
{
jksyj.OriginalKdpxNumber = 0;
}
}
// 如果原始手工费为空,使用当前值作为原始值
if (jksyj.OriginalLaborCost == null || jksyj.OriginalLaborCost == 0)
{
var currentLaborCost = jksyj.LaborCost ?? 0;
var currentOvertimeLaborCost = jksyj.OvertimeLaborCost ?? 0;
jksyj.OriginalLaborCost = currentLaborCost - currentOvertimeLaborCost;
if (jksyj.OriginalLaborCost < 0)
{
jksyj.OriginalLaborCost = 0;
}
}
// 重新计算加班字段
var originalKdpxNumber = jksyj.OriginalKdpxNumber ?? 0;
var originalLaborCost = jksyj.OriginalLaborCost ?? 0;
jksyj.OvertimeKdpxNumber = (decimal)(originalKdpxNumber * newCoefficient);
var accompaniedNumberForCalc = jksyj.AccompaniedProjectNumber ?? 0;
jksyj.KdpxNumber = originalKdpxNumber + (jksyj.OvertimeKdpxNumber ?? 0) + accompaniedNumberForCalc;
jksyj.OvertimeLaborCost = (decimal)(originalLaborCost * newCoefficient);
jksyj.LaborCost = originalLaborCost + (jksyj.OvertimeLaborCost ?? 0);
await _db.Updateable(jksyj)
.UpdateColumns(x => new { x.OvertimeKdpxNumber, x.KdpxNumber, x.OvertimeLaborCost, x.LaborCost })
.ExecuteCommandAsync();
}
// 6. 科技部老师业绩表:当前代码中不参与加班计算,保持原值不变
// 如果需要支持,可以取消注释以下代码
/*
var kjbsyjList = await _db.Queryable()
.Where(x => x.Glkdbh == id && x.IsEffective == StatusEnum.有效.GetHashCode())
.ToListAsync();
foreach (var kjbsyj in kjbsyjList)
{
if (kjbsyj.OriginalHdpxNumber == null || kjbsyj.OriginalHdpxNumber == 0)
{
var currentHdpxNumber = kjbsyj.HdpxNumber ?? 0;
var currentOvertime = kjbsyj.OvertimeHdpxNumber ?? 0;
kjbsyj.OriginalHdpxNumber = currentHdpxNumber - currentOvertime;
if (kjbsyj.OriginalHdpxNumber < 0)
{
kjbsyj.OriginalHdpxNumber = 0;
}
}
if (kjbsyj.OriginalLaborCost == null || kjbsyj.OriginalLaborCost == 0)
{
var currentLaborCost = kjbsyj.LaborCost ?? 0;
var currentOvertimeLaborCost = kjbsyj.OvertimeLaborCost ?? 0;
kjbsyj.OriginalLaborCost = currentLaborCost - currentOvertimeLaborCost;
if (kjbsyj.OriginalLaborCost < 0)
{
kjbsyj.OriginalLaborCost = 0;
}
}
kjbsyj.OvertimeHdpxNumber = (decimal)(kjbsyj.OriginalHdpxNumber * newCoefficient);
kjbsyj.HdpxNumber = kjbsyj.OriginalHdpxNumber + kjbsyj.OvertimeHdpxNumber;
kjbsyj.OvertimeLaborCost = (decimal)(kjbsyj.OriginalLaborCost * newCoefficient);
kjbsyj.LaborCost = kjbsyj.OriginalLaborCost + kjbsyj.OvertimeLaborCost;
await _db.Updateable(kjbsyj)
.UpdateColumns(x => new { x.OvertimeHdpxNumber, x.HdpxNumber, x.OvertimeLaborCost, x.LaborCost })
.ExecuteCommandAsync();
}
*/
// 提交事务
_db.CommitTran();
}
catch (Exception ex)
{
// 回滚事务
_db.RollbackTran();
throw;
}
}
#endregion
#region 查询健康师消耗业绩列表
///
/// 查询健康师业绩列表
///
/// 查询参数
/// 分页的健康师业绩列表
///
/// 查询健康师业绩记录,支持分页和时间筛选
///
/// 示例请求:
/// ```json
/// {
/// "glkdbh": "123456789",
/// "jksId": "健康师ID",
/// "startTime": "2025-01-01T00:00:00",
/// "endTime": "2025-01-31T23:59:59",
/// "currentPage": 1,
/// "pageSize": 10,
/// "sidx": "yjsj",
/// "sort": "desc"
/// }
/// ```
///
/// 参数说明:
/// - glkdbh: 开单记录ID(可选)
/// - jksId: 健康师ID(可选)
/// - startTime: 查询开始时间(可选)
/// - endTime: 查询结束时间(可选)
/// - currentPage: 当前页码
/// - pageSize: 每页大小
///
/// 成功获取健康师业绩列表
/// 请求参数错误
/// 服务器内部错误
[HttpGet("GetJksyjList")]
public async Task GetJksyjList([FromQuery] LqXhJksyjQueryInput input)
{
try
{
var sidx = string.IsNullOrEmpty(input.sidx) ? "yjsj" : input.sidx;
var sort = string.IsNullOrEmpty(input.sort) ? "desc" : input.sort;
var data = await _db.Queryable()
.WhereIF(!string.IsNullOrEmpty(input.Glkdbh), w => w.Glkdbh == input.Glkdbh)
.WhereIF(!string.IsNullOrEmpty(input.JksId), w => w.Jks == input.JksId)
.WhereIF(input.StartTime.HasValue, w => w.Yjsj >= input.StartTime.Value)
.WhereIF(input.EndTime.HasValue, w => w.Yjsj <= input.EndTime.Value)
.Where(w => w.IsEffective == StatusEnum.有效.GetHashCode())
.Select(it => new LqXhJksyjInfoOutput
{
id = it.Id,
glkdbh = it.Glkdbh,
jks = it.Jks,
jksxm = it.Jksxm,
jkszh = it.Jkszh,
jksyj = it.Jksyj.ToString(),
yjsj = it.Yjsj,
jsjId = it.JsjId,
kdpxid = it.Kdpxid,
laborCost = it.LaborCost,
kdpxNumber = it.KdpxNumber,
storeName = SqlFunc.Subqueryable().Where(w => w.Id == it.Glkdbh).Select(w => w.Mdmc),
memberId = SqlFunc.Subqueryable().Where(w => w.Id == it.Glkdbh).Select(w => w.Hy),
memberName = SqlFunc.Subqueryable().Where(w => w.Id == it.Glkdbh).Select(w => w.Hymc),
})
.MergeTable()
.OrderBy(sidx + " " + sort)
.ToPagedListAsync(input.currentPage, input.pageSize);
return PageResult.SqlSugarPageResult(data);
}
catch (Exception ex)
{
throw NCCException.Oh($"获取健康师业绩列表失败: {ex.Message}");
}
}
#endregion
#region 耗卡记录作废
///
/// 耗卡记录作废
///
/// 耗卡记录ID
/// 作废备注
///
[HttpPut("CancelCardUsageRecord/{id}")]
public async Task CancelCardUsageRecord(string id, [FromQuery] string remarks = null)
{
try
{
//开启事务
_db.BeginTran();
//查询消耗记录表信息
var LqhyhkInfo = await _db.Queryable().Where(p => p.Id == id && p.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync();
if (LqhyhkInfo == null)
{
throw NCCException.Oh(ErrorCode.COM1005, "耗卡记录不存在或已作废");
}
//更新耗卡记录状态
LqhyhkInfo.IsEffective = StatusEnum.无效.GetHashCode();
LqhyhkInfo.UpdateTime = DateTime.Now;
LqhyhkInfo.CancelRemark = remarks;
await _db.Updateable(LqhyhkInfo).ExecuteCommandAsync();
//更新品项明细表状态
await _db.Updateable().SetColumns(it => new LqXhPxmxEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(w => w.ConsumeInfoId == id).ExecuteCommandAsync();
//更新健康师业绩表状态
await _db.Updateable().SetColumns(it => new LqXhJksyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(w => w.Glkdbh == id).ExecuteCommandAsync();
//更新科技部老师业绩表状态
await _db.Updateable().SetColumns(it => new LqXhKjbsyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(w => w.Glkdbh == id).ExecuteCommandAsync();
//更新人次记录表状态
await _db.Updateable().SetColumns(it => new LqPersonTimesRecordEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(w => w.BusinessId == id).ExecuteCommandAsync();
//关闭事务
_db.CommitTran();
return LqhyhkInfo;
}
catch (Exception)
{
//回滚事务
_db.RollbackTran();
throw NCCException.Oh(ErrorCode.COM1001);
}
}
#endregion
#region 获取某个会员的耗卡记录
///
/// 获取某个会员的耗卡记录(分页)
///
/// 查询参数
///
[HttpGet("GetMemberCardUsageRecord")]
public async Task GetMemberCardUsageRecord([FromQuery] LqXhHyhkMemberCardUsageQueryInput input)
{
try
{
// 参数验证
if (string.IsNullOrEmpty(input.MemberId))
{
throw NCCException.Oh("会员ID不能为空");
}
// 分页查询会员耗卡记录
var data = await _db.Queryable()
.Where(p => p.Hy == input.MemberId && p.IsEffective == StatusEnum.有效.GetHashCode())
.WhereIF(!string.IsNullOrEmpty(input.StoreId), p => p.Md == input.StoreId)
.WhereIF(input.StartTime.HasValue, p => p.Hksj >= input.StartTime.Value)
.WhereIF(input.EndTime.HasValue, p => p.Hksj <= input.EndTime.Value)
.Select(it => new LqXhHyhkMemberCardUsageOutput
{
Id = it.Id,
MemberId = it.Hy,
MemberName = SqlFunc.Subqueryable().Where(w => w.Id == it.Hy).Select(w => w.Khmc),
StoreId = it.Md,
StoreName = it.Mdmc,
UsageDate = it.Hksj,
TotalAmount = it.Xfje,
Remarks = it.SignatureFile,
CreateTime = it.CreateTime
})
.MergeTable()
.OrderBy((input.sidx == null ? "UsageDate" : input.sidx) + " " + (input.sort ?? "desc"))
.ToPagedListAsync(input.currentPage, input.pageSize);
// 获取当前页耗卡记录ID,直接查询对应的品项明细
if (data.list.Any())
{
var recordIds = data.list.Select(r => r.Id).ToList();
// 批量查询当前页记录的所有品项明细
var itemDetails = await _db.Queryable().Where(w => recordIds.Contains(w.ConsumeInfoId) && w.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
// 按耗卡记录ID分组明细
var detailsGrouped = itemDetails.GroupBy(d => d.ConsumeInfoId).ToDictionary(g => g.Key, g => g.ToList());
// 为每个记录添加品项明细
foreach (var record in data.list)
{
if (detailsGrouped.ContainsKey(record.Id))
{
record.ItemDetails = detailsGrouped[record.Id].Select(detail => new LqXhHyhkMemberCardUsageItemDetail
{
Id = detail.Id,
ItemId = detail.Px,
ItemName = detail.Pxmc,
UnitPrice = detail.Pxjg,
ProjectNumber = detail.ProjectNumber,
TotalPrice = detail.TotalPrice,
SourceType = detail.SourceType,
CreateTime = detail.CreateTIme
}).ToList();
}
}
}
return PageResult.SqlSugarPageResult(data);
}
catch (Exception ex)
{
throw NCCException.Oh($"获取会员耗卡记录失败: {ex.Message}");
}
}
#endregion
#region 同步耗卡数据到人次记录表(同步的时候才用)
///
/// 同步耗卡数据到人次记录表(同步的时候才用)
///
///
/// 将耗卡记录中的健康师和科技老师业绩数据同步到人次记录表(lq_person_times_record)
///
/// 同步逻辑:
/// 1. 查询所有有效的耗卡记录
/// 2. 对于每条耗卡记录,查询关联的健康师业绩和科技老师业绩
/// 3. 对健康师和科技老师进行去重(按人员ID)
/// 4. 为每个去重后的健康师和科技老师创建人次记录
/// 5. 计算人次数量:1 / 健康师(或科技老师)数量
///
/// 字段映射:
/// - F_BusinessId: 耗卡ID(lq_xh_hyhk.F_Id)
/// - F_BusinessType: "耗卡"
/// - F_PersonType: "健康师" 或 "科技老师"
/// - F_PersonId: 健康师ID(lq_xh_jksyj.jks)或科技老师ID(lq_xh_kjbsyj.kjbls)
/// - F_PersonName: 健康师姓名(lq_xh_jksyj.jksxm)或科技老师姓名(lq_xh_kjbsyj.kjblsxm)
/// - F_MemberId: 客户ID(lq_xh_hyhk.hy)
/// - F_MemberName: 客户姓名(lq_xh_hyhk.hymc)
/// - F_WorkDate: 耗卡时间的日期部分(lq_xh_hyhk.hksj)
/// - F_WorkMonth: 耗卡时间的月份部分,格式:YYYYMM(lq_xh_hyhk.hksj)
/// - F_Quantity: 人次数量 = 1 / 健康师(或科技老师)数量
///
/// 注意事项:
/// - 只同步有效的耗卡记录(F_IsEffective = 1)
/// - 只同步有效的健康师业绩和科技老师业绩(F_IsEffective = 1)
/// - 如果已存在相同业务ID的记录,会先删除再重新插入
///
/// 可选,指定耗卡ID,如果为空则同步所有耗卡数据
/// 同步结果,包含同步的记录数
/// 同步成功
/// 服务器内部错误
[HttpPost("sync-person-times-record")]
public async Task SyncPersonTimesRecord([FromQuery] string consumeId = null)
{
try
{
_logger.LogInformation($"开始同步耗卡数据到人次记录表,耗卡ID: {consumeId ?? "全部"}");
// 1. 查询耗卡记录
var consumeQuery = _db.Queryable().Where(x => x.IsEffective == StatusEnum.有效.GetHashCode());
if (!string.IsNullOrEmpty(consumeId))
{
consumeQuery = consumeQuery.Where(x => x.Id == consumeId);
}
var consumeList = await consumeQuery.ToListAsync();
if (!consumeList.Any())
{
return new { success = true, message = "没有需要同步的耗卡记录", count = 0 };
}
_logger.LogInformation($"找到 {consumeList.Count} 条耗卡记录需要同步");
// 2. 批量查询关联数据
var consumeIds = consumeList.Select(x => x.Id).ToList();
// 查询健康师业绩
var jksyjList = await _db.Queryable().Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
// 查询科技老师业绩
var kjbsyjList = await _db.Queryable().Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync();
// 查询已存在的人次记录(按BusinessId去重,避免重复添加)
var existingBusinessIds = await _db.Queryable().Where(x => consumeIds.Contains(x.BusinessId) && x.BusinessType == "耗卡" && x.IsEffective == StatusEnum.有效.GetHashCode()).Select(x => x.BusinessId).Distinct().ToListAsync();
// 批量查询所有会员是否有开单记录(优化:避免在循环中执行N次查询)
var memberIds = consumeList.Where(x => !string.IsNullOrEmpty(x.Hy)).Select(x => x.Hy).Distinct().ToList();
var membersWithBilling = new HashSet();
if (memberIds.Any())
{
var billingMemberIds = await _db.Queryable()
.Where(x => memberIds.Contains(x.Kdhy) && x.IsEffective == StatusEnum.有效.GetHashCode() && x.Sfyj > 0)
.Select(x => x.Kdhy)
.Distinct()
.ToListAsync();
membersWithBilling = new HashSet(billingMemberIds);
}
// 3. 构建人次记录列表
var personTimesRecords = new List();
foreach (var consume in consumeList)
{
// 如果该耗卡记录已存在,跳过
if (existingBusinessIds.Contains(consume.Id))
{
_logger.LogInformation($"耗卡记录 {consume.Id} 已存在人次记录,跳过");
continue;
}
if (consume.Hksj == null)
{
_logger.LogWarning($"耗卡记录 {consume.Id} 的耗卡时间为空,跳过");
continue;
}
var workDate = consume.Hksj.Value.Date; // 工作日期(用于人次统计)
var workMonth = consume.Hksj.Value.ToString("yyyyMM"); // 工作月份(用于人头统计)
//查看该会员是否在开单记录表中存在开单记录(从批量查询结果中查找)
var billingRecord = !string.IsNullOrEmpty(consume.Hy) && membersWithBilling.Contains(consume.Hy);
// 处理健康师业绩:去重后计算人次数量(剔除T区健康师)
var consumeJksyjList = jksyjList.Where(x => x.Glkdbh == consume.Id && !string.IsNullOrEmpty(x.Jks) && !string.IsNullOrEmpty(x.Jksxm) && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList();
if (consumeJksyjList.Any())
{
// 按健康师ID去重
var distinctJksyjList = consumeJksyjList.GroupBy(x => x.Jks).Select(g => g.First()).ToList();
// 计算人次数量:1 / 健康师数量
var jksQuantity = distinctJksyjList.Count > 0 ? 1.0m / distinctJksyjList.Count : 0;
foreach (var jksyj in distinctJksyjList)
{
personTimesRecords.Add(new LqPersonTimesRecordEntity
{
Id = YitIdHelper.NextId().ToString(),
BusinessId = consume.Id,
BusinessType = "耗卡",
PersonType = "健康师",
PersonId = jksyj.Jks,
PersonName = jksyj.Jksxm,
MemberId = consume.Hy,
MemberName = consume.Hymc,
WorkDate = workDate,
WorkMonth = workMonth,
Quantity = jksQuantity,
CreateTime = DateTime.Now,
IsEffective = StatusEnum.有效.GetHashCode(),
HasBilling = billingRecord ? 1 : 0
});
}
}
// 处理科技老师业绩:去重后计算人次数量(剔除T区科技老师)
var consumeKjbsyjList = kjbsyjList.Where(x => x.Glkdbh == consume.Id && !string.IsNullOrEmpty(x.Kjbls) && !string.IsNullOrEmpty(x.Kjblsxm) && (x.Kjblsxm == null || !x.Kjblsxm.Contains("T区"))).ToList();
if (consumeKjbsyjList.Any())
{
// 按科技老师ID去重
var distinctKjbsyjList = consumeKjbsyjList.GroupBy(x => x.Kjbls).Select(g => g.First()).ToList();
// 计算人次数量:1 / 科技老师数量
var kjbsQuantity = distinctKjbsyjList.Count > 0 ? 1.0m / distinctKjbsyjList.Count : 0;
foreach (var kjbsyj in distinctKjbsyjList)
{
personTimesRecords.Add(new LqPersonTimesRecordEntity
{
Id = YitIdHelper.NextId().ToString(),
BusinessId = consume.Id,
BusinessType = "耗卡",
PersonType = "科技老师",
PersonId = kjbsyj.Kjbls,
PersonName = kjbsyj.Kjblsxm,
MemberId = consume.Hy,
MemberName = consume.Hymc,
WorkDate = workDate,
WorkMonth = workMonth,
Quantity = kjbsQuantity,
CreateTime = DateTime.Now,
IsEffective = StatusEnum.有效.GetHashCode(),
HasBilling = billingRecord ? 1 : 0
});
}
}
}
// 4. 使用事务保存数据
var result = await _db.Ado.UseTranAsync(async () =>
{
// 如果指定了耗卡ID,删除该耗卡的旧记录(用于重新同步)
// 如果没有指定耗卡ID,不删除任何记录(因为已经在构建记录列表时跳过了已存在的记录)
if (!string.IsNullOrEmpty(consumeId))
{
await _db.Deleteable().Where(x => x.BusinessId == consumeId && x.BusinessType == "耗卡").ExecuteCommandAsync();
}
// 批量插入新记录
if (personTimesRecords.Any())
{
// 分批插入,每批1000条
var batchSize = 1000;
for (int i = 0; i < personTimesRecords.Count; i += batchSize)
{
var batch = personTimesRecords.Skip(i).Take(batchSize).ToList();
await _db.Insertable(batch).ExecuteCommandAsync();
}
}
return personTimesRecords.Count;
});
if (result.IsSuccess)
{
_logger.LogInformation($"同步完成,共同步 {result.Data} 条人次记录");
return new { success = true, message = "同步成功", count = result.Data };
}
else
{
_logger.LogError($"同步失败: {result.ErrorMessage}");
throw NCCException.Oh(ErrorCode.COM1000, $"同步失败: {result.ErrorMessage}");
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"同步耗卡数据到人次记录表失败: {ex.ToString()}");
throw NCCException.Oh(ErrorCode.COM1005, $"同步耗卡数据到人次记录表失败: {ex.Message}");
}
}
#endregion
#region 耗卡品项明细列表
///
/// 获取耗卡品项明细记录列表
///
///
/// 根据多种条件查询耗卡品项明细记录列表,支持分页、排序、筛选等功能
///
/// 示例请求:
/// GET /api/Extend/LqXhHyhk/consume-item-detail-list?currentPage=1&pageSize=10&startTime=2025-01-01&endTime=2025-01-31
///
/// 传入参数说明:
/// - currentPage: 当前页码(必填,默认1)
/// - pageSize: 每页数量(必填,默认10)
/// - sidx: 排序字段(可选,默认"yjsj")
/// - sort: 排序方式(可选,ASC/DESC,默认DESC)
/// - Id: 品项明细ID(可选,精确匹配)
/// - ConsumeId: 耗卡记录ID(可选,精确匹配)
/// - StartConsumeTime/EndConsumeTime: 耗卡时间范围(可选,格式:yyyy-MM-dd)
/// - startTime/endTime: 耗卡时间范围(兼容参数名,格式:yyyy-MM-dd)
/// - MemberId: 会员ID(可选,精确匹配)
/// - MemberName: 会员名称(可选,模糊查询)
/// - MemberPhone: 会员手机号(可选,精确匹配)
/// - ItemId: 品项ID(可选,精确匹配)
/// - ItemName: 品项名称(可选,模糊查询)
/// - ItemType: 品项类型(可选,精确匹配)
/// - SourceType: 来源类型(可选,精确匹配)
/// - StoreId: 门店ID(可选,精确匹配)
///
/// 返回字段说明:
/// - id: 品项明细ID
/// - consumeId: 耗卡记录ID
/// - consumeTime: 耗卡时间(DateTime格式)
/// - memberName: 会员名称(关联会员表)
/// - memberPhone: 会员手机号(关联会员表)
/// - itemName: 品项名称(品项明细表字段)
/// - itemType: 品项类型(关联项目资料表的qt2字段)
/// - itemPrice: 品项价格(decimal类型)
/// - projectNumber: 项目次数(decimal类型,支持小数)
/// - originalProjectNumber: 原始项目次数(decimal类型)
/// - totalPrice: 合计金额(decimal类型)
/// - sourceType: 来源类型(字符串,如:购买、赠送、体验)
/// - storeId: 门店ID(关联耗卡记录表的门店ID)
/// - storeName: 门店名称(关联门店表的店名字段)
/// - lqXhJksyjList: 健康师业绩列表(关联健康师业绩表,通过F_kdpxid关联品项明细ID)
///
/// 查询参数
/// 耗卡品项明细记录列表(分页)
/// 查询成功,返回耗卡品项明细记录列表
/// 参数错误,如页码或页大小无效
/// 服务器错误,查询过程中发生异常
[HttpGet("consume-item-detail-list")]
public async Task GetConsumeItemDetailList([FromQuery] ConsumeItemDetailListQueryInput input)
{
try
{
var sidx = input.sidx == null ? "yjsj" : input.sidx;
var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
// 处理耗卡时间范围(兼容 StartConsumeTime/EndConsumeTime 和 startTime/endTime 两种参数名)
DateTime? startConsumeTime = null;
DateTime? endConsumeTime = null;
// 优先使用 StartConsumeTime/EndConsumeTime,如果没有则使用 startTime/endTime
string startTimeStr = !string.IsNullOrEmpty(input.StartConsumeTime) ? input.StartConsumeTime : input.startTime;
string endTimeStr = !string.IsNullOrEmpty(input.EndConsumeTime) ? input.EndConsumeTime : input.endTime;
if (!string.IsNullOrEmpty(startTimeStr) && !string.IsNullOrEmpty(endTimeStr))
{
// 尝试解析日期字符串(支持多种格式)
if (DateTime.TryParse(startTimeStr, out DateTime startDate))
{
startConsumeTime = startDate;
}
else
{
throw NCCException.Oh($"开始时间格式错误:{startTimeStr}");
}
if (DateTime.TryParse(endTimeStr, out DateTime endDate))
{
endConsumeTime = endDate;
}
else
{
throw NCCException.Oh($"结束时间格式错误:{endTimeStr}");
}
}
// 优化查询:先分页查询主表,再批量查询关联数据,避免子查询性能问题
// 1. 先构建基础查询条件
var baseQuery = _db.Queryable()
.Where(pxmx => pxmx.IsEffective == StatusEnum.有效.GetHashCode())
.WhereIF(!string.IsNullOrEmpty(input.Id), pxmx => pxmx.Id == input.Id)
.WhereIF(!string.IsNullOrEmpty(input.ConsumeId), pxmx => pxmx.ConsumeInfoId == input.ConsumeId)
.WhereIF(startConsumeTime.HasValue, pxmx => pxmx.Yjsj >= new DateTime(startConsumeTime.Value.Year, startConsumeTime.Value.Month, startConsumeTime.Value.Day, 0, 0, 0))
.WhereIF(endConsumeTime.HasValue, pxmx => pxmx.Yjsj <= new DateTime(endConsumeTime.Value.Year, endConsumeTime.Value.Month, endConsumeTime.Value.Day, 23, 59, 59))
.WhereIF(!string.IsNullOrEmpty(input.MemberId), pxmx => pxmx.MemberId == input.MemberId)
.WhereIF(!string.IsNullOrEmpty(input.ItemId), pxmx => pxmx.Px == input.ItemId)
.WhereIF(!string.IsNullOrEmpty(input.ItemName), pxmx => pxmx.Pxmc != null && pxmx.Pxmc.Contains(input.ItemName))
.WhereIF(!string.IsNullOrEmpty(input.SourceType), pxmx => pxmx.SourceType == input.SourceType)
.WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType);
// 2. 通过 EXISTS 子查询筛选关联字段(在分页前筛选,确保分页准确)
// 处理多门店筛选:优先使用StoreIds,如果没有则使用StoreId
var filterStoreIds = new List();
if (!string.IsNullOrEmpty(input.StoreIds))
{
// 解析逗号分隔的门店ID字符串
filterStoreIds = input.StoreIds.Split(',')
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x.Trim())
.ToList();
}
else if (!string.IsNullOrEmpty(input.StoreId))
{
filterStoreIds = new List { input.StoreId };
}
baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any())
.WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any())
.WhereIF(filterStoreIds.Any(), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.ConsumeInfoId && filterStoreIds.Contains(x.Md)).Any());
// 3. 先分页查询主表数据(查询实体类,提高性能)
var pagedData = await baseQuery.OrderBy(sidx + " " + sort).ToPagedListAsync(input.currentPage, input.pageSize);
// 4. 批量查询关联数据
var itemIds = pagedData.list.Select(x => x.Id).ToList();
var memberIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.MemberId)).Select(x => x.MemberId).Distinct().ToList();
var consumeIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.ConsumeInfoId)).Select(x => x.ConsumeInfoId).Distinct().ToList();
// 批量查询会员信息
var memberDict = new Dictionary();
if (memberIds.Any())
{
var members = await _db.Queryable().Where(x => memberIds.Contains(x.Id)).Select(x => new { x.Id, x.Khmc, x.Sjh }).ToListAsync();
memberDict = members.ToDictionary(x => x.Id, x => (x.Khmc ?? "", x.Sjh ?? ""));
}
// 批量查询耗卡记录,获取门店ID和耗卡时间
var consumeDict = new Dictionary();
if (consumeIds.Any())
{
var consumes = await _db.Queryable().Where(x => consumeIds.Contains(x.Id)).Select(x => new { x.Id, x.Md, x.Hksj }).ToListAsync();
consumeDict = consumes.ToDictionary(x => x.Id, x => (x.Md ?? "", x.Hksj));
}
// 批量查询门店信息
var storeDict = new Dictionary();
var storeIds = consumeDict.Values.Where(x => !string.IsNullOrEmpty(x.StoreId)).Select(x => x.StoreId).Distinct().ToList();
if (storeIds.Any())
{
var stores = await _db.Queryable().Where(x => storeIds.Contains(x.Id)).Select(x => new { x.Id, x.Dm }).ToListAsync();
storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
}
// 批量查询健康师业绩信息(通过 F_kdpxid 关联到品项明细ID)
var jksyjDict = new Dictionary>();
if (itemIds.Any())
{
var jksyjEntities = await _db.Queryable()
.Where(x => itemIds.Contains(x.Kdpxid) && x.IsEffective == StatusEnum.有效.GetHashCode())
.ToListAsync();
var jksyjList = jksyjEntities.Select(x => new LqXhJksyjInfoOutput
{
id = x.Id,
glkdbh = x.Glkdbh,
jks = x.Jks,
jksxm = x.Jksxm,
jkszh = x.Jkszh,
jksyj = x.Jksyj?.ToString() ?? "0",
yjsj = x.Yjsj,
jsjId = x.JsjId,
kdpxid = x.Kdpxid,
laborCost = x.LaborCost,
kdpxNumber = x.KdpxNumber,
originalKdpxNumber = x.OriginalKdpxNumber,
overtimeKdpxNumber = x.OvertimeKdpxNumber,
originalLaborCost = x.OriginalLaborCost,
overtimeLaborCost = x.OvertimeLaborCost,
isAccompanied = x.IsAccompanied,
accompaniedProjectNumber = x.AccompaniedProjectNumber
}).ToList();
jksyjDict = jksyjList
.Where(x => !string.IsNullOrEmpty(x.kdpxid))
.GroupBy(x => x.kdpxid)
.ToDictionary(g => g.Key, g => g.ToList());
}
// 5. 组装返回数据
var resultList = pagedData.list.Select(pxmx =>
{
var jksyjList = jksyjDict.ContainsKey(pxmx.Id) ? jksyjDict[pxmx.Id] : new List();
var firstJksyj = jksyjList.FirstOrDefault();
return new ConsumeItemDetailListOutput
{
id = pxmx.Id,
consumeId = pxmx.ConsumeInfoId,
consumeTime = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) ? consumeDict[pxmx.ConsumeInfoId].ConsumeTime : pxmx.Yjsj,
memberName = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Name : "",
memberPhone = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Phone : "",
itemName = pxmx.Pxmc,
itemType = pxmx.ItemCategory,
itemPrice = pxmx.Pxjg,
projectNumber = pxmx.ProjectNumber,
originalProjectNumber = pxmx.OriginalProjectNumber,
totalPrice = pxmx.TotalPrice,
sourceType = pxmx.SourceType,
storeId = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) ? consumeDict[pxmx.ConsumeInfoId].StoreId : "",
storeName = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) && !string.IsNullOrEmpty(consumeDict[pxmx.ConsumeInfoId].StoreId) && storeDict.ContainsKey(consumeDict[pxmx.ConsumeInfoId].StoreId) ? storeDict[consumeDict[pxmx.ConsumeInfoId].StoreId] : "",
healthCoachId = firstJksyj?.jks ?? "",
healthCoachName = firstJksyj?.jksxm ?? "",
healthCoachAccount = firstJksyj?.jkszh ?? "",
lqXhJksyjList = jksyjList
};
}).ToList();
// 6. 返回分页结果
var result = new SqlSugarPagedList
{
list = resultList,
pagination = pagedData.pagination
};
return PageResult.SqlSugarPageResult(result);
}
catch (Exception ex)
{
_logger.LogError(ex, $"获取耗卡品项明细记录列表失败: {ex.ToString()}");
throw NCCException.Oh(ErrorCode.COM1005, $"获取耗卡品项明细记录列表失败: {ex.Message}");
}
}
#endregion
#region 耗卡品项明细导出(每个健康师一行)
///
/// 导出耗卡品项明细记录(每个健康师一行)
///
///
/// 导出耗卡品项明细记录,如果一个耗卡品项明细有多个健康师,每个健康师单独一行
///
/// 示例请求:
/// GET /api/Extend/LqXhHyhk/consume-item-detail-export?StartConsumeTime=2026-01-01&EndConsumeTime=2026-01-21
///
/// 查询参数(与consume-item-detail-list相同)
/// Excel文件下载链接
[HttpGet("consume-item-detail-export")]
public async Task ExportConsumeItemDetailList([FromQuery] ConsumeItemDetailListQueryInput input)
{
try
{
// 获取所有数据(不分页)
var sidx = input.sidx == null ? "yjsj" : input.sidx;
var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
// 处理耗卡时间范围(兼容 StartConsumeTime/EndConsumeTime 和 startTime/endTime 两种参数名)
DateTime? startConsumeTime = null;
DateTime? endConsumeTime = null;
string startTimeStr = !string.IsNullOrEmpty(input.StartConsumeTime) ? input.StartConsumeTime : input.startTime;
string endTimeStr = !string.IsNullOrEmpty(input.EndConsumeTime) ? input.EndConsumeTime : input.endTime;
if (!string.IsNullOrEmpty(startTimeStr) && !string.IsNullOrEmpty(endTimeStr))
{
if (DateTime.TryParse(startTimeStr, out DateTime startDate))
{
startConsumeTime = startDate;
}
else
{
throw NCCException.Oh($"开始时间格式错误:{startTimeStr}");
}
if (DateTime.TryParse(endTimeStr, out DateTime endDate))
{
endConsumeTime = endDate;
}
else
{
throw NCCException.Oh($"结束时间格式错误:{endTimeStr}");
}
}
// 构建基础查询条件(与GetConsumeItemDetailList相同)
var baseQuery = _db.Queryable()
.Where(pxmx => pxmx.IsEffective == StatusEnum.有效.GetHashCode())
.WhereIF(!string.IsNullOrEmpty(input.Id), pxmx => pxmx.Id == input.Id)
.WhereIF(!string.IsNullOrEmpty(input.ConsumeId), pxmx => pxmx.ConsumeInfoId == input.ConsumeId)
.WhereIF(startConsumeTime.HasValue, pxmx => pxmx.Yjsj >= new DateTime(startConsumeTime.Value.Year, startConsumeTime.Value.Month, startConsumeTime.Value.Day, 0, 0, 0))
.WhereIF(endConsumeTime.HasValue, pxmx => pxmx.Yjsj <= new DateTime(endConsumeTime.Value.Year, endConsumeTime.Value.Month, endConsumeTime.Value.Day, 23, 59, 59))
.WhereIF(!string.IsNullOrEmpty(input.MemberId), pxmx => pxmx.MemberId == input.MemberId)
.WhereIF(!string.IsNullOrEmpty(input.ItemId), pxmx => pxmx.Px == input.ItemId)
.WhereIF(!string.IsNullOrEmpty(input.ItemName), pxmx => pxmx.Pxmc != null && pxmx.Pxmc.Contains(input.ItemName))
.WhereIF(!string.IsNullOrEmpty(input.SourceType), pxmx => pxmx.SourceType == input.SourceType)
.WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType);
// 处理多门店筛选
var filterStoreIds = new List();
if (!string.IsNullOrEmpty(input.StoreIds))
{
filterStoreIds = input.StoreIds.Split(',')
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x.Trim())
.ToList();
}
else if (!string.IsNullOrEmpty(input.StoreId))
{
filterStoreIds = new List { input.StoreId };
}
baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any())
.WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any())
.WhereIF(filterStoreIds.Any(), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.ConsumeInfoId && filterStoreIds.Contains(x.Md)).Any());
// 获取所有数据(不分页)
_logger.LogInformation($"导出耗卡品项明细:开始查询,时间范围: {startTimeStr} 到 {endTimeStr}");
var allData = await baseQuery.OrderBy(sidx + " " + sort).ToListAsync();
_logger.LogInformation($"导出耗卡品项明细:查询到 {allData?.Count ?? 0} 条品项明细记录");
if (allData == null || !allData.Any())
{
_logger.LogWarning($"导出耗卡品项明细:查询结果为空,查询条件 - StartConsumeTime: {startTimeStr}, EndConsumeTime: {endTimeStr}, StartTime: {input.startTime}, EndTime: {input.endTime}");
throw NCCException.Oh(ErrorCode.COM1005, "没有符合条件的数据可导出");
}
// 批量查询关联数据
var itemIds = allData.Select(x => x.Id).ToList();
var memberIds = allData.Where(x => !string.IsNullOrEmpty(x.MemberId)).Select(x => x.MemberId).Distinct().ToList();
var consumeIds = allData.Where(x => !string.IsNullOrEmpty(x.ConsumeInfoId)).Select(x => x.ConsumeInfoId).Distinct().ToList();
// 批量查询会员信息
var memberDict = new Dictionary();
if (memberIds.Any())
{
var members = await _db.Queryable().Where(x => memberIds.Contains(x.Id)).Select(x => new { x.Id, x.Khmc, x.Sjh }).ToListAsync();
memberDict = members.ToDictionary(x => x.Id, x => (x.Khmc ?? "", x.Sjh ?? ""));
}
// 批量查询耗卡记录
var consumeDict = new Dictionary();
if (consumeIds.Any())
{
var consumes = await _db.Queryable().Where(x => consumeIds.Contains(x.Id)).Select(x => new { x.Id, x.Md, x.Hksj }).ToListAsync();
consumeDict = consumes.ToDictionary(x => x.Id, x => (x.Md ?? "", x.Hksj));
}
// 批量查询门店信息
var storeDict = new Dictionary();
var storeIds = consumeDict.Values.Where(x => !string.IsNullOrEmpty(x.StoreId)).Select(x => x.StoreId).Distinct().ToList();
if (storeIds.Any())
{
var stores = await _db.Queryable().Where(x => storeIds.Contains(x.Id)).Select(x => new { x.Id, x.Dm }).ToListAsync();
storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
}
// 批量查询健康师业绩信息
var jksyjDict = new Dictionary>();
if (itemIds.Any())
{
var jksyjEntities = await _db.Queryable()
.Where(x => itemIds.Contains(x.Kdpxid) && x.IsEffective == StatusEnum.有效.GetHashCode())
.ToListAsync();
var jksyjList = jksyjEntities.Select(x => new LqXhJksyjInfoOutput
{
id = x.Id,
glkdbh = x.Glkdbh,
jks = x.Jks,
jksxm = x.Jksxm,
jkszh = x.Jkszh,
jksyj = x.Jksyj?.ToString() ?? "0",
yjsj = x.Yjsj,
jsjId = x.JsjId,
kdpxid = x.Kdpxid,
laborCost = x.LaborCost,
kdpxNumber = x.KdpxNumber,
originalKdpxNumber = x.OriginalKdpxNumber,
overtimeKdpxNumber = x.OvertimeKdpxNumber,
originalLaborCost = x.OriginalLaborCost,
overtimeLaborCost = x.OvertimeLaborCost,
isAccompanied = x.IsAccompanied,
accompaniedProjectNumber = x.AccompaniedProjectNumber
}).ToList();
jksyjDict = jksyjList
.Where(x => !string.IsNullOrEmpty(x.kdpxid))
.GroupBy(x => x.kdpxid)
.ToDictionary(g => g.Key, g => g.ToList());
}
// 展开数据:每个健康师一行
var exportData = new List();
_logger.LogInformation($"开始展开数据,品项明细数量: {allData.Count}, 健康师业绩字典数量: {jksyjDict.Count}");
int processedCount = 0;
foreach (var pxmx in allData)
{
var jksyjList = jksyjDict.ContainsKey(pxmx.Id) ? jksyjDict[pxmx.Id] : new List();
// 基础信息
var baseInfo = new
{
id = pxmx.Id,
consumeId = pxmx.ConsumeInfoId,
consumeTime = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) ? consumeDict[pxmx.ConsumeInfoId].ConsumeTime : pxmx.Yjsj,
memberName = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Name : "",
memberPhone = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Phone : "",
itemName = pxmx.Pxmc,
itemType = pxmx.ItemCategory,
itemPrice = pxmx.Pxjg,
projectNumber = pxmx.ProjectNumber,
originalProjectNumber = pxmx.OriginalProjectNumber,
totalPrice = pxmx.TotalPrice,
sourceType = pxmx.SourceType,
storeId = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) ? consumeDict[pxmx.ConsumeInfoId].StoreId : "",
storeName = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) && !string.IsNullOrEmpty(consumeDict[pxmx.ConsumeInfoId].StoreId) && storeDict.ContainsKey(consumeDict[pxmx.ConsumeInfoId].StoreId) ? storeDict[consumeDict[pxmx.ConsumeInfoId].StoreId] : ""
};
// 如果没有健康师,也要导出一行(健康师信息为空)
if (jksyjList == null || !jksyjList.Any())
{
exportData.Add(new ConsumeItemDetailExportOutput
{
id = baseInfo.id,
consumeId = baseInfo.consumeId,
consumeTime = baseInfo.consumeTime,
memberName = baseInfo.memberName,
memberPhone = baseInfo.memberPhone,
itemName = baseInfo.itemName,
itemType = baseInfo.itemType,
itemPrice = baseInfo.itemPrice,
projectNumber = baseInfo.projectNumber,
originalProjectNumber = baseInfo.originalProjectNumber,
totalPrice = baseInfo.totalPrice,
sourceType = baseInfo.sourceType,
storeId = baseInfo.storeId,
storeName = baseInfo.storeName,
healthCoachId = "",
healthCoachName = "",
healthCoachAccount = "",
healthCoachPerformance = "",
laborCost = null,
kdpxNumber = null
});
}
else
{
// 每个健康师一行
foreach (var jksyj in jksyjList)
{
exportData.Add(new ConsumeItemDetailExportOutput
{
id = baseInfo.id,
consumeId = baseInfo.consumeId,
consumeTime = baseInfo.consumeTime,
memberName = baseInfo.memberName,
memberPhone = baseInfo.memberPhone,
itemName = baseInfo.itemName,
itemType = baseInfo.itemType,
itemPrice = baseInfo.itemPrice,
projectNumber = baseInfo.projectNumber,
originalProjectNumber = baseInfo.originalProjectNumber,
totalPrice = baseInfo.totalPrice,
sourceType = baseInfo.sourceType,
storeId = baseInfo.storeId,
storeName = baseInfo.storeName,
healthCoachId = jksyj.jks ?? "",
healthCoachName = jksyj.jksxm ?? "",
healthCoachAccount = jksyj.jkszh ?? "",
healthCoachPerformance = jksyj.jksyj ?? "0",
laborCost = jksyj.laborCost,
kdpxNumber = jksyj.kdpxNumber
});
}
}
processedCount++;
if (processedCount % 1000 == 0)
{
_logger.LogInformation($"已处理 {processedCount}/{allData.Count} 条品项明细,当前导出数据行数: {exportData.Count}");
}
}
_logger.LogInformation($"数据展开完成,处理品项明细数量: {processedCount}, 导出数据行数: {exportData?.Count ?? 0}");
// 配置Excel导出
List paramList =
"[{\"value\":\"品项明细ID\",\"field\":\"id\"},{\"value\":\"耗卡记录ID\",\"field\":\"consumeId\"},{\"value\":\"耗卡时间\",\"field\":\"consumeTime\"},{\"value\":\"会员名称\",\"field\":\"memberName\"},{\"value\":\"会员手机\",\"field\":\"memberPhone\"},{\"value\":\"品项名称\",\"field\":\"itemName\"},{\"value\":\"品项类型\",\"field\":\"itemType\"},{\"value\":\"品项价格\",\"field\":\"itemPrice\"},{\"value\":\"项目次数\",\"field\":\"projectNumber\"},{\"value\":\"原始项目次数\",\"field\":\"originalProjectNumber\"},{\"value\":\"合计金额\",\"field\":\"totalPrice\"},{\"value\":\"来源类型\",\"field\":\"sourceType\"},{\"value\":\"门店ID\",\"field\":\"storeId\"},{\"value\":\"门店名称\",\"field\":\"storeName\"},{\"value\":\"健康师ID\",\"field\":\"healthCoachId\"},{\"value\":\"健康师姓名\",\"field\":\"healthCoachName\"},{\"value\":\"健康师账号\",\"field\":\"healthCoachAccount\"},{\"value\":\"健康师业绩\",\"field\":\"healthCoachPerformance\"},{\"value\":\"手工费\",\"field\":\"laborCost\"},{\"value\":\"耗卡品项次数\",\"field\":\"kdpxNumber\"}]".ToList();
ExcelConfig excelconfig = new ExcelConfig();
excelconfig.FileName = "耗卡品项明细_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xls";
excelconfig.HeadFont = "微软雅黑";
excelconfig.HeadPoint = 10;
excelconfig.IsAllSizeColumn = true;
excelconfig.ColumnModel = new List();
// 默认导出所有字段
foreach (var param in paramList)
{
excelconfig.ColumnModel.Add(new ExcelColumnModel() { Column = param.field, ExcelColumn = param.value });
}
// 查找项目根目录并创建ExportFiles文件夹
var baseDir = AppContext.BaseDirectory;
var projectRoot = baseDir;
var dir = new DirectoryInfo(baseDir);
while (dir != null && dir.Parent != null)
{
try
{
if (dir.GetDirectories(".git").Any() || dir.GetFiles("*.sln").Any())
{
projectRoot = dir.FullName;
break;
}
}
catch
{
// 忽略访问错误,继续向上查找
}
dir = dir.Parent;
}
// 如果没找到 .git 或 .sln 目录,再查找包含 .sln 文件的目录
if (projectRoot == baseDir)
{
dir = new DirectoryInfo(baseDir);
while (dir != null && dir.Parent != null)
{
try
{
if (dir.GetFiles("*.sln").Any())
{
projectRoot = dir.FullName;
break;
}
}
catch
{
// 忽略访问错误,继续向上查找
}
dir = dir.Parent;
}
}
// 在项目根目录下创建 ExportFiles 文件夹
var exportFilesPath = Path.Combine(projectRoot, "ExportFiles");
if (!Directory.Exists(exportFilesPath))
{
Directory.CreateDirectory(exportFilesPath);
}
var addPath = Path.Combine(exportFilesPath, excelconfig.FileName);
ExcelExportHelper.Export(exportData, excelconfig, addPath);
var fileName = _userManager.UserId + "|" + addPath + "|xls";
var output = new { name = excelconfig.FileName, url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC") };
return output;
}
catch (Exception ex)
{
_logger.LogError(ex, $"导出耗卡品项明细记录失败: {ex.ToString()}");
var errorMessage = $"导出耗卡品项明细记录失败: {ex.Message}";
if (ex.InnerException != null)
{
errorMessage += $" | 内部异常: {ex.InnerException.Message}";
}
throw NCCException.Oh(ErrorCode.COM1005, errorMessage);
}
}
#endregion
}
}