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.LqBusinessUnitManagerSalary;
using NCC.Extend.Entitys.lq_attendance_summary;
using NCC.Extend.Entitys.lq_hytk_hytk;
using NCC.Extend.Entitys.lq_kd_kdjlb;
using NCC.Extend.Entitys.lq_md_general_manager_lifeline;
using NCC.Extend.Entitys.lq_md_target;
using NCC.Extend.Entitys.lq_mdxx;
using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics;
using NCC.System.Entitys.Permission;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Yitter.IdGenerator;
namespace NCC.Extend
{
///
/// 事业部总经理/经理薪酬服务
///
[ApiDescriptionSettings(Tag = "事业部总经理/经理薪酬服务", Name = "LqBusinessUnitManagerSalary", Order = 304)]
[Route("api/Extend/[controller]")]
public class LqBusinessUnitManagerSalaryService : IDynamicApiController, ITransient
{
private readonly ISqlSugarClient _db;
///
/// 初始化一个类型的新实例
///
public LqBusinessUnitManagerSalaryService(ISqlSugarClient db)
{
_db = db;
}
///
/// 获取事业部总经理/经理工资列表
///
/// 查询参数
/// 事业部总经理/经理工资分页列表
[HttpGet("business-unit-manager")]
public async Task GetBusinessUnitManagerSalaryList([FromQuery] BusinessUnitManagerSalaryInput input)
{
var monthStr = $"{input.Year}{input.Month:D2}";
// 1. 检查当月是否已生成工资数据
var exists = await _db.Queryable()
.AnyAsync(x => x.StatisticsMonth == monthStr);
// 2. 如果没有数据,则进行计算
if (!exists)
{
await CalculateBusinessUnitManagerSalary(input.Year, input.Month);
}
// 3. 查询数据
var query = _db.Queryable()
.Where(x => x.StatisticsMonth == monthStr);
if (input.ManagerType.HasValue)
{
query = query.Where(x => x.ManagerType == input.ManagerType.Value);
}
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 BusinessUnitManagerSalaryOutput
{
Id = x.Id,
StatisticsMonth = x.StatisticsMonth,
Position = x.Position,
EmployeeName = x.EmployeeName,
EmployeeId = x.EmployeeId,
EmployeeAccount = x.EmployeeAccount,
ManagerType = x.ManagerType,
IsTerminated = x.IsTerminated,
StorePerformanceDetail = x.StorePerformanceDetail,
BaseSalary = x.BaseSalary,
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/business-unit-manager")]
public async Task CalculateBusinessUnitManagerSalary(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 获取总经理/经理归属信息(从lq_md_general_manager_lifeline表)
var lifelineList = await _db.Queryable()
.Where(x => x.Month == monthStr)
.ToListAsync();
if (!lifelineList.Any())
{
// 如果没有归属信息,直接返回
return;
}
// 1.2 获取所有不重复的总经理/经理ID(确保所有总经理/经理都被计算)
var allManagerIds = lifelineList
.Where(x => !string.IsNullOrEmpty(x.GeneralManagerId))
.Select(x => x.GeneralManagerId)
.Distinct()
.ToList();
// 1.3 按总经理/经理ID分组,获取每个总经理/经理管理的门店
var managerStoreDict = lifelineList
.Where(x => !string.IsNullOrEmpty(x.GeneralManagerId) && !string.IsNullOrEmpty(x.StoreId))
.GroupBy(x => x.GeneralManagerId)
.ToDictionary(g => g.Key, g => g.Select(x => x.StoreId).Distinct().ToList());
// 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.5 门店生命线信息 (lq_md_target)
var targetList = await _db.Queryable()
.Where(x => x.Month == monthStr)
.ToListAsync();
var storeLifelineDict = targetList
.Where(x => !string.IsNullOrEmpty(x.StoreId))
.ToDictionary(x => x.StoreId, x => x.StoreLifeline);
// 1.6 门店总业绩计算 (开单实付 - 退卡金额)
// 开单实付(从lq_kd_kdjlb表统计sfyj字段)
var storeBillingList = await _db.Queryable()
.Where(x => x.Kdrq >= startDate && x.Kdrq <= endDate.AddDays(1) && x.IsEffective == 1)
.Select(x => new { x.Djmd, x.Sfyj })
.ToListAsync();
var storeBillingDict = storeBillingList
.Where(x => !string.IsNullOrEmpty(x.Djmd))
.GroupBy(x => x.Djmd)
.ToDictionary(g => g.Key, g => g.Sum(x => x.Sfyj));
// 退卡金额(从lq_hytk_hytk表统计,使用F_ActualRefundAmount,如果没有则使用tkje)
var storeRefundList = await _db.Queryable()
.Where(x => x.Tksj >= startDate && x.Tksj <= endDate.AddDays(1) && x.IsEffective == 1)
.Select(x => new { x.Md, x.ActualRefundAmount, x.Tkje })
.ToListAsync();
var storeRefundDict = storeRefundList
.Where(x => !string.IsNullOrEmpty(x.Md))
.GroupBy(x => x.Md)
.ToDictionary(g => g.Key, g => g.Sum(x => x.ActualRefundAmount ?? x.Tkje ?? 0));
// 1.7 考勤数据 (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.8 获取员工信息 (BASE_USER)
var userList = await _db.Queryable()
.Where(x => allManagerIds.Contains(x.Id))
.Select(x => new { x.Id, x.RealName, x.Account, x.IsOnJob })
.ToListAsync();
var userDict = userList.ToDictionary(x => x.Id, x => x);
// 2. 按总经理/经理聚合数据
var managerStats = new Dictionary();
foreach (var managerId in allManagerIds)
{
if (string.IsNullOrEmpty(managerId))
{
continue;
}
// 获取该总经理/经理的信息
var managerLifeline = lifelineList.FirstOrDefault(x => x.GeneralManagerId == managerId);
if (managerLifeline == null)
{
continue;
}
// 2.1 创建工资统计对象
var salary = new LqBusinessUnitManagerSalaryStatisticsEntity
{
Id = YitIdHelper.NextId().ToString(),
StatisticsMonth = monthStr,
EmployeeId = managerId,
ManagerType = managerLifeline.ManagerType,
Position = managerLifeline.ManagerType == 1 ? "总经理" : "经理",
IsTerminated = 0,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now,
IsLocked = 0
};
// 2.2 填充员工信息
if (userDict.ContainsKey(managerId))
{
var user = userDict[managerId];
salary.EmployeeName = user.RealName ?? "";
salary.EmployeeAccount = user.Account ?? "";
salary.IsTerminated = user.IsOnJob == 0 ? 1 : 0;
}
// 2.3 考勤数据
var attendance = attendanceDict.ContainsKey(managerId) ? attendanceDict[managerId] : null;
salary.WorkingDays = attendance?.WorkDays ?? 0;
salary.LeaveDays = attendance?.LeaveDays ?? 0;
// 2.4 计算底薪(固定4000元)
salary.BaseSalary = 4000m;
// 2.5 遍历该总经理/经理管理的每个门店,计算提成
var storePerformanceDetails = new List();
decimal totalCommission = 0m;
// 获取该总经理/经理管理的门店列表(如果没有管理的门店,则为空列表)
var managedStores = managerStoreDict.ContainsKey(managerId) ? managerStoreDict[managerId] : new List();
foreach (var storeId in managedStores)
{
if (string.IsNullOrEmpty(storeId))
{
continue;
}
// 获取该门店的提成阶梯设置
var storeLifelineSetting = lifelineList.FirstOrDefault(x => x.StoreId == storeId && x.GeneralManagerId == managerId);
if (storeLifelineSetting == null)
{
continue;
}
// 获取门店信息
var storeName = storeDict.ContainsKey(storeId) ? storeDict[storeId].Dm ?? "" : "";
// 获取门店生命线(提成门槛)
if (!storeLifelineDict.ContainsKey(storeId))
{
// 门店生命线未设置,跳过该门店
storePerformanceDetails.Add(new StorePerformanceDetail
{
StoreId = storeId,
StoreName = storeName,
StoreLifeline = 0,
BillingPerformance = 0,
RefundPerformance = 0,
StorePerformance = 0,
ReachedLifeline = false,
CommissionAmount = 0,
CalculationDetail = "门店生命线未设置,无法计算提成"
});
continue;
}
var storeLifeline = storeLifelineDict[storeId];
if (storeLifeline <= 0)
{
// 门店生命线未设置或为0,跳过该门店
storePerformanceDetails.Add(new StorePerformanceDetail
{
StoreId = storeId,
StoreName = storeName,
StoreLifeline = 0,
BillingPerformance = 0,
RefundPerformance = 0,
StorePerformance = 0,
ReachedLifeline = false,
CommissionAmount = 0,
CalculationDetail = "门店生命线未设置或为0,无法计算提成"
});
continue;
}
// 获取门店业绩
var billing = storeBillingDict.ContainsKey(storeId) ? storeBillingDict[storeId] : 0;
var refund = storeRefundDict.ContainsKey(storeId) ? storeRefundDict[storeId] : 0;
var storePerformance = billing - refund;
// 判断是否达到门店生命线
var reachedLifeline = storePerformance >= storeLifeline;
// 计算提成
decimal commissionAmount = 0m;
string calculationDetail = "";
if (reachedLifeline)
{
// 达到门店生命线,使用提成阶梯计算提成(分段累进)
var commissionResult = CalculateStoreCommission(storePerformance, storeLifelineSetting);
commissionAmount = commissionResult.Amount;
calculationDetail = commissionResult.Detail;
totalCommission += commissionAmount;
}
else
{
calculationDetail = $"业绩{storePerformance:N2}元,未达到门店生命线{storeLifeline:N2}元,无提成";
}
// 添加到门店业绩明细
storePerformanceDetails.Add(new StorePerformanceDetail
{
StoreId = storeId,
StoreName = storeName,
StoreLifeline = storeLifeline,
BillingPerformance = billing,
RefundPerformance = refund,
StorePerformance = storePerformance,
ReachedLifeline = reachedLifeline,
Lifeline1 = storeLifelineSetting.Lifeline1,
CommissionRate1 = storeLifelineSetting.CommissionRate1,
Lifeline2 = storeLifelineSetting.Lifeline2,
CommissionRate2 = storeLifelineSetting.CommissionRate2,
Lifeline3 = storeLifelineSetting.Lifeline3,
CommissionRate3 = storeLifelineSetting.CommissionRate3,
CommissionAmount = commissionAmount,
CalculationDetail = calculationDetail
});
}
// 2.6 保存门店业绩明细(JSON格式)
salary.StorePerformanceDetail = storePerformanceDetails.ToJson();
// 2.7 提成合计
salary.TotalCommission = totalCommission;
// 2.8 计算应发工资
salary.CalculatedGrossSalary = salary.BaseSalary + salary.TotalCommission;
salary.FinalGrossSalary = salary.CalculatedGrossSalary;
// 2.9 初始化其他字段(默认值为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, string Detail) CalculateStoreCommission(decimal storePerformance, LqMdGeneralManagerLifelineEntity lifelineSetting)
{
// 验证提成阶梯1和提成比例1必须设置
if (lifelineSetting.Lifeline1 <= 0 || lifelineSetting.CommissionRate1 <= 0)
{
return (0m, "提成阶梯1或提成比例1未设置,无法计算提成");
}
decimal commissionAmount = 0m;
string detail = "";
var lifeline1 = lifelineSetting.Lifeline1;
var rate1 = lifelineSetting.CommissionRate1;
var lifeline2 = lifelineSetting.Lifeline2 ?? 0;
var rate2 = lifelineSetting.CommissionRate2 ?? 0;
var lifeline3 = lifelineSetting.Lifeline3 ?? 0;
var rate3 = lifelineSetting.CommissionRate3 ?? 0;
// 分段累进计算
if (storePerformance <= lifeline1)
{
// 业绩 ≤ 提成阶梯1
commissionAmount = storePerformance * (rate1 / 100m);
detail = $"业绩{storePerformance:N2}元,≤ 提成阶梯1({lifeline1:N2}元),提成 = {storePerformance:N2} × {rate1}% = {commissionAmount:N2}元";
}
else if (lifeline2 > 0 && storePerformance <= lifeline2)
{
// 提成阶梯1 < 业绩 ≤ 提成阶梯2
var part1 = lifeline1 * (rate1 / 100m);
var part2 = (storePerformance - lifeline1) * (rate2 / 100m);
commissionAmount = part1 + part2;
detail = $"业绩{storePerformance:N2}元,> 提成阶梯1({lifeline1:N2}元) 且 ≤ 提成阶梯2({lifeline2:N2}元),提成 = {lifeline1:N2} × {rate1}% + ({storePerformance:N2} - {lifeline1:N2}) × {rate2}% = {part1:N2} + {part2:N2} = {commissionAmount:N2}元";
}
else if (lifeline3 > 0 && storePerformance <= lifeline3)
{
// 提成阶梯2 < 业绩 ≤ 提成阶梯3
var part1 = lifeline1 * (rate1 / 100m);
var part2 = (lifeline2 - lifeline1) * (rate2 / 100m);
var part3 = (storePerformance - lifeline2) * (rate3 / 100m);
commissionAmount = part1 + part2 + part3;
detail = $"业绩{storePerformance:N2}元,> 提成阶梯2({lifeline2:N2}元) 且 ≤ 提成阶梯3({lifeline3:N2}元),提成 = {lifeline1:N2} × {rate1}% + ({lifeline2:N2} - {lifeline1:N2}) × {rate2}% + ({storePerformance:N2} - {lifeline2:N2}) × {rate3}% = {part1:N2} + {part2:N2} + {part3:N2} = {commissionAmount:N2}元";
}
else if (lifeline3 > 0)
{
// 业绩 > 提成阶梯3
var part1 = lifeline1 * (rate1 / 100m);
var part2 = (lifeline2 - lifeline1) * (rate2 / 100m);
var part3 = (lifeline3 - lifeline2) * (rate3 / 100m);
var part4 = (storePerformance - lifeline3) * (rate3 / 100m);
commissionAmount = part1 + part2 + part3 + part4;
detail = $"业绩{storePerformance:N2}元,> 提成阶梯3({lifeline3:N2}元),提成 = {lifeline1:N2} × {rate1}% + ({lifeline2:N2} - {lifeline1:N2}) × {rate2}% + ({lifeline3:N2} - {lifeline2:N2}) × {rate3}% + ({storePerformance:N2} - {lifeline3:N2}) × {rate3}% = {part1:N2} + {part2:N2} + {part3:N2} + {part4:N2} = {commissionAmount:N2}元";
}
else if (lifeline2 > 0)
{
// 提成阶梯3未设置,业绩 > 提成阶梯2,按提成比例2计算超出部分
var part1 = lifeline1 * (rate1 / 100m);
var part2 = (storePerformance - lifeline1) * (rate2 / 100m);
commissionAmount = part1 + part2;
detail = $"业绩{storePerformance:N2}元,> 提成阶梯2({lifeline2:N2}元),提成阶梯3未设置,提成 = {lifeline1:N2} × {rate1}% + ({storePerformance:N2} - {lifeline1:N2}) × {rate2}% = {part1:N2} + {part2:N2} = {commissionAmount:N2}元";
}
else
{
// 只有提成阶梯1,业绩 > 提成阶梯1,按提成比例1计算
commissionAmount = storePerformance * (rate1 / 100m);
detail = $"业绩{storePerformance:N2}元,> 提成阶梯1({lifeline1:N2}元),提成阶梯2未设置,提成 = {storePerformance:N2} × {rate1}% = {commissionAmount:N2}元";
}
return (commissionAmount, detail);
}
///
/// 门店业绩明细(用于JSON序列化)
///
private class StorePerformanceDetail
{
public string StoreId { get; set; }
public string StoreName { get; set; }
public decimal StoreLifeline { get; set; }
public decimal BillingPerformance { get; set; }
public decimal RefundPerformance { get; set; }
public decimal StorePerformance { get; set; }
public bool ReachedLifeline { get; set; }
public decimal Lifeline1 { get; set; }
public decimal CommissionRate1 { get; set; }
public decimal? Lifeline2 { get; set; }
public decimal? CommissionRate2 { get; set; }
public decimal? Lifeline3 { get; set; }
public decimal? CommissionRate3 { get; set; }
public decimal CommissionAmount { get; set; }
public string CalculationDetail { get; set; }
}
}
}