using F9WebAiTest.Application.Common;
using F9WebAiTest.Application.Dtos.PayRoll;
using F9WebAiTest.Domain.Common;
using F9WebAiTest.Domain.Entities.Base;
using F9WebAiTest.Domain.Entities.PayRoll;
using F9WebAiTest.Domain.Entities.Prod;
using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments;
using SysEmp = F9WebAiTest.Domain.Entities.SYS.Employees;
using SysWt = F9WebAiTest.Domain.Entities.SYS.WorkTypes;
namespace F9WebAiTest.Application.Services.PayRoll;
///
/// 薪资计算-立缫挡车车头(挡车工 / 车头工 产量计算)
/// 对应 F9 WinClient\F9.Business.PayRoll\PAYROLL\CALCLDCCT(Page_CalcLdcct + Page_CalcLdcct_Edit)
/// 数据源:看台记录(挡车/车头)、煮茧日报(今缫桶数)、落丝记录(茧量)、秤大丝(丝量/缫折)、
/// 纤度检验(纤度明细/均方差/总差)、黑板检验(二度/三度/清洁/洁净)、生丝规格、生丝定级指标。
/// 配置:工艺庄口薪资配置、工种薪资配置、挡车岗位薪资配置、挡车纤度奖扣、薪资价格配置。
///
public interface ICalcLdcctService
{
Task> GetPagedAsync(int pageIndex, int pageSize, DateTime start, DateTime end,
long? depId, long? worktypeId, string like);
Task CalcDcgAsync(CalcLdcctCalcDto dto);
Task CalcCtgAsync(CalcLdcctCalcDto dto);
Task SaveAsync(CalcLdcctSaveDto dto);
Task DeleteAsync(DateTime start, DateTime end, long depId, long worktypeId);
}
public class CalcLdcctService : ICalcLdcctService
{
private readonly IRepository _repo;
private readonly IRepository _dcgRepo;
private readonly IRepository _ctgRepo;
private readonly IRepository _zjrbRepo;
private readonly IRepository _zjrbJizuRepo;
private readonly IRepository _lsjlRepo;
private readonly IRepository _cdsLsjlRepo;
private readonly IRepository _cdsChetaiRepo;
private readonly IRepository _xdjyLsjlRepo;
private readonly IRepository _xdjyRecordRepo;
private readonly IRepository _hbjyRepo;
private readonly IRepository _confGyzkRepo;
private readonly IRepository _confGzRepo;
private readonly IRepository _confDcgRepo;
private readonly IRepository _xdjkRepo;
private readonly IRepository _xzjgRepo;
private readonly IRepository _guigeRepo;
private readonly IRepository _ssdjzbRepo;
private readonly IRepository _zkRepo;
private readonly IRepository _depRepo;
private readonly IRepository _empRepo;
private readonly IRepository _wtRepo;
public CalcLdcctService(
IRepository repo,
IRepository dcgRepo,
IRepository ctgRepo,
IRepository zjrbRepo,
IRepository zjrbJizuRepo,
IRepository lsjlRepo,
IRepository cdsLsjlRepo,
IRepository cdsChetaiRepo,
IRepository xdjyLsjlRepo,
IRepository xdjyRecordRepo,
IRepository hbjyRepo,
IRepository confGyzkRepo,
IRepository confGzRepo,
IRepository confDcgRepo,
IRepository xdjkRepo,
IRepository xzjgRepo,
IRepository guigeRepo,
IRepository ssdjzbRepo,
IRepository zkRepo,
IRepository depRepo,
IRepository empRepo,
IRepository wtRepo)
{
_repo = repo; _dcgRepo = dcgRepo; _ctgRepo = ctgRepo;
_zjrbRepo = zjrbRepo; _zjrbJizuRepo = zjrbJizuRepo; _lsjlRepo = lsjlRepo;
_cdsLsjlRepo = cdsLsjlRepo; _cdsChetaiRepo = cdsChetaiRepo;
_xdjyLsjlRepo = xdjyLsjlRepo; _xdjyRecordRepo = xdjyRecordRepo; _hbjyRepo = hbjyRepo;
_confGyzkRepo = confGyzkRepo; _confGzRepo = confGzRepo; _confDcgRepo = confDcgRepo;
_xdjkRepo = xdjkRepo; _xzjgRepo = xzjgRepo; _guigeRepo = guigeRepo; _ssdjzbRepo = ssdjzbRepo;
_zkRepo = zkRepo; _depRepo = depRepo; _empRepo = empRepo; _wtRepo = wtRepo;
}
// ==================== 常量(对齐 F9 Page_CalcLdcct) ====================
private const decimal XushuPerTai = 20m; // 每台绪数
private const decimal CtgTaiShu = 20m; // 车头工台时按 20 台/组
private const decimal CtgHoursDiv = 9m; // 车头基础单价 /9(标准上班时长)
private const decimal CtgRenShu = 2m; // 车头标准人数 2
private const decimal DengjiStep = 0.1m; // 等级分步长
private const int SaozheDeadZone = 2; // 缫折死区 ±2kg
private const int DcgSaozheCap = 10; // 挡车缫折封顶 ±10
private const int CtgSaozheCap = 3; // 车头缫折封顶 ±3
private const string PriceGroup = "薪资.前缫";
private const string DcgDengjiPriceKey = "挡车工计件等级奖扣单价";
private const string CtgDengjiPriceKey = "车头工计件等级奖扣单价";
// ==================== 查询 ====================
public async Task> GetPagedAsync(int pageIndex, int pageSize, DateTime start,
DateTime end, long? depId, long? worktypeId, string like)
{
var all = await _repo.GetAllAsync();
var from = start.Date;
var to = end.Date;
var q = all.AsQueryable().Where(x => x.Date.Date >= from && x.Date.Date <= to);
if (depId.HasValue) q = q.Where(x => x.DepId == depId.Value);
if (worktypeId.HasValue) q = q.Where(x => x.WorktypeId == worktypeId.Value);
var deps = await _depRepo.GetAllAsync();
var emps = await _empRepo.GetAllAsync();
var wts = await _wtRepo.GetAllAsync();
var zks = await _zkRepo.GetAllAsync();
var confGyzks = await _confGyzkRepo.GetAllAsync();
var depDict = deps.ToDictionary(d => d.Id, d => d.Name);
var empDict = emps.ToDictionary(e => e.Id, e => e.Name);
var wtDict = wts.ToDictionary(w => w.Id, w => w.WorkName);
var zkDict = zks.ToDictionary(z => z.Id, z => z.ZhuangKouName);
var confDict = confGyzks.GroupBy(c => $"{c.Year}-{c.Month}-{c.GyzkId}")
.ToDictionary(g => g.Key, g => g.OrderByDescending(c => c.Id).First());
if (!string.IsNullOrWhiteSpace(like))
{
var empIds = emps.Where(e => e.Name.Contains(like)).Select(e => e.Id).ToHashSet();
var zkIds = zks.Where(z => z.ZhuangKouName.Contains(like)).Select(z => z.Id).ToHashSet();
q = q.Where(x => empIds.Contains(x.EmployeeId) || zkIds.Contains(x.GyzkId));
}
var total = q.LongCount();
var items = q.OrderBy(x => x.Date).ThenBy(x => x.Jizu).ThenBy(x => x.Banci).ThenBy(x => x.EmployeeId)
.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
return new PageResult
{
Items = items.Select(x =>
{
confDict.TryGetValue($"{x.Date.Year}-{x.Date.Month}-{x.GyzkId}", out var cfg);
return new CalcLdcctListItemDto
{
Id = x.Id, Date = x.Date, EmployeeId = x.EmployeeId,
EmployeeName = empDict.GetValueOrDefault(x.EmployeeId, ""),
DepId = x.DepId, DepName = depDict.GetValueOrDefault(x.DepId, ""),
WorktypeId = x.WorktypeId, WorktypeName = wtDict.GetValueOrDefault(x.WorktypeId, ""),
GyzkId = x.GyzkId, GyzkName = zkDict.GetValueOrDefault(x.GyzkId, ""),
Guige = x.Guige, Chetai = x.Chetai, Jizu = x.Jizu, Banci = x.Banci,
BanciName = BanciName(x.Banci),
HoursKantai = x.HoursKantai, Dangcheshu = x.Dangcheshu,
Dengji = x.Dengji, Dengjifen = x.Dengjifen,
GyzkTaichan = cfg?.QsTaichan ?? 0, GyzkSaozhe = cfg?.SaoZhe ?? 0,
ClDanjiaBase = x.ClDanjiaBase, ClDanjiaDengji = x.ClDanjiaDengji,
ClDanjiaActual = x.ClDanjiaActual, ClSiliang = x.ClSiliang,
ZlSaozhe = x.ZlSaozhe, ZlCenterxd = x.ZlCenterxd, ZlYexiannum = x.ZlYexiannum,
ZlQingjie = x.ZlQingjie, ZlJiejing = x.ZlJiejing, ZlErdu = x.ZlErdu, ZlSandu = x.ZlSandu,
JineCl_Original = x.JineCl_Original, Xishu_JineCl = x.Xishu_JineCl, JineCl = x.JineCl,
JineZlSaozhe = x.JineZlSaozhe, JineZlCenterxd = x.JineZlCenterxd, JineZlYexian = x.JineZlYexian,
JineZlQingjie = x.JineZlQingjie, JineZlJiejing = x.JineZlJiejing,
JineZlErdu = x.JineZlErdu, JineZlSandu = x.JineZlSandu,
JineZlTotal = x.JineZlSaozhe + x.JineZlCenterxd + x.JineZlYexian + x.JineZlQingjie
+ x.JineZlJiejing + x.JineZlErdu + x.JineZlSandu
};
}).ToList(),
Total = total, PageIndex = pageIndex, PageSize = pageSize
};
}
// ==================== 一键计算:挡车工 ====================
public async Task CalcDcgAsync(CalcLdcctCalcDto dto)
{
var ctx = await BuildContextAsync(dto);
var result = new CalcLdcctResultDto();
if (ctx.DcgKtjl.Count == 0)
{
result.Warnings.Add("该账期该部门没有挡车工看台记录");
return result;
}
var rows = new List();
// 按「日 + 机组」组织
var jizuDays = ctx.DcgKtjl.GroupBy(k => (k.Date.Date, k.Jizu));
foreach (var jd in jizuDays)
{
var (date, jizu) = jd.Key;
var ktjls = jd.Where(k => !k.NoChanliang).ToList();
if (ktjls.Count == 0) continue;
// 该机组当日各庄口的今缫桶数(煮茧日报不分班)
var gyzkTongshu = ctx.GyzkTongshuByJizuDay.TryGetValue((date, jizu), out var gt)
? gt : new List<(long GyzkId, string Guige, int Tongshu, int StdLuosi)>();
var tongshuSum = gyzkTongshu.Sum(g => g.Tongshu);
var banciNum = ktjls.Select(k => k.Banci).Distinct().Count();
var half = banciNum >= 2 ? 0.5m : 1m;
foreach (var (gyzkId, guige, tongshu, stdLuosi) in gyzkTongshu)
{
var cfg = ctx.ConfGyzk(gyzkId, date);
if (cfg == null)
{
result.Warnings.Add($"{date:yyyy-MM-dd} 机组 {jizu} 庄口缺少薪资配置,已跳过");
continue;
}
var guigeCfg = ctx.Guige(guige);
// 每桶丝量 = 每车丝量(kg→g) / 标准落丝桶数
if (stdLuosi <= 0 || cfg.MeiCheSiLiang <= 0)
{
result.Warnings.Add($"{date:yyyy-MM-dd} 庄口 {cfg.Guige} 缺少每车丝量/标准落丝桶数,已跳过");
continue;
}
var meitongSiliang = cfg.MeiCheSiLiang * 1000m / stdLuosi;
var jiayiSumSiliang = Math.Round(tongshu * meitongSiliang, 0, MidpointRounding.AwayFromZero);
var rate = tongshuSum > 0 ? (decimal)tongshu / tongshuSum : 0m;
// 按班次拆分丝量:按 台时 = 挡车数 × 时长 加权
var jizuDayTaishi = ktjls.Sum(k => k.DangcheNum * k.Hours);
foreach (var banciGroup in ktjls.GroupBy(k => k.Banci))
{
var banciTaishi = banciGroup.Sum(k => k.DangcheNum * k.Hours);
var banciSiliang = jizuDayTaishi > 0
? Math.Round(jiayiSumSiliang * (banciTaishi / jizuDayTaishi), 0, MidpointRounding.AwayFromZero)
: 0m;
var groupTaishi = banciTaishi;
foreach (var k in banciGroup)
{
// 看台时长:按庄口今缫桶数占比分摊个人工时
var hoursKantai = Math.Round(k.Hours * rate, 1, MidpointRounding.AwayFromZero);
var siliang = groupTaishi > 0
? Math.Round(banciSiliang * (k.DangcheNum * k.Hours / groupTaishi), 0, MidpointRounding.AwayFromZero)
: 0m;
// ---- 质量(按车台看台比例分摊) ----
var quality = CalcDcgQuality(ctx, date, jizu, gyzkId, k, half, cfg, guigeCfg);
// ---- 等级(木桶短板) ----
var (dengji, dengjifen) = CalDengJi(ctx, guige, quality.Junfangcha, quality.Zongcha,
quality.QingjieFen, quality.JiejingFen, quality.Erdu, quality.Sandu, cfg);
// ---- 单价 ----
var xushu = k.DangcheNum * XushuPerTai;
var gangweiBase = GetDcgDanJiaBase(xushu, ctx.ConfDcg);
if (ctx.ConfGz == null || ctx.ConfGz.QqDaysStandard <= 0 || ctx.ConfGz.HoursPerday <= 0
|| cfg.QsTaichan <= 0 || k.DangcheNum <= 0)
{
result.Warnings.Add($"工种薪资配置或庄口台产缺失,{date:yyyy-MM-dd} {k.EmployeeId} 已跳过");
continue;
}
var danjiaBase = Math.Round(gangweiBase / ctx.ConfGz.QqDaysStandard / k.DangcheNum
/ ctx.ConfGz.HoursPerday / cfg.QsTaichan, 7, MidpointRounding.AwayFromZero);
if (danjiaBase == 0) continue;
var danjiaDengji = Math.Round(
(dengjifen - cfg.QsDengjifen) / DengjiStep * ctx.DcgJjDengJiJkPrice / 10m / 1000m,
7, MidpointRounding.AwayFromZero);
var danjiaActual = Math.Round(danjiaBase + danjiaDengji, 7, MidpointRounding.AwayFromZero);
// ---- 金额 ----
var e = new CalcLdcct
{
Date = date, EmployeeId = k.EmployeeId, DepId = dto.DepId, WorktypeId = dto.WorktypeId,
Jizu = jizu, Banci = (int)k.Banci, GyzkId = gyzkId, Guige = guige,
Chetai = k.ChetaiStart == k.ChetaiEnd ? k.ChetaiStart.ToString() : $"{k.ChetaiStart}-{k.ChetaiEnd}",
HoursKantai = hoursKantai, Dangcheshu = k.DangcheNum, Dengji = dengji, Dengjifen = dengjifen,
ClDanjiaBase = danjiaBase, ClDanjiaDengji = danjiaDengji, ClDanjiaActual = danjiaActual,
ClSiliang = siliang,
ZlSaozhe = quality.Saozhe, ZlCenterxd = quality.CenterNum, ZlYexiannum = quality.YexianNum,
ZlQingjie = quality.QingjiePianshu, ZlJiejing = quality.JiejingPianshu,
ZlErdu = quality.Erdu, ZlSandu = quality.Sandu,
Xishu_JineCl = cfg.DcgJijianXishu
};
e.JineCl_Original = Math.Round(danjiaActual * siliang, 2, MidpointRounding.AwayFromZero);
e.JineCl = Math.Round(danjiaActual * siliang * cfg.DcgJijianXishu, 2, MidpointRounding.AwayFromZero);
e.JineZlSaozhe = CalJineSaozhe(cfg.SaoZhe, quality.Saozhe, siliang, DcgSaozheCap, 5m);
e.JineZlCenterxd = Math.Round(quality.CenterNum * 1m, 2, MidpointRounding.AwayFromZero);
e.JineZlYexian = -Math.Round(quality.YexianKouKuan, 2, MidpointRounding.AwayFromZero);
e.JineZlQingjie = -Math.Round(quality.QingjieKouKuan, 2, MidpointRounding.AwayFromZero);
e.JineZlJiejing = -Math.Round(quality.JiejingKouKuan, 2, MidpointRounding.AwayFromZero);
e.JineZlErdu = -Math.Round(quality.Erdu * 2m, 2, MidpointRounding.AwayFromZero);
e.JineZlSandu = -Math.Round(quality.Sandu * 20m, 2, MidpointRounding.AwayFromZero);
rows.Add(e);
}
}
}
}
return await PersistAsync(rows, dto, result);
}
// ==================== 一键计算:车头工 ====================
public async Task CalcCtgAsync(CalcLdcctCalcDto dto)
{
var ctx = await BuildContextAsync(dto);
var result = new CalcLdcctResultDto();
if (ctx.CtgKtjl.Count == 0)
{
result.Warnings.Add("该账期该部门没有车头工看台记录");
return result;
}
// 车头工强依赖已存的挡车工产量
var dcgRows = (await _repo.GetAllAsync())
.Where(x => x.Date.Date >= dto.StartDate.Date && x.Date.Date <= dto.EndDate.Date
&& x.DepId == dto.DepId && x.WorktypeId == ctx.DcgWorktypeId).ToList();
var rows = new List();
var jizuDays = ctx.CtgKtjl.GroupBy(k => (k.Date.Date, k.Jizu));
foreach (var jd in jizuDays)
{
var (date, jizu) = jd.Key;
var ktjls = jd.Where(k => !k.NoChanliang).ToList();
if (ktjls.Count == 0) continue;
var gyzkTongshu = ctx.GyzkTongshuByJizuDay.TryGetValue((date, jizu), out var gt)
? gt : new List<(long GyzkId, string Guige, int Tongshu, int StdLuosi)>();
var tongshuSum = gyzkTongshu.Sum(g => g.Tongshu);
var banciNum = ktjls.Select(k => k.Banci).Distinct().Count();
var half = banciNum >= 2 ? 0.5m : 1m;
foreach (var (gyzkId, guige, tongshu, stdLuosi) in gyzkTongshu)
{
var cfg = ctx.ConfGyzk(gyzkId, date);
if (cfg == null)
{
result.Warnings.Add($"{date:yyyy-MM-dd} 机组 {jizu} 庄口缺少薪资配置,已跳过");
continue;
}
if (stdLuosi <= 0 || cfg.MeiCheSiLiang <= 0)
{
result.Warnings.Add($"{date:yyyy-MM-dd} 庄口 {cfg.Guige} 缺少每车丝量/标准落丝桶数,已跳过");
continue;
}
var meitongSiliang = cfg.MeiCheSiLiang * 1000m / stdLuosi;
var jiayiSumSiliang = Math.Round(tongshu * meitongSiliang, 0, MidpointRounding.AwayFromZero);
var rate = tongshuSum > 0 ? (decimal)tongshu / tongshuSum : 0m;
// 全组汇总质量(落丝主表,不分摊)
var lsjlIds = ctx.LsjlIdsByJizuDayGyzk.TryGetValue((date, jizu, gyzkId), out var ids)
? ids : new List();
var saozheList = new List();
var erduSum = 0m; var sanduSum = 0m;
foreach (var lid in lsjlIds)
{
if (ctx.CdsLsjl.TryGetValue(lid, out var cds) && cds.Siliang > 0 && cds.Jianliang > 0)
saozheList.Add((decimal)cds.Jianliang * 100m / cds.Siliang);
if (ctx.HbjyByLsjl.TryGetValue(lid, out var hb))
{
erduSum += hb.Sum(h => h.YdErdu);
sanduSum += hb.Sum(h => h.YdSandu);
}
}
var zlSaozhe = saozheList.Count > 0
? Math.Round(saozheList.Average(), 0, MidpointRounding.AwayFromZero) : cfg.SaoZhe;
var jizuDayTaishi = ktjls.Sum(k => CtgTaiShu * k.Hours);
foreach (var banciGroup in ktjls.GroupBy(k => k.Banci))
{
var banciTaishi = banciGroup.Sum(k => CtgTaiShu * k.Hours);
var banciSiliang = jizuDayTaishi > 0
? Math.Round(jiayiSumSiliang * (banciTaishi / jizuDayTaishi), 0, MidpointRounding.AwayFromZero)
: 0m;
var maxHours = banciGroup.Max(k => k.Hours);
foreach (var k in banciGroup)
{
var hoursKantai = Math.Round(k.Hours * rate, 1, MidpointRounding.AwayFromZero);
var siliang = maxHours > 0
? Math.Round(banciSiliang * (k.Hours / (maxHours * CtgRenShu)), 0, MidpointRounding.AwayFromZero)
: 0m;
// 等级:按挡车工丝量加权平均等级分再回落取整档
var (dengji, dengjifen) = CalCtgDengji(ctx, dcgRows, date, jizu, gyzkId, guige, cfg);
if (ctx.ConfGz == null || ctx.ConfGz.QqDaysStandard <= 0 || cfg.QsTaichan <= 0)
{
result.Warnings.Add($"工种薪资配置缺失,{date:yyyy-MM-dd} {k.EmployeeId} 已跳过");
continue;
}
var danjiaBase = Math.Round(ctx.ConfDcg.JineCtg / ctx.ConfGz.QqDaysStandard / CtgHoursDiv
/ CtgTaiShu / cfg.QsTaichan * CtgRenShu, 7, MidpointRounding.AwayFromZero);
if (danjiaBase == 0) continue;
var danjiaDengji = Math.Round(
(dengjifen - cfg.QsDengjifen) / DengjiStep * ctx.CtgJjDengJiJkPrice / 10m / 1000m,
7, MidpointRounding.AwayFromZero);
var danjiaActual = Math.Round(danjiaBase + danjiaDengji, 7, MidpointRounding.AwayFromZero);
var e = new CalcLdcct
{
Date = date, EmployeeId = k.EmployeeId, DepId = dto.DepId, WorktypeId = dto.WorktypeId,
Jizu = jizu, Banci = (int)k.Banci, GyzkId = gyzkId, Guige = guige, Chetai = "",
HoursKantai = hoursKantai, Dangcheshu = 0, Dengji = dengji, Dengjifen = dengjifen,
ClDanjiaBase = danjiaBase, ClDanjiaDengji = danjiaDengji, ClDanjiaActual = danjiaActual,
ClSiliang = siliang,
ZlSaozhe = zlSaozhe, ZlCenterxd = 0, ZlYexiannum = 0, ZlQingjie = 0, ZlJiejing = 0,
ZlErdu = Math.Round(erduSum * half, 2, MidpointRounding.AwayFromZero),
ZlSandu = Math.Round(sanduSum * half, 2, MidpointRounding.AwayFromZero),
Xishu_JineCl = cfg.CtgJijianXishu
};
e.JineCl_Original = Math.Round(danjiaActual * siliang, 2, MidpointRounding.AwayFromZero);
e.JineCl = Math.Round(danjiaActual * siliang * cfg.CtgJijianXishu, 2, MidpointRounding.AwayFromZero);
e.JineZlSaozhe = CalJineSaozhe(cfg.SaoZhe, zlSaozhe, siliang, CtgSaozheCap, 1m);
e.JineZlCenterxd = 0; e.JineZlYexian = 0; e.JineZlQingjie = 0; e.JineZlJiejing = 0;
e.JineZlErdu = -Math.Round(e.ZlErdu * 2m, 2, MidpointRounding.AwayFromZero);
e.JineZlSandu = -Math.Round(e.ZlSandu * 20m, 2, MidpointRounding.AwayFromZero);
rows.Add(e);
}
}
}
}
return await PersistAsync(rows, dto, result);
}
// ==================== 保存 / 删除 ====================
public async Task SaveAsync(CalcLdcctSaveDto dto)
{
var olds = (await _repo.GetAllAsync())
.Where(x => x.Date.Date >= dto.StartDate.Date && x.Date.Date <= dto.EndDate.Date
&& x.DepId == dto.DepId && x.WorktypeId == dto.WorktypeId).ToList();
if (olds.Count > 0) await _repo.HardDeleteRangeAsync(olds);
foreach (var it in dto.Items)
{
var e = new CalcLdcct
{
Date = it.Date.Date, EmployeeId = it.EmployeeId, DepId = it.DepId, WorktypeId = it.WorktypeId,
Jizu = it.Jizu, Banci = it.Banci, GyzkId = it.GyzkId, Guige = it.Guige ?? "", Chetai = it.Chetai ?? "",
HoursKantai = it.HoursKantai, Dangcheshu = it.Dangcheshu, Dengji = it.Dengji ?? "", Dengjifen = it.Dengjifen,
ClDanjiaBase = it.ClDanjiaBase, ClDanjiaDengji = it.ClDanjiaDengji, ClDanjiaActual = it.ClDanjiaActual,
ClSiliang = it.ClSiliang,
ZlSaozhe = it.ZlSaozhe, ZlCenterxd = it.ZlCenterxd, ZlYexiannum = it.ZlYexiannum,
ZlQingjie = it.ZlQingjie, ZlJiejing = it.ZlJiejing, ZlErdu = it.ZlErdu, ZlSandu = it.ZlSandu,
JineCl_Original = it.JineCl_Original, Xishu_JineCl = it.Xishu_JineCl, JineCl = it.JineCl,
JineZlSaozhe = it.JineZlSaozhe, JineZlCenterxd = it.JineZlCenterxd, JineZlYexian = it.JineZlYexian,
JineZlQingjie = it.JineZlQingjie, JineZlJiejing = it.JineZlJiejing,
JineZlErdu = it.JineZlErdu, JineZlSandu = it.JineZlSandu
};
await _repo.AddAsync(e);
}
return dto.Items.Count;
}
public async Task DeleteAsync(DateTime start, DateTime end, long depId, long worktypeId)
{
var olds = (await _repo.GetAllAsync())
.Where(x => x.Date.Date >= start.Date && x.Date.Date <= end.Date
&& x.DepId == depId && x.WorktypeId == worktypeId).ToList();
if (olds.Count > 0) await _repo.HardDeleteRangeAsync(olds);
}
// ==================== 内部:上下文 ====================
private class CalcContext
{
public List DcgKtjl = new();
public List CtgKtjl = new();
public long DcgWorktypeId;
public Dictionary<(DateTime, int), List<(long GyzkId, string Guige, int Tongshu, int StdLuosi)>> GyzkTongshuByJizuDay = new();
public Dictionary<(DateTime, int, long), List> LsjlIdsByJizuDayGyzk = new();
public Dictionary CdsLsjl = new();
public Dictionary> CdsChetai = new();
public Dictionary> XdjyByLsjl = new();
public Dictionary> HbjyByLsjl = new();
public Dictionary> ConfByGyzk = new();
public Dictionary GuigeDict = new();
public Dictionary> YeXianJk = new();
public List Djzb = new();
public ConfGz ConfGz = null!;
public ConfDcg ConfDcg = null!;
public decimal DcgJjDengJiJkPrice;
public decimal CtgJjDengJiJkPrice;
public List Warnings = new();
public ConfGyzk ConfGyzk(long gyzkId, DateTime date) =>
ConfByGyzk.TryGetValue(gyzkId, out var list)
? list.FirstOrDefault(c => c.Year == date.Year && c.Month == date.Month)
?? list.OrderByDescending(c => c.Year).ThenByDescending(c => c.Month).FirstOrDefault()
: null;
public ShengsiGuige Guige(string guige) =>
GuigeDict.TryGetValue(guige ?? "", out var g) ? g : null;
public List YeXianJiangKou(string guige) =>
YeXianJk.TryGetValue(guige ?? "", out var l) ? l : new List();
}
private async Task BuildContextAsync(CalcLdcctCalcDto dto)
{
var ctx = new CalcContext();
var from = dto.StartDate.Date;
var to = dto.EndDate.Date;
ctx.DcgKtjl = (await _dcgRepo.GetAllAsync())
.Where(x => x.Date.Date >= from && x.Date.Date <= to && x.DeptId == dto.DepId).ToList();
ctx.CtgKtjl = (await _ctgRepo.GetAllAsync())
.Where(x => x.Date.Date >= from && x.Date.Date <= to && x.DeptId == dto.DepId).ToList();
// 挡车工工种:取看台记录中出现最多的工种(车头计算需按挡车工工种取已存产量)
ctx.DcgWorktypeId = ctx.DcgKtjl.GroupBy(k => k.WorktypeId).OrderByDescending(g => g.Count())
.Select(g => g.Key).FirstOrDefault();
// 煮茧日报:日 + 机组 → 各庄口今缫桶数(不分班)
var zjrbs = (await _zjrbRepo.GetAllAsync())
.Where(x => x.Date.Date >= from && x.Date.Date <= to && x.DeptId == dto.DepId).ToList();
var zjrbJizus = await _zjrbJizuRepo.GetAllAsync();
foreach (var z in zjrbs)
{
foreach (var jz in zjrbJizus.Where(j => j.ZjrbId == z.Id))
{
var key = (z.Date.Date, jz.Jizu);
if (!ctx.GyzkTongshuByJizuDay.TryGetValue(key, out var list))
ctx.GyzkTongshuByJizuDay[key] = list = new List<(long, string, int, int)>();
list.Add((z.GyzkId, z.Guige, jz.TongshuJinsao, z.StandardLuositongshu));
}
}
// 落丝记录:日 + 机组 + 庄口 → lsjlId 列表
var lsjls = (await _lsjlRepo.GetAllAsync())
.Where(x => x.WorkDate.Date >= from && x.WorkDate.Date <= to && x.DeptId == dto.DepId).ToList();
foreach (var l in lsjls)
{
var key = (l.WorkDate.Date, l.Jizu, l.GyzkId);
if (!ctx.LsjlIdsByJizuDayGyzk.TryGetValue(key, out var list))
ctx.LsjlIdsByJizuDayGyzk[key] = list = new List();
list.Add(l.Id);
}
var allLsjlIds = lsjls.Select(l => l.Id).ToHashSet();
ctx.CdsLsjl = (await _cdsLsjlRepo.GetAllAsync()).Where(x => allLsjlIds.Contains(x.LsjlId))
.GroupBy(x => x.LsjlId).ToDictionary(g => g.Key, g => g.First());
ctx.CdsChetai = (await _cdsChetaiRepo.GetAllAsync()).Where(x => allLsjlIds.Contains(x.LsjlId))
.GroupBy(x => x.LsjlId).ToDictionary(g => g.Key, g => g.ToList());
ctx.XdjyByLsjl = (await _xdjyRecordRepo.GetAllAsync()).Where(x => allLsjlIds.Contains(x.LsjlId))
.GroupBy(x => x.LsjlId).ToDictionary(g => g.Key, g => g.ToList());
ctx.HbjyByLsjl = (await _hbjyRepo.GetAllAsync()).Where(x => allLsjlIds.Contains(x.LsjlId))
.GroupBy(x => x.LsjlId).ToDictionary(g => g.Key, g => g.ToList());
_ = await _xdjyLsjlRepo.GetAllAsync(); // 纤度汇总(等级参考,保留实体引用)
ctx.ConfByGyzk = (await _confGyzkRepo.GetAllAsync())
.GroupBy(c => c.GyzkId).ToDictionary(g => g.Key, g => g.ToList());
ctx.GuigeDict = (await _guigeRepo.GetAllAsync())
.GroupBy(g => g.Guige).ToDictionary(g => g.Key, g => g.First());
ctx.Djzb = (await _ssdjzbRepo.GetAllAsync()).Where(x => x.Standard == 2).ToList(); // 2=个人
ctx.YeXianJk = (await _xdjkRepo.GetAllAsync())
.GroupBy(x => x.GuiGe).ToDictionary(g => g.Key, g => g.OrderBy(x => x.XianDu).ToList());
ctx.ConfGz = await _confGzRepo.FirstOrDefaultAsync(x => x.WorkTypeId == dto.WorktypeId);
ctx.ConfDcg = await _confDcgRepo.FirstOrDefaultAsync(x => x.DepId == dto.DepId)
?? new ConfDcg { DepId = dto.DepId };
ctx.DcgJjDengJiJkPrice = await PriceAsync(DcgDengjiPriceKey);
ctx.CtgJjDengJiJkPrice = await PriceAsync(CtgDengjiPriceKey);
return ctx;
}
private async Task PriceAsync(string key)
{
var e = await _xzjgRepo.FirstOrDefaultAsync(x => x.Group == PriceGroup && x.Key == key);
return e?.Price ?? 0m;
}
private async Task PersistAsync(List rows, CalcLdcctCalcDto dto, CalcLdcctResultDto result)
{
await SaveAsync(new CalcLdcctSaveDto
{
StartDate = dto.StartDate, EndDate = dto.EndDate, DepId = dto.DepId, WorktypeId = dto.WorktypeId,
Items = rows.Select(r => new CalcLdcctSaveItemDto
{
Date = r.Date, EmployeeId = r.EmployeeId, DepId = r.DepId, WorktypeId = r.WorktypeId,
Jizu = r.Jizu, Banci = r.Banci, GyzkId = r.GyzkId, Guige = r.Guige, Chetai = r.Chetai,
HoursKantai = r.HoursKantai, Dangcheshu = r.Dangcheshu, Dengji = r.Dengji, Dengjifen = r.Dengjifen,
ClDanjiaBase = r.ClDanjiaBase, ClDanjiaDengji = r.ClDanjiaDengji, ClDanjiaActual = r.ClDanjiaActual,
ClSiliang = r.ClSiliang,
ZlSaozhe = r.ZlSaozhe, ZlCenterxd = r.ZlCenterxd, ZlYexiannum = r.ZlYexiannum,
ZlQingjie = r.ZlQingjie, ZlJiejing = r.ZlJiejing, ZlErdu = r.ZlErdu, ZlSandu = r.ZlSandu,
JineCl_Original = r.JineCl_Original, Xishu_JineCl = r.Xishu_JineCl, JineCl = r.JineCl,
JineZlSaozhe = r.JineZlSaozhe, JineZlCenterxd = r.JineZlCenterxd, JineZlYexian = r.JineZlYexian,
JineZlQingjie = r.JineZlQingjie, JineZlJiejing = r.JineZlJiejing,
JineZlErdu = r.JineZlErdu, JineZlSandu = r.JineZlSandu
}).ToList()
});
result.Count = rows.Count;
result.SumSiliang = rows.Sum(r => r.ClSiliang);
result.SumJineCl = Math.Round(rows.Sum(r => r.JineCl), 2, MidpointRounding.AwayFromZero);
result.SumJineZl = Math.Round(rows.Sum(r => r.JineZlSaozhe + r.JineZlCenterxd + r.JineZlYexian
+ r.JineZlQingjie + r.JineZlJiejing + r.JineZlErdu + r.JineZlSandu), 2, MidpointRounding.AwayFromZero);
return result;
}
// ==================== 内部:挡车工质量 ====================
private class DcgQuality
{
public decimal Saozhe;
public decimal CenterNum;
public decimal YexianNum;
public decimal YexianKouKuan;
public decimal QingjiePianshu;
public decimal QingjieKouKuan;
public decimal JiejingPianshu;
public decimal JiejingKouKuan;
public decimal Erdu;
public decimal Sandu;
public double Junfangcha;
public double Zongcha;
public decimal QingjieFen;
public decimal JiejingFen;
}
private DcgQuality CalcDcgQuality(CalcContext ctx, DateTime date, int jizu, long gyzkId, KtjlDcg k,
decimal half, ConfGyzk cfg, ShengsiGuige guigeCfg)
{
var q = new DcgQuality();
var lsjlIds = ctx.LsjlIdsByJizuDayGyzk.TryGetValue((date, jizu, gyzkId), out var ids)
? ids : new List();
decimal jianliangNum = 0m, siliangDen = 0m;
double junfangchaSum = 0, zongchaSum = 0;
int junfangchaCnt = 0;
decimal erduSum = 0m, sanduSum = 0m;
decimal qjTdc = 0m, qjDc = 0m, qjCj = 0m;
decimal jj85 = 0m, jj80 = 0m, jj75 = 0m, jj70 = 0m;
decimal pianshuSum = 0m, qingjieDeduct = 0m, jiejingDeduct = 0m;
foreach (var lid in lsjlIds)
{
var chetais = ctx.CdsChetai.TryGetValue(lid, out var ct) ? ct : new List();
var cdsSum = ctx.CdsLsjl.TryGetValue(lid, out var cs) ? cs : null;
var xdjy = ctx.XdjyByLsjl.TryGetValue(lid, out var xd) ? xd : new List();
var hbjy = ctx.HbjyByLsjl.TryGetValue(lid, out var hb) ? hb : new List();
foreach (var ctItem in chetais)
{
if (ctItem.Chetaihao < k.ChetaiStart || ctItem.Chetaihao > k.ChetaiEnd) continue;
var rate = ChetaiRate(ctx, date, jizu, ctItem.Chetaihao, k);
// 缫折:茧量只记在落丝层,按「车台丝量占落丝丝量的比例」摊到车台,再看台比例摊到人
var lsjlSiliang = cdsSum?.Siliang ?? 0;
var lsjlJianliang = cdsSum?.Jianliang ?? 0;
var chetaiShare = lsjlSiliang > 0 ? (decimal)ctItem.Siliang / lsjlSiliang : 0m;
jianliangNum += lsjlJianliang * chetaiShare * rate;
siliangDen += ctItem.Siliang * rate;
// 中心纤度 / 野纤(纤度检验明细)
foreach (var x in xdjy.Where(x => x.Chetaihao == ctItem.Chetaihao))
{
var xds = new[] { x.Xd1, x.Xd2, x.Xd3, x.Xd4 }.Where(v => v > 0).ToArray();
if (guigeCfg != null)
{
q.CenterNum += xds.Count(v => Math.Abs(v - guigeCfg.CenterXiandu) < 0.0001) * rate;
q.YexianNum += xds.Count(v => v <= guigeCfg.XiaoyeLolimit || v >= guigeCfg.XiaoyeUplimit) * rate;
}
if (xds.Length > 0)
{
var avg = xds.Average();
var jfc = Math.Sqrt(xds.Sum(v => (v - avg) * (v - avg)) / xds.Length);
junfangchaSum += jfc; junfangchaCnt++;
var n = Math.Max(1, (int)Math.Ceiling(xds.Length * 0.02));
var sorted = xds.OrderBy(v => v).ToArray();
var minAvg = sorted.Take(n).Average();
var maxAvg = sorted.Skip(xds.Length - n).Average();
zongchaSum += Math.Max(Math.Abs(minAvg - avg), Math.Abs(maxAvg - avg));
}
}
// 黑板检验:二度/三度/清洁/洁净
foreach (var h in hbjy.Where(h => h.Chetaihao == ctItem.Chetaihao))
{
erduSum += h.YdErdu * rate;
sanduSum += h.YdSandu * rate;
qjTdc += h.QjZycdTdc * rate;
qjDc += h.QjCycdDc * rate;
qjCj += h.QjPtcdCj * rate;
jj85 += h.Jj85 * rate; jj80 += h.Jj80 * rate;
jj75 += h.Jj75 * rate; jj70 += h.Jj70 * rate;
pianshuSum += h.Pianshu * rate;
qingjieDeduct += (h.QjZycdTdc * 1.0m
+ (h.QjCycdFs + h.QjCycdDc + h.QjCycdNfc + h.QjCycdDcj + h.QjCycdZlx) * 0.4m
+ (h.QjPtcdXc + h.QjPtcdCj + h.QjPtcdLx + h.QjPtcdHjls) * 0.1m) * rate;
var jjCounts = new[] { h.Jj95, h.Jj90, h.Jj85, h.Jj80, h.Jj75, h.Jj70, h.Jj65, h.Jj60 };
for (var i = 0; i < jjCounts.Length; i++) jiejingDeduct += jjCounts[i] * (i + 1) * rate;
}
}
}
q.Saozhe = siliangDen > 0 ? Math.Round(jianliangNum * 100m / siliangDen, 0, MidpointRounding.AwayFromZero)
: cfg.SaoZhe;
q.Erdu = Math.Round(erduSum * half, 2, MidpointRounding.AwayFromZero);
q.Sandu = Math.Round(sanduSum * half, 2, MidpointRounding.AwayFromZero);
q.CenterNum = Math.Round(q.CenterNum * half, 2, MidpointRounding.AwayFromZero);
q.YexianNum = Math.Round(q.YexianNum * half, 2, MidpointRounding.AwayFromZero);
q.QingjiePianshu = Math.Round((qjTdc + qjDc + qjCj) * half, 2, MidpointRounding.AwayFromZero);
q.JiejingPianshu = Math.Round((jj85 + jj80 + jj75 + jj70) * half, 2, MidpointRounding.AwayFromZero);
q.QingjieKouKuan = (qjTdc * 2m + qjDc * 0.5m + qjCj * 2m) * half;
q.JiejingKouKuan = (jj85 * 0.2m + jj80 * 1m + jj75 * 2m + jj70 * 5m) * half;
q.YexianKouKuan = CalYeXianKouKuan(ctx, cfg.Guige, q.YexianNum);
q.Junfangcha = junfangchaCnt > 0 ? Math.Round(junfangchaSum / junfangchaCnt, 2) : 0;
q.Zongcha = junfangchaCnt > 0 ? Math.Round(zongchaSum / junfangchaCnt, 2) : 0;
q.QingjieFen = pianshuSum > 0
? Math.Round((pianshuSum - qingjieDeduct) * 100m / pianshuSum, 2, MidpointRounding.AwayFromZero) : 100m;
q.JiejingFen = pianshuSum > 0
? Math.Round((pianshuSum * 100m - jiejingDeduct * 5m) / pianshuSum, 2, MidpointRounding.AwayFromZero) : 100m;
return q;
}
/// 车台看台比例 = 本人该台工时 / 该台所有看台人工时之和
private decimal ChetaiRate(CalcContext ctx, DateTime date, int jizu, int chetaihao, KtjlDcg k)
{
var same = ctx.DcgKtjl.Where(x => x.Date.Date == date && x.Jizu == jizu && x.Banci == k.Banci
&& x.ChetaiStart <= chetaihao && x.ChetaiEnd >= chetaihao).ToList();
var sum = same.Sum(x => x.Hours);
return sum > 0 ? k.Hours / sum : 0m;
}
/// 野纤扣款:按规格取挡车纤度奖扣表,≤2 条取扣款最大的 2 条求和
private decimal CalYeXianKouKuan(CalcContext ctx, string guige, decimal yexianNum)
{
if (yexianNum <= 0) return 0m;
var list = ctx.YeXianJiangKou(guige);
if (list.Count == 0) return 0m;
var items = ExpandCount(yexianNum, list);
if (items.Count >= 2) return items.OrderByDescending(i => i.KouKuan).Take(2).Sum(i => i.KouKuan);
return items.Sum(i => i.KouKuan);
}
/// 按数量展开:整数部分逐条取,小数部分按最后一条比例折算
private static List ExpandCount(decimal count, List list)
{
var result = new List();
var intPart = (int)Math.Floor(count);
var frac = count - intPart;
for (var i = 0; i < intPart; i++) result.Add(list[i % list.Count]);
if (frac > 0) result.Add(list[intPart % list.Count]);
return result;
}
// ==================== 内部:等级 ====================
/// 挡车工等级:木桶短板法,六项各自定级后取最低分
private (string Dengji, decimal Dengjifen) CalDengJi(CalcContext ctx, string guige, double piancha,
double dacha, decimal qingjie, decimal jiejing, decimal erdu, decimal sandu, ConfGyzk cfg)
{
var zbs = ctx.Djzb.Where(z => z.Guige == guige).ToList();
if (zbs.Count == 0) return (cfg.QsDengji, cfg.QsDengjifen);
var candidates = new List();
AddBest(candidates, zbs.Where(z => z.Piancha >= piancha));
AddBest(candidates, zbs.Where(z => z.Dacha >= dacha));
AddBest(candidates, zbs.Where(z => z.Qingjie <= (double)qingjie));
AddBest(candidates, zbs.Where(z => z.Jiejing <= (double)jiejing));
AddBest(candidates, zbs.Where(z => z.Erdu >= (double)erdu));
AddBest(candidates, zbs.Where(z => z.Sandu >= (double)sandu));
if (candidates.Count == 0) return (cfg.QsDengji, cfg.QsDengjifen);
var worst = candidates.OrderBy(z => z.GradeScore).First();
return (worst.Grade, (decimal)worst.GradeScore);
}
private static void AddBest(List target, IEnumerable src)
{
var best = src.OrderByDescending(z => z.GradeScore).FirstOrDefault();
if (best != null) target.Add(best);
}
/// 车头工等级:按挡车工丝量加权平均等级分后回落取整档
private (string Dengji, decimal Dengjifen) CalCtgDengji(CalcContext ctx, List dcgRows,
DateTime date, int jizu, long gyzkId, string guige, ConfGyzk cfg)
{
var related = dcgRows.Where(x => x.Date.Date == date && x.Jizu == jizu && x.GyzkId == gyzkId).ToList();
var siliangSum = related.Sum(x => x.ClSiliang);
if (siliangSum <= 0) return (cfg.QsDengji, cfg.QsDengjifen);
var weighted = related.Sum(x => x.ClSiliang * x.Dengjifen) / siliangSum;
var zbs = ctx.Djzb.Where(z => z.Guige == guige && (decimal)z.GradeScore <= weighted)
.OrderByDescending(z => z.GradeScore).FirstOrDefault();
return zbs != null ? (zbs.Grade, (decimal)zbs.GradeScore) : (cfg.QsDengji, cfg.QsDengjifen);
}
// ==================== 内部:单价 / 金额 ====================
/// 按绪数档取岗位基数金额:<60→50档,<70→60档,<100→70档,否则 100 档
private static decimal GetDcgDanJiaBase(decimal xushu, ConfDcg conf)
{
if (xushu < 60m) return conf.JineJisuanjishu50;
if (xushu < 70m) return conf.JineJisuanjishu60;
if (xushu < 100m) return conf.JineJisuanjishu70;
return conf.JineJisuanjishu100;
}
/// 缫折考核:±2kg 死区不奖不扣,超出部分按封顶 clamp,每 1kg 每 100kg 丝 奖扣 price 元
private static decimal CalJineSaozhe(decimal saoZheCfg, decimal saozhe, decimal siliang, int cap, decimal price)
{
if (Math.Abs(saoZheCfg - saozhe) <= SaozheDeadZone) return 0m;
var diff = saoZheCfg - saozhe;
diff = diff < 0 ? diff + SaozheDeadZone : diff - SaozheDeadZone;
if (diff > cap) diff = cap;
if (diff < -cap) diff = -cap;
return Math.Round(diff * (siliang / 1000m / 100m) * price, 2, MidpointRounding.AwayFromZero);
}
private static string BanciName(int banci) => banci switch { 1 => "甲", 2 => "乙", _ => banci.ToString() };
}