- 新增 PayRoll(工资)、Wms(仓储)、Prod(车速/缫丝/考勤/丝片检测/丝片台账/纤检日报/质量日报) 模块 - 新增对应 Domain 实体、Application DTO/Service、Api Controller 与前端页面 - 移除旧 Attendance 考勤模块(Controller/Service/Entity/DTO 及 5 个前端页面),由 Prod/Kaoqin 取代 - 调整路由、仪表盘、Lsjl 流丝记录实体与服务 - 更新业务种子数据与 DbSeeder - 补充工作进度文档 14-27
820 lines
44 KiB
C#
820 lines
44 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 薪资计算-立缫挡车车头(挡车工 / 车头工 产量计算)
|
||
/// 对应 F9 WinClient\F9.Business.PayRoll\PAYROLL\CALCLDCCT(Page_CalcLdcct + Page_CalcLdcct_Edit)
|
||
/// 数据源:看台记录(挡车/车头)、煮茧日报(今缫桶数)、落丝记录(茧量)、秤大丝(丝量/缫折)、
|
||
/// 纤度检验(纤度明细/均方差/总差)、黑板检验(二度/三度/清洁/洁净)、生丝规格、生丝定级指标。
|
||
/// 配置:工艺庄口薪资配置、工种薪资配置、挡车岗位薪资配置、挡车纤度奖扣、薪资价格配置。
|
||
/// </summary>
|
||
public interface ICalcLdcctService
|
||
{
|
||
Task<PageResult<CalcLdcctListItemDto>> GetPagedAsync(int pageIndex, int pageSize, DateTime start, DateTime end,
|
||
long? depId, long? worktypeId, string like);
|
||
Task<CalcLdcctResultDto> CalcDcgAsync(CalcLdcctCalcDto dto);
|
||
Task<CalcLdcctResultDto> CalcCtgAsync(CalcLdcctCalcDto dto);
|
||
Task<int> SaveAsync(CalcLdcctSaveDto dto);
|
||
Task DeleteAsync(DateTime start, DateTime end, long depId, long worktypeId);
|
||
}
|
||
|
||
public class CalcLdcctService : ICalcLdcctService
|
||
{
|
||
private readonly IRepository<CalcLdcct> _repo;
|
||
private readonly IRepository<KtjlDcg> _dcgRepo;
|
||
private readonly IRepository<KtjlCtg> _ctgRepo;
|
||
private readonly IRepository<ZjrbRecord> _zjrbRepo;
|
||
private readonly IRepository<ZjrbJizu> _zjrbJizuRepo;
|
||
private readonly IRepository<LsjlRecord> _lsjlRepo;
|
||
private readonly IRepository<CdsLsjl> _cdsLsjlRepo;
|
||
private readonly IRepository<CdsChetai> _cdsChetaiRepo;
|
||
private readonly IRepository<XdjyLsjl> _xdjyLsjlRepo;
|
||
private readonly IRepository<XdjyRecord> _xdjyRecordRepo;
|
||
private readonly IRepository<HbjyRecord> _hbjyRepo;
|
||
private readonly IRepository<ConfGyzk> _confGyzkRepo;
|
||
private readonly IRepository<ConfGz> _confGzRepo;
|
||
private readonly IRepository<ConfDcg> _confDcgRepo;
|
||
private readonly IRepository<DangCheXianDuJiangKou> _xdjkRepo;
|
||
private readonly IRepository<XinZiJiaGePeiZhi> _xzjgRepo;
|
||
private readonly IRepository<ShengsiGuige> _guigeRepo;
|
||
private readonly IRepository<ShengsiDingjizhibiao> _ssdjzbRepo;
|
||
private readonly IRepository<CraftZhuangKou> _zkRepo;
|
||
private readonly IRepository<SysDept.Department> _depRepo;
|
||
private readonly IRepository<SysEmp.Employee> _empRepo;
|
||
private readonly IRepository<SysWt.WorkType> _wtRepo;
|
||
|
||
public CalcLdcctService(
|
||
IRepository<CalcLdcct> repo,
|
||
IRepository<KtjlDcg> dcgRepo,
|
||
IRepository<KtjlCtg> ctgRepo,
|
||
IRepository<ZjrbRecord> zjrbRepo,
|
||
IRepository<ZjrbJizu> zjrbJizuRepo,
|
||
IRepository<LsjlRecord> lsjlRepo,
|
||
IRepository<CdsLsjl> cdsLsjlRepo,
|
||
IRepository<CdsChetai> cdsChetaiRepo,
|
||
IRepository<XdjyLsjl> xdjyLsjlRepo,
|
||
IRepository<XdjyRecord> xdjyRecordRepo,
|
||
IRepository<HbjyRecord> hbjyRepo,
|
||
IRepository<ConfGyzk> confGyzkRepo,
|
||
IRepository<ConfGz> confGzRepo,
|
||
IRepository<ConfDcg> confDcgRepo,
|
||
IRepository<DangCheXianDuJiangKou> xdjkRepo,
|
||
IRepository<XinZiJiaGePeiZhi> xzjgRepo,
|
||
IRepository<ShengsiGuige> guigeRepo,
|
||
IRepository<ShengsiDingjizhibiao> ssdjzbRepo,
|
||
IRepository<CraftZhuangKou> zkRepo,
|
||
IRepository<SysDept.Department> depRepo,
|
||
IRepository<SysEmp.Employee> empRepo,
|
||
IRepository<SysWt.WorkType> 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<PageResult<CalcLdcctListItemDto>> 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<CalcLdcctListItemDto>
|
||
{
|
||
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<CalcLdcctResultDto> 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<CalcLdcct>();
|
||
|
||
// 按「日 + 机组」组织
|
||
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<CalcLdcctResultDto> 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<CalcLdcct>();
|
||
|
||
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<long>();
|
||
var saozheList = new List<decimal>();
|
||
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<int> 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<KtjlDcg> DcgKtjl = new();
|
||
public List<KtjlCtg> 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<long>> LsjlIdsByJizuDayGyzk = new();
|
||
public Dictionary<long, CdsLsjl> CdsLsjl = new();
|
||
public Dictionary<long, List<CdsChetai>> CdsChetai = new();
|
||
public Dictionary<long, List<XdjyRecord>> XdjyByLsjl = new();
|
||
public Dictionary<long, List<HbjyRecord>> HbjyByLsjl = new();
|
||
public Dictionary<long, List<ConfGyzk>> ConfByGyzk = new();
|
||
public Dictionary<string, ShengsiGuige> GuigeDict = new();
|
||
public Dictionary<string, List<DangCheXianDuJiangKou>> YeXianJk = new();
|
||
public List<ShengsiDingjizhibiao> Djzb = new();
|
||
public ConfGz ConfGz = null!;
|
||
public ConfDcg ConfDcg = null!;
|
||
public decimal DcgJjDengJiJkPrice;
|
||
public decimal CtgJjDengJiJkPrice;
|
||
public List<string> 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<DangCheXianDuJiangKou> YeXianJiangKou(string guige) =>
|
||
YeXianJk.TryGetValue(guige ?? "", out var l) ? l : new List<DangCheXianDuJiangKou>();
|
||
}
|
||
|
||
private async Task<CalcContext> 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<long>();
|
||
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<decimal> PriceAsync(string key)
|
||
{
|
||
var e = await _xzjgRepo.FirstOrDefaultAsync(x => x.Group == PriceGroup && x.Key == key);
|
||
return e?.Price ?? 0m;
|
||
}
|
||
|
||
private async Task<CalcLdcctResultDto> PersistAsync(List<CalcLdcct> 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<long>();
|
||
|
||
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<CdsChetai>();
|
||
var cdsSum = ctx.CdsLsjl.TryGetValue(lid, out var cs) ? cs : null;
|
||
var xdjy = ctx.XdjyByLsjl.TryGetValue(lid, out var xd) ? xd : new List<XdjyRecord>();
|
||
var hbjy = ctx.HbjyByLsjl.TryGetValue(lid, out var hb) ? hb : new List<HbjyRecord>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>车台看台比例 = 本人该台工时 / 该台所有看台人工时之和</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>野纤扣款:按规格取挡车纤度奖扣表,≤2 条取扣款最大的 2 条求和</summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>按数量展开:整数部分逐条取,小数部分按最后一条比例折算</summary>
|
||
private static List<DangCheXianDuJiangKou> ExpandCount(decimal count, List<DangCheXianDuJiangKou> list)
|
||
{
|
||
var result = new List<DangCheXianDuJiangKou>();
|
||
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;
|
||
}
|
||
|
||
// ==================== 内部:等级 ====================
|
||
|
||
/// <summary>挡车工等级:木桶短板法,六项各自定级后取最低分</summary>
|
||
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<ShengsiDingjizhibiao>();
|
||
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<ShengsiDingjizhibiao> target, IEnumerable<ShengsiDingjizhibiao> src)
|
||
{
|
||
var best = src.OrderByDescending(z => z.GradeScore).FirstOrDefault();
|
||
if (best != null) target.Add(best);
|
||
}
|
||
|
||
/// <summary>车头工等级:按挡车工丝量加权平均等级分后回落取整档</summary>
|
||
private (string Dengji, decimal Dengjifen) CalCtgDengji(CalcContext ctx, List<CalcLdcct> 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);
|
||
}
|
||
|
||
// ==================== 内部:单价 / 金额 ====================
|
||
|
||
/// <summary>按绪数档取岗位基数金额:<60→50档,<70→60档,<100→70档,否则 100 档</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>缫折考核:±2kg 死区不奖不扣,超出部分按封顶 clamp,每 1kg 每 100kg 丝 奖扣 price 元</summary>
|
||
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() };
|
||
}
|