using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NCC.Common.Filter;
using NCC.Common.Helper;
using NCC.Dependency;
using NCC.DynamicApiController;
using NCC.Extend.Entitys.Dto.LqTechGeneralManagerSalary;
using NCC.Extend.Entitys.lq_attendance_summary;
using NCC.Extend.Entitys.lq_hytk_hytk;
using NCC.Extend.Entitys.lq_hytk_jksyj;
using NCC.Extend.Entitys.lq_hytk_mx;
using NCC.Extend.Entitys.lq_kd_jksyj;
using NCC.Extend.Entitys.lq_kd_kdjlb;
using NCC.Extend.Entitys.lq_kd_pxmx;
using NCC.Extend.Entitys.lq_md_general_manager_lifeline;
using NCC.Extend.Entitys.lq_mdxx;
using NCC.Extend.Entitys.lq_tech_general_manager_salary_statistics;
using NCC.Extend.Entitys.lq_xmzl;
using NCC.System.Entitys.Permission;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Yitter.IdGenerator;
using Newtonsoft.Json;
namespace NCC.Extend
{
///
/// 科技部总经理薪酬服务
///
[ApiDescriptionSettings(Tag = "科技部总经理薪酬服务", Name = "LqTechGeneralManagerSalary", Order = 305)]
[Route("api/Extend/[controller]")]
public class LqTechGeneralManagerSalaryService : IDynamicApiController, ITransient
{
private readonly ISqlSugarClient _db;
///
/// 初始化一个类型的新实例
///
public LqTechGeneralManagerSalaryService(ISqlSugarClient db)
{
_db = db;
}
///
/// 获取科技部总经理工资列表
///
/// 查询参数
/// 科技部总经理工资分页列表
[HttpGet("tech-general-manager")]
public async Task GetTechGeneralManagerSalaryList([FromQuery] TechGeneralManagerSalaryInput input)
{
var monthStr = $"{input.Year}{input.Month:D2}";
// 1. 检查当月是否已生成工资数据
var exists = await _db.Queryable()
.AnyAsync(x => x.StatisticsMonth == monthStr);
// 2. 如果没有数据,则进行计算
if (!exists)
{
await CalculateTechGeneralManagerSalary(input.Year, input.Month);
}
// 3. 查询数据
var query = _db.Queryable()
.Where(x => x.StatisticsMonth == monthStr);
if (!string.IsNullOrEmpty(input.Position))
{
query = query.Where(x => x.Position == input.Position);
}
if (!string.IsNullOrEmpty(input.Keyword))
{
query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeAccount.Contains(input.Keyword));
}
var list = await query.Select(x => new TechGeneralManagerSalaryOutput
{
Id = x.Id,
StatisticsMonth = x.StatisticsMonth,
Position = x.Position,
EmployeeName = x.EmployeeName,
EmployeeId = x.EmployeeId,
EmployeeAccount = x.EmployeeAccount,
IsTerminated = x.IsTerminated,
StoreDetail = x.StoreDetail,
TraceabilityAmount = x.TraceabilityAmount,
CellAmount = x.CellAmount,
BaseSalary = x.BaseSalary,
TraceabilityCommissionRate = x.TraceabilityCommissionRate,
TraceabilityCommissionAmount = x.TraceabilityCommissionAmount,
CellCommissionRate = x.CellCommissionRate,
CellCommissionAmount = x.CellCommissionAmount,
TotalCommission = x.TotalCommission,
WorkingDays = x.WorkingDays,
LeaveDays = x.LeaveDays,
CalculatedGrossSalary = x.CalculatedGrossSalary,
FinalGrossSalary = x.FinalGrossSalary,
MonthlyTrainingSubsidy = x.MonthlyTrainingSubsidy,
MonthlyTransportSubsidy = x.MonthlyTransportSubsidy,
LastMonthTrainingSubsidy = x.LastMonthTrainingSubsidy,
LastMonthTransportSubsidy = x.LastMonthTransportSubsidy,
TotalSubsidy = x.TotalSubsidy,
MissingCard = x.MissingCard,
LateArrival = x.LateArrival,
LeaveDeduction = x.LeaveDeduction,
SocialInsuranceDeduction = x.SocialInsuranceDeduction,
RewardDeduction = x.RewardDeduction,
AccommodationDeduction = x.AccommodationDeduction,
StudyPeriodDeduction = x.StudyPeriodDeduction,
WorkClothesDeduction = x.WorkClothesDeduction,
TotalDeduction = x.TotalDeduction,
Bonus = x.Bonus,
ReturnPhoneDeposit = x.ReturnPhoneDeposit,
ReturnAccommodationDeposit = x.ReturnAccommodationDeposit,
ActualSalary = x.ActualSalary,
MonthlyPaymentStatus = x.MonthlyPaymentStatus,
PaidAmount = x.PaidAmount,
PendingAmount = x.PendingAmount,
LastMonthSupplement = x.LastMonthSupplement,
MonthlyTotalPayment = x.MonthlyTotalPayment,
IsLocked = x.IsLocked,
UpdateTime = x.UpdateTime
})
.ToPagedListAsync(input.currentPage, input.pageSize);
return PageResult.SqlSugarPageResult(list);
}
///
/// 计算科技部总经理工资
///
/// 年份
/// 月份
///
[HttpPost("calculate/tech-general-manager")]
public async Task CalculateTechGeneralManagerSalary(int year, int month)
{
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var monthStr = $"{year}{month:D2}";
// 1. 获取基础数据
// 1.1 先从BASE_ORGANIZE表查找组织名称包含"科技一部"或"科技二部"的组织
var techOrganizeList = await _db.Queryable()
.Where(x => x.FullName != null && (x.FullName.Contains("科技一部") || x.FullName.Contains("科技二部"))
&& x.DeleteMark == null && x.EnabledMark == 1)
.Select(x => new { x.Id, x.FullName })
.ToListAsync();
if (!techOrganizeList.Any())
{
// 如果没有找到科技部组织,直接返回
return;
}
var techOrganizeIds = techOrganizeList.Select(x => x.Id).ToList();
var techOrganizeDict = techOrganizeList.ToDictionary(x => x.Id, x => x.FullName);
// 1.2 从BASE_USER表查询岗位为"总经理"且组织ID在科技一部或科技二部的员工
var techGeneralManagerUserList = await _db.Queryable()
.Where(x => x.Gw == "总经理"
&& techOrganizeIds.Contains(x.OrganizeId)
&& x.DeleteMark == null && x.EnabledMark == 1)
.Select(x => new { x.Id, x.RealName, x.Account, x.Gw, x.OrganizeId, x.IsOnJob })
.ToListAsync();
if (!techGeneralManagerUserList.Any())
{
// 如果没有科技部总经理员工,直接返回
return;
}
if (!techGeneralManagerUserList.Any())
{
// 如果没有科技部总经理员工,直接返回
return;
}
// 1.3 获取科技部总经理归属信息(从lq_md_general_manager_lifeline表)
// 通过门店的科技部组织ID(kjb字段)找到科技一部或科技二部管理的门店
// 然后在lifeline表中找到这些门店的记录,这些记录对应的总经理就是科技部总经理
var lifelineList = await _db.Queryable(
(lifeline, store) => lifeline.StoreId == store.Id)
.Where((lifeline, store) =>
lifeline.Month == monthStr
&& techOrganizeIds.Contains(store.Kjb))
.Select((lifeline, store) => lifeline)
.ToListAsync();
// 1.4 获取科技一部和科技二部管理的门店(通过门店的kjb字段)
var techManagedStoreIds = await _db.Queryable()
.Where(x => techOrganizeIds.Contains(x.Kjb))
.Select(x => x.Id)
.ToListAsync();
// 1.5 按科技部总经理ID分组,获取每个科技部总经理管理的门店
// 科技部总经理管理的门店 = 科技一部/科技二部管理的所有门店(通过门店的kjb字段确定)
var managerStoreDict = new Dictionary>();
foreach (var managerUser in techGeneralManagerUserList)
{
var managerId = managerUser.Id;
var managerOrganizeId = managerUser.OrganizeId;
// 如果该总经理属于科技一部,则管理所有科技一部的门店
// 如果该总经理属于科技二部,则管理所有科技二部的门店
var managedStores = await _db.Queryable()
.Where(x => x.Kjb == managerOrganizeId)
.Select(x => x.Id)
.ToListAsync();
managerStoreDict[managerId] = managedStores;
}
// 1.4 门店信息 (lq_mdxx)
var storeList = await _db.Queryable().ToListAsync();
var storeDict = storeList.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id, x => x);
// 1.6 考勤数据 (lq_attendance_summary)
var attendanceList = await _db.Queryable()
.Where(x => x.Year == year && x.Month == month && x.IsEffective == 1)
.ToListAsync();
var attendanceDict = attendanceList.ToDictionary(x => x.UserId, x => x);
// 1.7 获取所有管理的门店ID列表(用于后续查询,如果没有管理的门店,则为空列表)
var allManagedStoreIds = managerStoreDict.Values.SelectMany(x => x).Distinct().ToList();
// 1.8 按科技部总经理和门店分组统计(用于生成门店明细JSON和汇总数据)
var storeDetailDict = new Dictionary>();
// 按门店统计溯源和Cell金额(如果有管理的门店)
if (allManagedStoreIds.Any())
{
foreach (var storeId in allManagedStoreIds)
{
// 该门店的开单溯源金额(从健康师业绩表统计)
var storeTraceabilityBillingList = await _db.Queryable()
.Where(x => x.IsEffective == 1
&& x.StoreId == storeId
&& (x.BeautyType == "溯源系统" || x.BeautyType == "溯源")
&& x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1))
.Select(x => x.Jksyj)
.ToListAsync();
var storeTraceabilityBilling = storeTraceabilityBillingList
.Where(x => !string.IsNullOrEmpty(x))
.Sum(x => decimal.TryParse(x, out var val) ? val : 0m);
// 该门店的退卡溯源金额(从退卡健康师业绩表统计)
var storeTraceabilityRefund = await _db.Queryable()
.Where(x => x.IsEffective == 1
&& x.StoreId == storeId
&& (x.BeautyType == "溯源系统" || x.BeautyType == "溯源")
&& x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1))
.SumAsync(x => (decimal?)x.Jksyj) ?? 0m;
// 该门店的开单Cell金额(从健康师业绩表统计)
var storeCellBillingList = await _db.Queryable()
.Where(x => x.IsEffective == 1
&& x.StoreId == storeId
&& (x.BeautyType == "cell" || x.BeautyType == "Cell")
&& x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1))
.Select(x => x.Jksyj)
.ToListAsync();
var storeCellBilling = storeCellBillingList
.Where(x => !string.IsNullOrEmpty(x))
.Sum(x => decimal.TryParse(x, out var val) ? val : 0m);
// 该门店的退卡Cell金额(从退卡健康师业绩表统计)
var storeCellRefund = await _db.Queryable()
.Where(x => x.IsEffective == 1
&& x.StoreId == storeId
&& (x.BeautyType == "cell" || x.BeautyType == "Cell")
&& x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1))
.SumAsync(x => (decimal?)x.Jksyj) ?? 0m;
// 获取该门店属于哪些科技部总经理
// 通过门店的kjb字段确定:如果门店的kjb等于科技一部的组织ID,则该门店属于科技一部总经理
var store = storeDict.ContainsKey(storeId) ? storeDict[storeId] : null;
var managersOfStore = new List();
if (store != null && !string.IsNullOrEmpty(store.Kjb))
{
// 找到组织ID等于门店kjb的科技部总经理
var managers = techGeneralManagerUserList
.Where(x => x.OrganizeId == store.Kjb)
.Select(x => x.Id)
.ToList();
managersOfStore.AddRange(managers);
}
foreach (var managerId in managersOfStore)
{
if (!storeDetailDict.ContainsKey(managerId))
{
storeDetailDict[managerId] = new Dictionary();
}
var storeName = storeDict.ContainsKey(storeId) ? storeDict[storeId].Dm ?? "" : "";
storeDetailDict[managerId][storeId] = new StoreDetailItem
{
StoreId = storeId,
StoreName = storeName,
TraceabilityBillingAmount = storeTraceabilityBilling,
TraceabilityRefundAmount = storeTraceabilityRefund,
TraceabilityAmount = storeTraceabilityBilling - storeTraceabilityRefund,
CellBillingAmount = storeCellBilling,
CellRefundAmount = storeCellRefund,
CellAmount = storeCellBilling - storeCellRefund
};
}
}
}
// 2. 按科技部总经理聚合数据
var managerStats = new Dictionary();
foreach (var managerUser in techGeneralManagerUserList)
{
var managerId = managerUser.Id;
// 获取该科技部总经理管理的门店列表
var managedStores = managerStoreDict.ContainsKey(managerId) ? managerStoreDict[managerId] : new List();
// 2.1 创建工资统计对象
// 岗位使用组织名称(科技一部/科技二部)
var position = techOrganizeDict.ContainsKey(managerUser.OrganizeId)
? techOrganizeDict[managerUser.OrganizeId]
: "";
var salary = new LqTechGeneralManagerSalaryStatisticsEntity
{
Id = YitIdHelper.NextId().ToString(),
StatisticsMonth = monthStr,
EmployeeId = managerId,
Position = position,
EmployeeName = managerUser.RealName ?? "",
EmployeeAccount = managerUser.Account ?? "",
IsTerminated = managerUser.IsOnJob == 0 ? 1 : 0,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now,
IsLocked = 0
};
// 2.2 考勤数据
var attendance = attendanceDict.ContainsKey(managerId) ? attendanceDict[managerId] : null;
salary.WorkingDays = attendance?.WorkDays ?? 0;
salary.LeaveDays = attendance?.LeaveDays ?? 0;
// 2.3 计算底薪(固定4000元)
salary.BaseSalary = 4000m;
// 2.4 统计该科技部总经理管理的所有门店的溯源金额和Cell金额总和
decimal totalTraceabilityAmount = 0m;
decimal totalCellAmount = 0m;
var storeDetails = new List();
if (managedStores.Any() && storeDetailDict.ContainsKey(managerId))
{
foreach (var storeId in managedStores)
{
if (storeDetailDict[managerId].ContainsKey(storeId))
{
var storeDetail = storeDetailDict[managerId][storeId];
totalTraceabilityAmount += storeDetail.TraceabilityAmount;
totalCellAmount += storeDetail.CellAmount;
storeDetails.Add(storeDetail);
}
}
}
salary.TraceabilityAmount = totalTraceabilityAmount;
salary.CellAmount = totalCellAmount;
// 2.5 保存门店明细(JSON格式)
salary.StoreDetail = JsonConvert.SerializeObject(storeDetails);
// 2.6 计算溯源金额提成(分段累进)
var traceabilityCommission = CalculateTraceabilityCommission(totalTraceabilityAmount);
salary.TraceabilityCommissionAmount = traceabilityCommission.Amount;
salary.TraceabilityCommissionRate = traceabilityCommission.Rate;
// 2.7 计算Cell金额提成(分段累进)
var cellCommission = CalculateCellCommission(totalCellAmount);
salary.CellCommissionAmount = cellCommission.Amount;
salary.CellCommissionRate = cellCommission.Rate;
// 2.8 提成合计
salary.TotalCommission = salary.TraceabilityCommissionAmount + salary.CellCommissionAmount;
// 2.9 计算应发工资
salary.CalculatedGrossSalary = salary.BaseSalary + salary.TotalCommission;
salary.FinalGrossSalary = salary.CalculatedGrossSalary;
// 2.10 初始化其他字段(默认值为0)
salary.MonthlyTrainingSubsidy = 0;
salary.MonthlyTransportSubsidy = 0;
salary.LastMonthTrainingSubsidy = 0;
salary.LastMonthTransportSubsidy = 0;
salary.TotalSubsidy = 0;
salary.MissingCard = 0;
salary.LateArrival = 0;
salary.LeaveDeduction = 0;
salary.SocialInsuranceDeduction = 0;
salary.RewardDeduction = 0;
salary.AccommodationDeduction = 0;
salary.StudyPeriodDeduction = 0;
salary.WorkClothesDeduction = 0;
salary.TotalDeduction = 0;
salary.Bonus = 0;
salary.ReturnPhoneDeposit = 0;
salary.ReturnAccommodationDeposit = 0;
salary.ActualSalary = salary.FinalGrossSalary - salary.TotalDeduction + salary.TotalSubsidy + salary.Bonus;
salary.MonthlyPaymentStatus = "未发放";
salary.PaidAmount = 0;
salary.PendingAmount = salary.ActualSalary;
salary.LastMonthSupplement = 0;
salary.MonthlyTotalPayment = 0;
managerStats[managerId] = salary;
}
// 3. 保存数据
if (managerStats.Any())
{
// 先删除当月旧数据 (防止重复)
await _db.Deleteable()
.Where(x => x.StatisticsMonth == monthStr)
.ExecuteCommandAsync();
await _db.Insertable(managerStats.Values.ToList()).ExecuteCommandAsync();
}
}
///
/// 计算溯源金额提成(分段累进)
///
/// 溯源金额
/// 提成金额和平均比例
private (decimal Amount, decimal? Rate) CalculateTraceabilityCommission(decimal traceabilityAmount)
{
if (traceabilityAmount <= 0)
{
return (0m, null);
}
decimal commissionAmount = 0m;
decimal? averageRate = null;
if (traceabilityAmount < 200000m)
{
// < 200,000元:1%
commissionAmount = traceabilityAmount * 0.01m;
averageRate = 1.00m;
}
else if (traceabilityAmount < 300000m)
{
// 200,000-300,000元:1.5%
commissionAmount = 200000m * 0.01m + (traceabilityAmount - 200000m) * 0.015m;
averageRate = (commissionAmount / traceabilityAmount) * 100m;
}
else if (traceabilityAmount < 500000m)
{
// 300,000-500,000元:2%
commissionAmount = 200000m * 0.01m + 100000m * 0.015m + (traceabilityAmount - 300000m) * 0.02m;
averageRate = (commissionAmount / traceabilityAmount) * 100m;
}
else
{
// ≥ 500,000元:2.5%
commissionAmount = 200000m * 0.01m + 100000m * 0.015m + 200000m * 0.02m + (traceabilityAmount - 500000m) * 0.025m;
averageRate = (commissionAmount / traceabilityAmount) * 100m;
}
return (commissionAmount, averageRate);
}
///
/// 计算Cell金额提成(分段累进)
///
/// Cell金额
/// 提成金额和平均比例
private (decimal Amount, decimal? Rate) CalculateCellCommission(decimal cellAmount)
{
if (cellAmount <= 0)
{
return (0m, null);
}
if (cellAmount < 50000m)
{
// < 50,000元:无提成
return (0m, null);
}
decimal commissionAmount = 0m;
decimal? averageRate = null;
if (cellAmount < 400000m)
{
// 50,000-400,000元:1%
commissionAmount = (cellAmount - 50000m) * 0.01m;
averageRate = (commissionAmount / cellAmount) * 100m;
}
else
{
// ≥ 400,000元:1.5%
commissionAmount = 350000m * 0.01m + (cellAmount - 400000m) * 0.015m;
averageRate = (commissionAmount / cellAmount) * 100m;
}
return (commissionAmount, averageRate);
}
///
/// 门店明细项(用于JSON序列化)
///
private class StoreDetailItem
{
public string StoreId { get; set; }
public string StoreName { get; set; }
public decimal TraceabilityBillingAmount { get; set; }
public decimal TraceabilityRefundAmount { get; set; }
public decimal TraceabilityAmount { get; set; }
public decimal CellBillingAmount { get; set; }
public decimal CellRefundAmount { get; set; }
public decimal CellAmount { get; set; }
}
}
}