- 新增 PayRoll(工资)、Wms(仓储)、Prod(车速/缫丝/考勤/丝片检测/丝片台账/纤检日报/质量日报) 模块 - 新增对应 Domain 实体、Application DTO/Service、Api Controller 与前端页面 - 移除旧 Attendance 考勤模块(Controller/Service/Entity/DTO 及 5 个前端页面),由 Prod/Kaoqin 取代 - 调整路由、仪表盘、Lsjl 流丝记录实体与服务 - 更新业务种子数据与 DbSeeder - 补充工作进度文档 14-27
891 lines
42 KiB
C#
891 lines
42 KiB
C#
using F9WebAiTest.Application.Common;
|
||
using F9WebAiTest.Application.Dtos.Prod;
|
||
using F9WebAiTest.Domain.Common;
|
||
using F9WebAiTest.Domain.Entities.PayRoll;
|
||
using F9WebAiTest.Domain.Entities.Prod;
|
||
using F9WebAiTest.Domain.Enums.SYS.Departments;
|
||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||
using SysZq = F9WebAiTest.Domain.Entities.SYS.Zq;
|
||
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.Prod;
|
||
|
||
/// <summary>
|
||
/// 职工考勤-考勤登记(CQDJ)。
|
||
/// 对应 F9 WinClient\F9.Business.Prod\KQ\CQDJ。
|
||
/// 保存语义与老系统一致:按(日期 + 部门 + 工种 + 班次)先软删旧登记与明细,再整批新增(不做增量 diff)。
|
||
/// </summary>
|
||
public interface IKaoqinService
|
||
{
|
||
/// <summary>取某「日期+部门+工种+班次」的整组登记(未登记的职工按默认 9 小时生成)</summary>
|
||
Task<CqdjViewDto> GetRegisterAsync(DateTime date, long depId, long worktypeId, int banci, string keyword);
|
||
|
||
/// <summary>整组保存(覆盖)</summary>
|
||
Task<int> SaveRegisterAsync(CqdjSaveDto dto);
|
||
|
||
/// <summary>删除整组登记(软删)</summary>
|
||
Task<int> DeleteRegisterAsync(DateTime date, long depId, long worktypeId, int banci);
|
||
|
||
/// <summary>「添加职工」可选职工(本部门在职、未登记过的)</summary>
|
||
Task<List<CqdjEmpOptionDto>> GetEmployeesAsync(long depId, long worktypeId, int banci);
|
||
|
||
// ==================== 考勤统计(CQTJ) ====================
|
||
|
||
/// <summary>统计分页(库中的月快照)</summary>
|
||
Task<PageResult<CqtjRowDto>> GetCqtjPagedAsync(int year, int month, long depId, long worktypeId,
|
||
string like, int pageIndex, int pageSize);
|
||
|
||
/// <summary>录入:库中已有快照则取库,否则按账期实时汇总</summary>
|
||
Task<CqtjViewDto> GetCqtjForEditAsync(int year, int month, long depId);
|
||
|
||
/// <summary>保存(先软删该 年/月/部门 旧快照,再整批新增)</summary>
|
||
Task<int> SaveCqtjAsync(CqtjSaveDto dto);
|
||
|
||
/// <summary>删除该 年/月/部门 的统计快照</summary>
|
||
Task<int> DeleteCqtjAsync(int year, int month, long depId);
|
||
|
||
/// <summary>一键生成:遍历全部车间,缺的才生成</summary>
|
||
Task<int> GenerateCqtjAllAsync(int year, int month);
|
||
|
||
/// <summary>全部删除:删除该年月的全部统计快照</summary>
|
||
Task<int> DeleteCqtjAllAsync(int year, int month);
|
||
}
|
||
|
||
public class KaoqinService : IKaoqinService
|
||
{
|
||
// 出勤类型(Cqdj_CqType_Enum,老系统硬编码)
|
||
private const int CqZhengchang = 1;
|
||
private const int CqQingjia = 2;
|
||
private const int CqLiudong = 3;
|
||
private const int CqJiaban = 4;
|
||
private const int CqDaiban = 5;
|
||
private const int CqWaipai = 6;
|
||
private const int CqKuanggong = 7;
|
||
private const int CqGongshang = 8;
|
||
|
||
private const decimal DefaultHours = 9m; // 新职工默认标准 / 出勤时长
|
||
|
||
private readonly IRepository<Cqdj> _cqdjRepo;
|
||
private readonly IRepository<CqdjItem> _itemRepo;
|
||
private readonly IRepository<Cqtj> _cqtjRepo;
|
||
private readonly IRepository<SysDept.Department> _deptRepo;
|
||
private readonly IRepository<SysEmp.Employee> _empRepo;
|
||
private readonly IRepository<SysWt.WorkType> _wtRepo;
|
||
private readonly IRepository<ConfGz> _confGzRepo;
|
||
private readonly IRepository<ConfZq> _confZqRepo;
|
||
private readonly IRepository<ConfEmployee> _confEmpRepo;
|
||
private readonly IRepository<Gwsrkh> _gwsrkhRepo;
|
||
private readonly IRepository<SysZq.Zhangqi> _zhangqiRepo;
|
||
|
||
public KaoqinService(IRepository<Cqdj> cqdjRepo, IRepository<CqdjItem> itemRepo, IRepository<Cqtj> cqtjRepo,
|
||
IRepository<SysDept.Department> deptRepo, IRepository<SysEmp.Employee> empRepo,
|
||
IRepository<SysWt.WorkType> wtRepo, IRepository<ConfGz> confGzRepo, IRepository<ConfZq> confZqRepo,
|
||
IRepository<ConfEmployee> confEmpRepo, IRepository<Gwsrkh> gwsrkhRepo, IRepository<SysZq.Zhangqi> zhangqiRepo)
|
||
{
|
||
_cqdjRepo = cqdjRepo;
|
||
_itemRepo = itemRepo;
|
||
_cqtjRepo = cqtjRepo;
|
||
_deptRepo = deptRepo;
|
||
_empRepo = empRepo;
|
||
_wtRepo = wtRepo;
|
||
_confGzRepo = confGzRepo;
|
||
_confZqRepo = confZqRepo;
|
||
_confEmpRepo = confEmpRepo;
|
||
_gwsrkhRepo = gwsrkhRepo;
|
||
_zhangqiRepo = zhangqiRepo;
|
||
}
|
||
|
||
// ==================== 查询 ====================
|
||
|
||
public async Task<CqdjViewDto> GetRegisterAsync(DateTime date, long depId, long worktypeId, int banci, string keyword)
|
||
{
|
||
if (depId <= 0) throw new ServiceException("请先选择部门");
|
||
|
||
var day = date.Date;
|
||
var nextDay = day.AddDays(1);
|
||
var depts = (await _deptRepo.GetAllAsync()).ToDictionary(d => d.Id, d => d.Name);
|
||
var wts = (await _wtRepo.GetAllAsync()).ToDictionary(w => w.Id, w => w.WorkName);
|
||
var emps = await LoadEmployeesAsync(depId, worktypeId, banci);
|
||
|
||
// 已保存的登记
|
||
var saved = await _cqdjRepo.FindAsync(x => x.Date >= day && x.Date < nextDay
|
||
&& x.DepId == depId && x.Banci == banci);
|
||
if (worktypeId > 0) saved = saved.Where(x => x.WorktypeId == worktypeId).ToList();
|
||
var savedByEmp = saved.GroupBy(x => x.EmployeeId).ToDictionary(g => g.Key, g => g.First());
|
||
|
||
var ids = saved.Select(x => x.Id).ToList();
|
||
var items = ids.Count == 0
|
||
? new List<CqdjItem>()
|
||
: await _itemRepo.FindAsync(x => ids.Contains(x.CqdjId));
|
||
|
||
var view = new CqdjViewDto { Date = day, DepId = depId, WorktypeId = worktypeId, Banci = banci };
|
||
|
||
foreach (var emp in emps)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(keyword))
|
||
{
|
||
var key = keyword.Trim();
|
||
if (!((emp.Name ?? "").Contains(key) || (emp.Code ?? "").Contains(key))) continue;
|
||
}
|
||
|
||
var row = new CqdjRowDto
|
||
{
|
||
EmployeeId = emp.Id,
|
||
EmployeeCode = emp.Code ?? "",
|
||
EmployeeName = emp.Name ?? "",
|
||
DepId = emp.DepId,
|
||
DepName = depts.TryGetValue(emp.DepId, out var dn) ? dn : "",
|
||
WorktypeId = emp.WorktypeId,
|
||
WorktypeName = wts.TryGetValue(emp.WorktypeId, out var wn) ? wn : "",
|
||
Banci = banci,
|
||
IsXuegong = !emp.IsZhuanzheng,
|
||
};
|
||
|
||
if (savedByEmp.TryGetValue(emp.Id, out var rec))
|
||
{
|
||
row.Id = rec.Id;
|
||
row.Saved = true;
|
||
row.IsXuegong = rec.IsXuegong;
|
||
row.HoursStandard = rec.HoursStandard;
|
||
row.HoursCq = rec.HoursCq;
|
||
row.HoursQj = rec.HoursQj;
|
||
row.HoursLd = rec.HoursLd;
|
||
row.HoursJb = rec.HoursJb;
|
||
row.HoursDb = rec.HoursDb;
|
||
row.HoursWp = rec.HoursWp;
|
||
row.HoursKg = rec.HoursKg;
|
||
row.HoursGs = rec.HoursGs;
|
||
row.CsYeban = rec.CsYeban;
|
||
row.Memo = rec.Memo ?? "";
|
||
row.Items = items.Where(i => i.CqdjId == rec.Id)
|
||
.OrderBy(i => i.Id)
|
||
.Select(ToItemDto).ToList();
|
||
}
|
||
else
|
||
{
|
||
// 未登记:默认标准 9 小时 + 一条「正常」明细
|
||
row.HoursStandard = DefaultHours;
|
||
row.HoursCq = DefaultHours;
|
||
row.Items.Add(new CqdjItemDto
|
||
{
|
||
DepId = emp.DepId,
|
||
WorktypeId = emp.WorktypeId,
|
||
TypeCq = CqZhengchang,
|
||
TypeQj = 0,
|
||
Hours = DefaultHours,
|
||
});
|
||
}
|
||
|
||
view.Rows.Add(row);
|
||
}
|
||
|
||
return view;
|
||
}
|
||
|
||
// ==================== 保存 ====================
|
||
|
||
public async Task<int> SaveRegisterAsync(CqdjSaveDto dto)
|
||
{
|
||
if (dto == null) throw new ServiceException("保存内容为空");
|
||
if (dto.Date == default) throw new ServiceException("请选择考勤日期");
|
||
if (dto.DepId <= 0) throw new ServiceException("请选择部门");
|
||
if (dto.Rows == null || dto.Rows.Count == 0) throw new ServiceException("没有需要保存的职工");
|
||
|
||
var day = dto.Date.Date;
|
||
var nextDay = day.AddDays(1);
|
||
var empDict = (await _empRepo.GetAllAsync()).ToDictionary(e => e.Id);
|
||
|
||
// 校验
|
||
foreach (var row in dto.Rows)
|
||
{
|
||
var name = empDict.TryGetValue(row.EmployeeId, out var e) ? e.Name : $"#{row.EmployeeId}";
|
||
var items = row.Items ?? new List<CqdjItemDto>();
|
||
if (items.Count == 0) throw new ServiceException($"职工「{name}」没有考勤明细");
|
||
if (items.Count(x => x.TypeCq == CqZhengchang) != 1)
|
||
throw new ServiceException($"职工「{name}」必须且只能有一条「正常」明细");
|
||
foreach (var it in items)
|
||
{
|
||
if (it.TypeCq == CqQingjia && it.TypeQj == 0) throw new ServiceException($"职工「{name}」的请假明细未选择请假类型");
|
||
if (it.TypeCq != CqQingjia && it.TypeQj != 0) throw new ServiceException($"职工「{name}」的非请假明细不能带请假类型");
|
||
if (it.Hours < 0) throw new ServiceException($"职工「{name}」存在负时长");
|
||
}
|
||
if (CalcHoursCq(items) < 0) throw new ServiceException($"职工「{name}」的出勤时长不能为负,请检查请假 / 旷工时长");
|
||
}
|
||
|
||
// 整组覆盖:先软删该(日期 + 部门 + 工种 + 班次)的旧登记与明细
|
||
var old = await _cqdjRepo.FindAsync(x => x.Date >= day && x.Date < nextDay
|
||
&& x.DepId == dto.DepId && x.Banci == dto.Banci);
|
||
if (dto.WorktypeId > 0) old = old.Where(x => x.WorktypeId == dto.WorktypeId).ToList();
|
||
if (old.Count > 0)
|
||
{
|
||
var oldIds = old.Select(x => x.Id).ToList();
|
||
var oldItems = await _itemRepo.FindAsync(x => oldIds.Contains(x.CqdjId));
|
||
if (oldItems.Count > 0) await _itemRepo.DeleteRangeAsync(oldItems);
|
||
await _cqdjRepo.DeleteRangeAsync(old);
|
||
}
|
||
|
||
var count = 0;
|
||
foreach (var row in dto.Rows)
|
||
{
|
||
var emp = empDict.TryGetValue(row.EmployeeId, out var e) ? e : null;
|
||
var items = row.Items ?? new List<CqdjItemDto>();
|
||
|
||
var master = new Cqdj
|
||
{
|
||
Date = day,
|
||
DepId = dto.DepId,
|
||
WorktypeId = row.WorktypeId > 0 ? row.WorktypeId : (emp?.WorktypeId ?? 0),
|
||
EmployeeId = row.EmployeeId,
|
||
Banci = dto.Banci,
|
||
IsXuegong = row.IsXuegong,
|
||
HoursStandard = row.HoursStandard,
|
||
HoursCq = CalcHoursCq(items),
|
||
HoursQj = Sum(items, CqQingjia),
|
||
HoursLd = Sum(items, CqLiudong),
|
||
HoursJb = Sum(items, CqJiaban),
|
||
HoursDb = Sum(items, CqDaiban),
|
||
HoursWp = Sum(items, CqWaipai),
|
||
HoursKg = Sum(items, CqKuanggong),
|
||
HoursGs = Sum(items, CqGongshang),
|
||
CsYeban = row.CsYeban,
|
||
Memo = row.Memo ?? "",
|
||
};
|
||
await _cqdjRepo.AddAsync(master);
|
||
|
||
var entities = items.Select(it => new CqdjItem
|
||
{
|
||
CqdjId = master.Id,
|
||
Date = day,
|
||
DepId = it.DepId > 0 ? it.DepId : (row.DepId > 0 ? row.DepId : dto.DepId),
|
||
WorktypeId = it.WorktypeId > 0 ? it.WorktypeId : master.WorktypeId,
|
||
EmployeeId = row.EmployeeId,
|
||
Banci = dto.Banci,
|
||
TypeCq = it.TypeCq,
|
||
TypeQj = it.TypeQj,
|
||
Hours = it.Hours,
|
||
Jizu = it.Jizu ?? "",
|
||
CtStart = it.CtStart ?? "",
|
||
CtEnd = it.CtEnd ?? "",
|
||
DangcheNum = it.DangcheNum,
|
||
Memo = it.Memo ?? "",
|
||
}).ToList();
|
||
await _itemRepo.AddRangeAsync(entities);
|
||
count++;
|
||
}
|
||
|
||
return count;
|
||
}
|
||
|
||
// ==================== 删除 ====================
|
||
|
||
public async Task<int> DeleteRegisterAsync(DateTime date, long depId, long worktypeId, int banci)
|
||
{
|
||
if (depId <= 0) throw new ServiceException("请选择部门");
|
||
var day = date.Date;
|
||
var nextDay = day.AddDays(1);
|
||
var old = await _cqdjRepo.FindAsync(x => x.Date >= day && x.Date < nextDay
|
||
&& x.DepId == depId && x.Banci == banci);
|
||
if (worktypeId > 0) old = old.Where(x => x.WorktypeId == worktypeId).ToList();
|
||
if (old.Count == 0) return 0;
|
||
var oldIds = old.Select(x => x.Id).ToList();
|
||
var oldItems = await _itemRepo.FindAsync(x => oldIds.Contains(x.CqdjId));
|
||
if (oldItems.Count > 0) await _itemRepo.DeleteRangeAsync(oldItems);
|
||
await _cqdjRepo.DeleteRangeAsync(old);
|
||
return old.Count;
|
||
}
|
||
|
||
// ==================== 下拉 ====================
|
||
|
||
public async Task<List<CqdjEmpOptionDto>> GetEmployeesAsync(long depId, long worktypeId, int banci)
|
||
{
|
||
if (depId <= 0) return new List<CqdjEmpOptionDto>();
|
||
var depts = (await _deptRepo.GetAllAsync()).ToDictionary(d => d.Id, d => d.Name);
|
||
var wts = (await _wtRepo.GetAllAsync()).ToDictionary(w => w.Id, w => w.WorkName);
|
||
var emps = await LoadEmployeesAsync(depId, worktypeId, banci);
|
||
return emps.Select(e => new CqdjEmpOptionDto
|
||
{
|
||
Id = e.Id,
|
||
Code = e.Code ?? "",
|
||
Name = e.Name ?? "",
|
||
DepId = e.DepId,
|
||
DepName = depts.TryGetValue(e.DepId, out var dn) ? dn : "",
|
||
WorktypeId = e.WorktypeId,
|
||
WorktypeName = wts.TryGetValue(e.WorktypeId, out var wn) ? wn : "",
|
||
IsXuegong = !e.IsZhuanzheng,
|
||
}).ToList();
|
||
}
|
||
|
||
// ==================== 考勤统计(CQTJ) ====================
|
||
|
||
public async Task<PageResult<CqtjRowDto>> GetCqtjPagedAsync(int year, int month, long depId, long worktypeId,
|
||
string like, int pageIndex, int pageSize)
|
||
{
|
||
var dicts = await LoadNameDictsAsync();
|
||
var all = (await _cqtjRepo.GetAllAsync())
|
||
.Where(x => x.Year == year && x.Month == month);
|
||
if (depId > 0) all = all.Where(x => x.DepId == depId);
|
||
if (worktypeId > 0) all = all.Where(x => x.WorktypeId == worktypeId);
|
||
|
||
var rows = all.Select(x => ToCqtjDto(x, dicts)).ToList();
|
||
if (!string.IsNullOrWhiteSpace(like))
|
||
{
|
||
var key = like.Trim();
|
||
rows = rows.Where(x => (x.EmployeeName ?? "").Contains(key) || (x.EmployeeCode ?? "").Contains(key)).ToList();
|
||
}
|
||
|
||
var total = rows.Count;
|
||
var items = rows
|
||
.OrderBy(x => x.DepId).ThenBy(x => x.WorktypeId).ThenBy(x => x.EmployeeCode)
|
||
.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||
return new PageResult<CqtjRowDto> { Items = items, Total = total, PageIndex = pageIndex, PageSize = pageSize };
|
||
}
|
||
|
||
public async Task<CqtjViewDto> GetCqtjForEditAsync(int year, int month, long depId)
|
||
{
|
||
if (depId <= 0) throw new ServiceException("请选择部门");
|
||
var dicts = await LoadNameDictsAsync();
|
||
var exist = (await _cqtjRepo.GetAllAsync())
|
||
.Where(x => x.Year == year && x.Month == month && x.DepId == depId)
|
||
.OrderBy(x => x.WorktypeId).ThenBy(x => x.EmployeeId).ToList();
|
||
|
||
var confZq = await _confZqRepo.FirstOrDefaultAsync(x => x.Year == year && x.Month == month);
|
||
var view = new CqtjViewDto
|
||
{
|
||
Year = year, Month = month, DepId = depId,
|
||
DepName = dicts.Depts.TryGetValue(depId, out var dn) ? dn : "",
|
||
QsQuanqinDays = confZq?.QsQuanqinDays ?? 0,
|
||
Exists = exist.Count > 0
|
||
};
|
||
view.Rows = exist.Count > 0
|
||
? exist.Select(x => ToCqtjDto(x, dicts)).ToList()
|
||
: await CalcCqtjAsync(year, month, depId);
|
||
return view;
|
||
}
|
||
|
||
public async Task<int> SaveCqtjAsync(CqtjSaveDto dto)
|
||
{
|
||
if (dto == null) throw new ServiceException("保存内容为空");
|
||
if (dto.DepId <= 0) throw new ServiceException("请选择部门");
|
||
var zq = await _zhangqiRepo.FirstOrDefaultAsync(x => x.Year == dto.Year && x.Month == dto.Month);
|
||
if (zq != null && zq.IsZhaZhang) throw new ServiceException("账期已扎帐,无法修改!");
|
||
|
||
var old = (await _cqtjRepo.GetAllAsync())
|
||
.Where(x => x.Year == dto.Year && x.Month == dto.Month && x.DepId == dto.DepId).ToList();
|
||
if (old.Count > 0) await _cqtjRepo.DeleteRangeAsync(old);
|
||
|
||
var list = (dto.Rows ?? new List<CqtjRowDto>()).Select(x => new Cqtj
|
||
{
|
||
Year = dto.Year, Month = dto.Month,
|
||
DepId = dto.DepId, WorktypeId = x.WorktypeId, EmployeeId = x.EmployeeId,
|
||
DaysCqStandard = x.DaysCqStandard, DaysCqActual = x.DaysCqActual, CqTianci = x.CqTianci,
|
||
HoursCq = x.HoursCq, HoursQj = x.HoursQj, HoursLd = x.HoursLd, HoursJb = x.HoursJb,
|
||
HoursDb = x.HoursDb, HoursWp = x.HoursWp, HoursKg = x.HoursKg, CsYeban = x.CsYeban,
|
||
HoursQjShijia = x.HoursQjShijia, HoursQjBj = x.HoursQjBj, HoursQjHj = x.HoursQjHj,
|
||
HoursQjCj = x.HoursQjCj, HoursQjLx = x.HoursQjLx, HoursQjSangjia = x.HoursQjSangjia,
|
||
HoursQjGj = x.HoursQjGj, HoursQjNj = x.HoursQjNj, HoursQjTx = x.HoursQjTx,
|
||
HoursXgCq = x.HoursXgCq, HoursXgJiaban = x.HoursXgJiaban,
|
||
HoursFadingjiaqi = x.HoursFadingjiaqi, HoursQjQqmc = x.HoursQjQqmc, HoursQjQqcal = x.HoursQjQqcal,
|
||
JineQuanqin = x.JineQuanqin, JineJb = x.JineJb, JineLiudong = x.JineLiudong,
|
||
JineDaiban = x.JineDaiban, JineYb = x.JineYb, JineKgKk = x.JineKgKk,
|
||
JineJqBzHj = x.JineJqBzHj, JineJqBzBj = x.JineJqBzBj, JineJqBzLx = x.JineJqBzLx,
|
||
JineJqBzSangjia = x.JineJqBzSangjia, JineJqBzFading = x.JineJqBzFading,
|
||
JineJqBzTxnj = x.JineJqBzTxnj, JineJqBz = x.JineJqBz,
|
||
Memo = x.Memo ?? ""
|
||
}).ToList();
|
||
await _cqtjRepo.AddRangeAsync(list);
|
||
return list.Count;
|
||
}
|
||
|
||
public async Task<int> DeleteCqtjAsync(int year, int month, long depId)
|
||
{
|
||
if (depId <= 0) throw new ServiceException("请选择部门");
|
||
var zq = await _zhangqiRepo.FirstOrDefaultAsync(x => x.Year == year && x.Month == month);
|
||
if (zq != null && zq.IsZhaZhang) throw new ServiceException("账期已扎帐,无法修改!");
|
||
var old = (await _cqtjRepo.GetAllAsync())
|
||
.Where(x => x.Year == year && x.Month == month && x.DepId == depId).ToList();
|
||
if (old.Count == 0) return 0;
|
||
await _cqtjRepo.DeleteRangeAsync(old);
|
||
return old.Count;
|
||
}
|
||
|
||
public async Task<int> GenerateCqtjAllAsync(int year, int month)
|
||
{
|
||
var zq = await _zhangqiRepo.FirstOrDefaultAsync(x => x.Year == year && x.Month == month);
|
||
if (zq != null && zq.IsZhaZhang) throw new ServiceException("账期已扎帐,无法生成!");
|
||
var confZq = await _confZqRepo.FirstOrDefaultAsync(x => x.Year == year && x.Month == month);
|
||
if (confZq == null || confZq.QsQuanqinDays <= 0)
|
||
throw new ServiceException("请先录入薪资配置账期的前缫出勤天数!");
|
||
|
||
var depts = (await _deptRepo.GetAllAsync())
|
||
.Where(d => d.IsActive && d.ManageType != Department_ManageType_Enum.公司)
|
||
.ToList();
|
||
var saved = (await _cqtjRepo.GetAllAsync())
|
||
.Where(x => x.Year == year && x.Month == month)
|
||
.Select(x => x.DepId).Distinct().ToHashSet();
|
||
|
||
var count = 0;
|
||
foreach (var d in depts)
|
||
{
|
||
if (saved.Contains(d.Id)) continue;
|
||
var rows = await CalcCqtjAsync(year, month, d.Id);
|
||
if (rows.Count == 0) continue;
|
||
await SaveCqtjAsync(new CqtjSaveDto { Year = year, Month = month, DepId = d.Id, Rows = rows });
|
||
count++;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
public async Task<int> DeleteCqtjAllAsync(int year, int month)
|
||
{
|
||
var zq = await _zhangqiRepo.FirstOrDefaultAsync(x => x.Year == year && x.Month == month);
|
||
if (zq != null && zq.IsZhaZhang) throw new ServiceException("账期已扎帐,无法修改!");
|
||
var old = (await _cqtjRepo.GetAllAsync()).Where(x => x.Year == year && x.Month == month).ToList();
|
||
if (old.Count == 0) return 0;
|
||
await _cqtjRepo.DeleteRangeAsync(old);
|
||
return old.Count;
|
||
}
|
||
|
||
// ==================== 私有 ====================
|
||
|
||
private class NameDicts
|
||
{
|
||
public Dictionary<long, string> Depts = new();
|
||
public Dictionary<long, string> Worktypes = new();
|
||
public Dictionary<long, SysEmp.Employee> Emps = new();
|
||
}
|
||
|
||
private async Task<NameDicts> LoadNameDictsAsync() => new()
|
||
{
|
||
Depts = (await _deptRepo.GetAllAsync()).ToDictionary(d => d.Id, d => d.Name),
|
||
Worktypes = (await _wtRepo.GetAllAsync()).ToDictionary(w => w.Id, w => w.WorkName),
|
||
Emps = (await _empRepo.GetAllAsync()).ToDictionary(e => e.Id)
|
||
};
|
||
|
||
private static CqtjRowDto ToCqtjDto(Cqtj x, NameDicts d) => new()
|
||
{
|
||
Id = x.Id, Year = x.Year, Month = x.Month,
|
||
DepId = x.DepId, DepName = d.Depts.TryGetValue(x.DepId, out var dn) ? dn : "",
|
||
WorktypeId = x.WorktypeId, WorktypeName = d.Worktypes.TryGetValue(x.WorktypeId, out var wn) ? wn : "",
|
||
EmployeeId = x.EmployeeId,
|
||
EmployeeCode = d.Emps.TryGetValue(x.EmployeeId, out var e) ? (e.Code ?? "") : "",
|
||
EmployeeName = d.Emps.TryGetValue(x.EmployeeId, out var e2) ? (e2.Name ?? "") : "",
|
||
DaysCqStandard = x.DaysCqStandard, DaysCqActual = x.DaysCqActual, CqTianci = x.CqTianci,
|
||
HoursCq = x.HoursCq, HoursQj = x.HoursQj, HoursLd = x.HoursLd, HoursJb = x.HoursJb,
|
||
HoursDb = x.HoursDb, HoursWp = x.HoursWp, HoursKg = x.HoursKg, CsYeban = x.CsYeban,
|
||
HoursQjShijia = x.HoursQjShijia, HoursQjBj = x.HoursQjBj, HoursQjHj = x.HoursQjHj,
|
||
HoursQjCj = x.HoursQjCj, HoursQjLx = x.HoursQjLx, HoursQjSangjia = x.HoursQjSangjia,
|
||
HoursQjGj = x.HoursQjGj, HoursQjNj = x.HoursQjNj, HoursQjTx = x.HoursQjTx,
|
||
HoursXgCq = x.HoursXgCq, HoursXgJiaban = x.HoursXgJiaban,
|
||
HoursFadingjiaqi = x.HoursFadingjiaqi, HoursQjQqmc = x.HoursQjQqmc, HoursQjQqcal = x.HoursQjQqcal,
|
||
JineQuanqin = x.JineQuanqin, JineJb = x.JineJb, JineLiudong = x.JineLiudong,
|
||
JineDaiban = x.JineDaiban, JineYb = x.JineYb, JineKgKk = x.JineKgKk,
|
||
JineJqBzHj = x.JineJqBzHj, JineJqBzBj = x.JineJqBzBj, JineJqBzLx = x.JineJqBzLx,
|
||
JineJqBzSangjia = x.JineJqBzSangjia, JineJqBzFading = x.JineJqBzFading,
|
||
JineJqBzTxnj = x.JineJqBzTxnj, JineJqBz = x.JineJqBz,
|
||
Memo = x.Memo ?? ""
|
||
};
|
||
|
||
/// <summary>账期(取不到时按自然月兜底)</summary>
|
||
private async Task<(DateTime Start, DateTime End)> GetZhangqiRangeAsync(int year, int month)
|
||
{
|
||
var zq = await _zhangqiRepo.FirstOrDefaultAsync(x => x.Year == year && x.Month == month);
|
||
if (zq != null) return (zq.StartDate.Date, zq.EndDate.Date.AddDays(1).AddSeconds(-1));
|
||
var start = new DateTime(year, month, 1);
|
||
return (start, start.AddMonths(1).AddSeconds(-1));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按账期汇总某部门的考勤统计(对齐 Page_Cqtj.CqtjViews)。
|
||
/// 全勤奖涉及「该职工在全部部门 / 工种」的合计出勤 / 请假 / 旷工,故登记数据按账期全量加载。
|
||
/// </summary>
|
||
private async Task<List<CqtjRowDto>> CalcCqtjAsync(int year, int month, long depId)
|
||
{
|
||
var (start, end) = await GetZhangqiRangeAsync(year, month);
|
||
var confZq = await _confZqRepo.FirstOrDefaultAsync(x => x.Year == year && x.Month == month);
|
||
var qsDays = confZq?.QsQuanqinDays ?? 0;
|
||
if (qsDays <= 0) throw new ServiceException("请先在【薪资配置账期】功能中设置当月前缫出勤天数!");
|
||
|
||
var dicts = await LoadNameDictsAsync();
|
||
var cqdjAll = await _cqdjRepo.FindAsync(x => x.Date >= start && x.Date <= end);
|
||
if (cqdjAll.Count == 0) return new List<CqtjRowDto>();
|
||
var ids = cqdjAll.Select(x => x.Id).ToList();
|
||
var itemsAll = await _itemRepo.FindAsync(x => ids.Contains(x.CqdjId));
|
||
|
||
var confGzAll = await _confGzRepo.GetAllAsync();
|
||
var confEmpAll = await _confEmpRepo.GetAllAsync();
|
||
var gwsrkhAll = (await _gwsrkhRepo.GetAllAsync()).Where(x => x.Year == year && x.Month == month).ToList();
|
||
var deptsAll = await _deptRepo.GetAllAsync();
|
||
|
||
// 每职工全部门合计(全勤奖口径)
|
||
var cqHoursByEmp = cqdjAll.GroupBy(x => x.EmployeeId).Select(g => new
|
||
{
|
||
EmployeeId = g.Key,
|
||
CqHours = g.Sum(x => x.HoursCq),
|
||
JiabanHours = g.Sum(x => x.HoursJb),
|
||
QjHours = g.Sum(x => x.HoursQj),
|
||
KgHours = g.Sum(x => x.HoursKg)
|
||
}).ToDictionary(x => x.EmployeeId);
|
||
|
||
var groups = cqdjAll.Where(x => x.DepId == depId)
|
||
.GroupBy(x => new { x.EmployeeId, x.DepId, x.WorktypeId })
|
||
.Select(g => new
|
||
{
|
||
g.Key.EmployeeId, g.Key.DepId, g.Key.WorktypeId,
|
||
HoursCq = g.Sum(x => x.HoursCq),
|
||
HoursQj = g.Sum(x => x.HoursQj),
|
||
HoursLd = g.Sum(x => x.HoursLd),
|
||
HoursJb = g.Sum(x => x.HoursJb),
|
||
HoursDb = g.Sum(x => x.HoursDb),
|
||
HoursWp = g.Sum(x => x.HoursWp),
|
||
HoursKg = g.Sum(x => x.HoursKg),
|
||
CsYeban = g.Sum(x => x.CsYeban),
|
||
List = g.ToList()
|
||
}).ToList();
|
||
|
||
var result = new List<CqtjRowDto>();
|
||
foreach (var item in groups)
|
||
{
|
||
if (item.HoursCq + item.HoursJb + item.HoursQj + item.HoursLd + item.HoursDb
|
||
+ item.HoursKg + item.CsYeban == 0) continue;
|
||
|
||
var empDepWtIds = item.List.Select(x => x.Id).ToHashSet();
|
||
var depItems = itemsAll.Where(x => empDepWtIds.Contains(x.CqdjId)).ToList();
|
||
var confEmp = confEmpAll.FirstOrDefault(x => x.EmployeeId == item.EmployeeId && x.WorktypeId == item.WorktypeId);
|
||
var confGz = confGzAll.FirstOrDefault(x => x.WorkTypeId == item.WorktypeId);
|
||
dicts.Emps.TryGetValue(item.EmployeeId, out var emp);
|
||
|
||
var row = new CqtjRowDto
|
||
{
|
||
Year = year, Month = month,
|
||
DepId = item.DepId, DepName = dicts.Depts.TryGetValue(item.DepId, out var dn) ? dn : "",
|
||
WorktypeId = item.WorktypeId, WorktypeName = dicts.Worktypes.TryGetValue(item.WorktypeId, out var wn) ? wn : "",
|
||
EmployeeId = item.EmployeeId,
|
||
EmployeeCode = emp?.Code ?? "", EmployeeName = emp?.Name ?? "",
|
||
HoursCq = item.HoursCq, HoursQj = item.HoursQj, HoursLd = item.HoursLd,
|
||
HoursJb = item.HoursJb, HoursDb = item.HoursDb, HoursWp = item.HoursWp,
|
||
HoursKg = item.HoursKg, CsYeban = item.CsYeban
|
||
};
|
||
|
||
if (confGz == null) { result.Add(row); continue; }
|
||
|
||
// 学工时长
|
||
var xgList = item.List.Where(x => x.IsXuegong).ToList();
|
||
var xgIds = xgList.Select(x => x.Id).ToHashSet();
|
||
var xgItems = itemsAll.Where(x => xgIds.Contains(x.CqdjId)).ToList();
|
||
row.HoursXgCq = xgList.Sum(x => x.HoursCq);
|
||
row.HoursXgJiaban = xgList.Sum(x => x.HoursJb);
|
||
|
||
// 法定假
|
||
var fadingBzHours = confEmp?.HoursFadingBz ?? 0;
|
||
var fadingGzHours = confEmp?.HoursFadingGz ?? 0;
|
||
var jineFadingBz = jisuanFadingBz(fadingBzHours, fadingGzHours, confGz);
|
||
row.HoursFadingjiaqi = R2(fadingBzHours + fadingGzHours);
|
||
|
||
// 假期补助
|
||
var bz = jiaqiBuzhuJisuan(confGzAll, depItems, jineFadingBz);
|
||
row.HoursQjShijia = bz.HoursShijia; row.HoursQjBj = bz.HoursBj; row.HoursQjHj = bz.HoursHj;
|
||
row.HoursQjCj = bz.HoursCj; row.HoursQjLx = bz.HoursLx; row.HoursQjSangjia = bz.HoursSangjia;
|
||
row.HoursQjGj = bz.HoursGj; row.HoursQjNj = bz.HoursNj; row.HoursQjTx = bz.HoursTx;
|
||
row.JineJqBzHj = bz.JineHj; row.JineJqBzBj = bz.JineBj; row.JineJqBzLx = bz.JineLx;
|
||
row.JineJqBzSangjia = bz.JineSangjia; row.JineJqBzFading = jineFadingBz;
|
||
row.JineJqBzTxnj = bz.JineTxnj; row.JineJqBz = bz.JineTotal;
|
||
|
||
var daixinQjHours = bz.HoursNj + bz.HoursTx + bz.HoursGj + bz.HoursLx + bz.HoursHj + bz.HoursSangjia + bz.HoursCj;
|
||
var empSum = cqHoursByEmp.TryGetValue(item.EmployeeId, out var s) ? s : null;
|
||
var sumCqHours = empSum?.CqHours ?? 0;
|
||
var sumJiabanHours = empSum?.JiabanHours ?? 0;
|
||
var sumQjHours = empSum?.QjHours ?? 0;
|
||
var sumKgHours = empSum?.KgHours ?? 0;
|
||
|
||
// 加班免扣请假 / 全勤计算请假
|
||
row.HoursQjQqmc = jisuanJiabanMkHours(confGz, sumJiabanHours, item.HoursQj, qsDays, item.HoursCq);
|
||
var sumDaixinHours = sumDaixinHoursJisuan(itemsAll, item.EmployeeId);
|
||
var qqJisuanQjHours = sumQjHours - row.HoursQjQqmc - sumDaixinHours;
|
||
var depQj = item.HoursQj - row.HoursQjQqmc - daixinQjHours;
|
||
row.HoursQjQqcal = depQj < 0 ? 0 : depQj;
|
||
|
||
// 全勤奖
|
||
var hoursPerday = confGz.HoursPerday;
|
||
if (sumCqHours <= 15 * hoursPerday && emp != null && emp.WorkStatus == Employee_WorkStatus_Enum.离职)
|
||
row.JineQuanqin = 0;
|
||
else if (confGz.JineQuanqin > 0)
|
||
row.JineQuanqin = quanqin(confGz, sumCqHours, sumQjHours, item.HoursCq, item.HoursJb,
|
||
row.HoursQjQqcal, qqJisuanQjHours, daixinQjHours, confEmp, sumDaixinHours, sumCqHours,
|
||
sumKgHours, emp, start, end, qsDays,
|
||
deptsAll.FirstOrDefault(d => d.Id == item.DepId), sumJiabanHours);
|
||
|
||
// 加班费 / 流动 / 代班 / 夜班 / 旷工
|
||
var gwsrkh = gwsrkhAll.FirstOrDefault(x => x.EmployeeId == item.EmployeeId);
|
||
row.JineJb = jisuanJianbanJine(depItems, confGzAll, xgItems, gwsrkh?.KhJieguo);
|
||
row.JineLiudong = liudongJineJisuan(depItems, confGz);
|
||
row.JineDaiban = daibanJineJisuan(depItems, confGzAll);
|
||
row.JineYb = R2(confGz.JineYeban * item.CsYeban);
|
||
row.JineKgKk = R2(confGz.JiaqiJineKuangongKk * item.HoursKg);
|
||
|
||
row.CqTianci = jisuanCqTianci(item.List);
|
||
row.DaysCqStandard = confGz.QqDaysStandard;
|
||
row.DaysCqActual = hoursPerday == 0 ? 0 : R2(item.HoursCq / hoursPerday);
|
||
|
||
result.Add(row);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// ---------- 口径函数(对齐 Page_Cqtj 的同名方法) ----------
|
||
|
||
private static decimal R2(decimal v) => Math.Round(v, 2, MidpointRounding.AwayFromZero);
|
||
|
||
private class JiaqiBuzhu
|
||
{
|
||
public decimal HoursShijia, HoursBj, HoursHj, HoursCj, HoursLx, HoursSangjia, HoursGj, HoursNj, HoursTx;
|
||
public decimal JineHj, JineBj, JineLx, JineSangjia, JineTxnj, JineTotal;
|
||
}
|
||
|
||
/// <summary>假期补助 + 请假分项时长(按明细的 请假类型 × 出勤工种 分组计算)</summary>
|
||
private static JiaqiBuzhu jiaqiBuzhuJisuan(List<ConfGz> confGzAll, List<CqdjItem> items, decimal jineFadingBz)
|
||
{
|
||
var r = new JiaqiBuzhu();
|
||
var groups = items.GroupBy(x => new { x.TypeQj, x.WorktypeId })
|
||
.Select(g => new { Hours = g.Sum(x => x.Hours), g.Key.TypeQj, g.Key.WorktypeId }).ToList();
|
||
foreach (var g in groups)
|
||
{
|
||
var cfg = confGzAll.FirstOrDefault(x => x.WorkTypeId == g.WorktypeId);
|
||
switch (g.TypeQj)
|
||
{
|
||
case 2: // 病假
|
||
r.HoursBj = g.Hours;
|
||
r.JineBj += R2((cfg?.JiaqiJineBingjiaBz ?? 0) * g.Hours); break;
|
||
case 10: // 轮休
|
||
r.HoursLx = g.Hours;
|
||
r.JineLx += R2((cfg?.JiaqiJineLunxiuBz ?? 0) * g.Hours); break;
|
||
case 4: // 婚假
|
||
r.HoursHj = g.Hours;
|
||
r.JineHj += R2((cfg?.JiaqiJineHunjiaBz ?? 0) * g.Hours); break;
|
||
case 5: // 丧假
|
||
r.HoursSangjia = g.Hours;
|
||
r.JineSangjia += R2((cfg?.JiaqiJineSangjiaBz ?? 0) * g.Hours); break;
|
||
case 3: // 产假
|
||
r.HoursCj = g.Hours; break;
|
||
case 1: // 事假
|
||
r.HoursShijia = g.Hours; break;
|
||
case 7: // 年假
|
||
r.HoursNj = g.Hours;
|
||
r.JineTxnj += R2((cfg?.Jiaqi_Jine_NianjiaBz ?? 0) * g.Hours); break;
|
||
case 6: // 公假
|
||
r.HoursGj = g.Hours; break;
|
||
case 9: // 调休
|
||
r.HoursTx = g.Hours;
|
||
r.JineTxnj += R2((cfg?.Jiaqi_Jine_NianjiaBz ?? 0) * g.Hours); break;
|
||
}
|
||
}
|
||
r.JineTotal = r.JineHj + r.JineBj + r.JineLx + r.JineSangjia + jineFadingBz + r.JineTxnj;
|
||
return r;
|
||
}
|
||
|
||
/// <summary>加班免扣请假时长:满勤且加班达到门槛时免扣固定时长</summary>
|
||
private static decimal jisuanJiabanMkHours(ConfGz cfg, decimal sumJiabanHours, decimal hoursQj,
|
||
decimal qsDays, decimal depCqHours)
|
||
{
|
||
var manqinHours = qsDays * cfg.HoursPerday;
|
||
if (depCqHours + sumJiabanHours >= manqinHours && sumJiabanHours >= cfg.QqHoursDkMinjiaban && sumJiabanHours != 0)
|
||
return cfg.QqHoursDkQingjia;
|
||
return 0;
|
||
}
|
||
|
||
/// <summary>带薪请假时长合计(跨部门,按该职工全部明细)</summary>
|
||
private static decimal sumDaixinHoursJisuan(List<CqdjItem> items, long employeeId)
|
||
=> items.Where(x => x.EmployeeId == employeeId && x.TypeQj is 10 or 7 or 6 or 9 or 4 or 3 or 5)
|
||
.Sum(x => x.Hours);
|
||
|
||
/// <summary>加班费(含学工加班:学工岗位薪资折算)</summary>
|
||
private static decimal jisuanJianbanJine(List<CqdjItem> items, List<ConfGz> confGzAll,
|
||
List<CqdjItem> xgItems, string khJieguo)
|
||
{
|
||
decimal jine = 0;
|
||
var groups = items.Where(x => x.TypeCq == CqJiaban)
|
||
.GroupBy(x => x.WorktypeId)
|
||
.Select(g => new { Hours = g.Sum(x => x.Hours), WorktypeId = g.Key }).ToList();
|
||
foreach (var g in groups)
|
||
{
|
||
var cfg = confGzAll.FirstOrDefault(x => x.WorkTypeId == g.WorktypeId);
|
||
decimal price = cfg?.JiabanJineBz ?? 0;
|
||
if (!string.IsNullOrWhiteSpace(khJieguo) && cfg != null)
|
||
{
|
||
price = khJieguo switch
|
||
{
|
||
"欠合格" => cfg.Jiaban_GwsrkhJineQianhege,
|
||
"期望" => cfg.Jiaban_GwsrkhJineQiwang,
|
||
"合格" => cfg.Jiaban_GwsrkhJineHege,
|
||
"胜任" => cfg.Jiaban_GwsrkhJineShengren,
|
||
"超胜任" => cfg.Jiaban_GwsrkhJineChaoshengren,
|
||
_ => cfg.JiabanJineBz
|
||
};
|
||
}
|
||
jine += g.Hours * price;
|
||
}
|
||
|
||
var xgGroups = xgItems.Where(x => x.TypeCq == CqJiaban)
|
||
.GroupBy(x => x.WorktypeId)
|
||
.Select(g => new { Hours = g.Sum(x => x.Hours), WorktypeId = g.Key }).ToList();
|
||
foreach (var g in xgGroups)
|
||
{
|
||
var cfg = confGzAll.FirstOrDefault(x => x.WorkTypeId == g.WorktypeId);
|
||
if (cfg == null) continue;
|
||
var std = cfg.QqDaysStandard * cfg.HoursPerday;
|
||
if (std == 0) continue;
|
||
jine += cfg.XgJineGangwei / std * g.Hours;
|
||
}
|
||
return R2(jine);
|
||
}
|
||
|
||
/// <summary>流动工资:工种配置的固定流动工资(元/小时)× 流动时长</summary>
|
||
private static decimal liudongJineJisuan(List<CqdjItem> items, ConfGz cfg)
|
||
{
|
||
decimal jine = 0;
|
||
var groups = items.Where(x => x.TypeCq == CqLiudong)
|
||
.GroupBy(x => new { x.DepId, x.WorktypeId })
|
||
.Select(g => new { Hours = g.Sum(x => x.Hours) }).ToList();
|
||
foreach (var g in groups) jine += R2(cfg.LiudongJineGuding * g.Hours);
|
||
return R2(jine);
|
||
}
|
||
|
||
/// <summary>代班工资:代班所在工种岗位薪资 / 标准出勤时长 × 代班时长</summary>
|
||
private static decimal daibanJineJisuan(List<CqdjItem> items, List<ConfGz> confGzAll)
|
||
{
|
||
decimal jine = 0;
|
||
var groups = items.Where(x => x.TypeCq == CqDaiban)
|
||
.GroupBy(x => new { x.DepId, x.WorktypeId })
|
||
.Select(g => new { Hours = g.Sum(x => x.Hours), g.Key.WorktypeId }).ToList();
|
||
foreach (var g in groups)
|
||
{
|
||
var cfg = confGzAll.FirstOrDefault(x => x.WorkTypeId == g.WorktypeId);
|
||
if (cfg == null) continue;
|
||
var std = cfg.QqDaysStandard * cfg.HoursPerday;
|
||
if (std == 0) continue;
|
||
jine += cfg.JineGangwei / std * g.Hours;
|
||
}
|
||
return R2(jine);
|
||
}
|
||
|
||
/// <summary>出勤天次:(出勤 + 加班) > 0 的天数</summary>
|
||
private static decimal jisuanCqTianci(List<Cqdj> list)
|
||
=> list.Count(x => x.HoursCq + x.HoursJb > 0);
|
||
|
||
/// <summary>法定假补助:补助小时 × 补助单价 + 工资小时 × 工资单价</summary>
|
||
private static decimal jisuanFadingBz(decimal hoursFadingBz, decimal hoursFadingGz, ConfGz cfg)
|
||
{
|
||
if (hoursFadingBz + hoursFadingGz <= 0) return 0;
|
||
return R2(hoursFadingBz * cfg.JiaqiJineFadingBz + hoursFadingGz * cfg.JiaqiJineFadingGz);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 全勤奖(对齐 Page_Cqtj.quanqin)。
|
||
/// sumCqHours / sumQjHours / sumKgHours 为该职工全部门合计;depCqHours / depHoursQj 为本部门。
|
||
/// </summary>
|
||
private decimal quanqin(ConfGz cfg, decimal sumCqHours, decimal sumQjHours, decimal depCqHours, decimal hoursJb,
|
||
decimal depHoursQj, decimal qqJisuanQjHours, decimal daixinQjHours, ConfEmployee confEmp,
|
||
decimal sumDaixinHours, decimal sumCqHours2, decimal sumKgHours, SysEmp.Employee emp,
|
||
DateTime zqStart, DateTime zqEnd, decimal qsDays, SysDept.Department dep, decimal sumJiabanHours)
|
||
{
|
||
var hoursPerday = cfg.HoursPerday;
|
||
var jineQuanqin = confEmp != null && confEmp.IsXuegong ? cfg.XgJineQuanqin : cfg.JineQuanqin;
|
||
var qqHoursMaxQj = cfg.QqHoursMaxQj;
|
||
var standardCqHours = cfg.QqDaysStandard * hoursPerday;
|
||
if (hoursPerday <= 0) return 0;
|
||
|
||
var ruzhi = emp?.RuzhiDate;
|
||
var lizhi = emp?.LizhiDate;
|
||
bool InPeriod(DateTime? d) => d.HasValue && d.Value.Date >= zqStart.Date && d.Value.Date <= zqEnd.Date;
|
||
|
||
// 1、总旷工超过半天(含半天)不计全勤
|
||
if (sumKgHours >= hoursPerday / 2) return 0;
|
||
// 2、出勤总时长小于等于 3 天不计全勤
|
||
if (sumCqHours <= hoursPerday * 3) return 0;
|
||
|
||
if (sumCqHours != depCqHours) // 存在多部门 / 多工种出勤
|
||
{
|
||
var shijiCqDays = (sumCqHours + sumDaixinHours) / hoursPerday;
|
||
if (InPeriod(lizhi))
|
||
return standardCqHours == 0 ? 0 : R2(jineQuanqin / standardCqHours * depCqHours);
|
||
|
||
if (InPeriod(ruzhi))
|
||
{
|
||
if (shijiCqDays >= qsDays) return sumCqHours == 0 ? 0 : jineQuanqin / sumCqHours * depCqHours;
|
||
if (shijiCqDays < 3) return 0;
|
||
return standardCqHours == 0 ? 0 : R2(jineQuanqin / standardCqHours * depCqHours);
|
||
}
|
||
|
||
var chushi = (sumCqHours + sumDaixinHours) / hoursPerday >= 15
|
||
? (sumCqHours == 0 ? 0 : jineQuanqin / sumCqHours * depCqHours)
|
||
: (standardCqHours == 0 ? 0 : jineQuanqin / standardCqHours * depCqHours);
|
||
|
||
if (qqJisuanQjHours >= qqHoursMaxQj) return 0;
|
||
return qqHoursMaxQj == 0 ? 0 : R2(chushi - (chushi / qqHoursMaxQj) * qqJisuanQjHours);
|
||
}
|
||
|
||
var shijiDays = (sumCqHours + sumDaixinHours) / hoursPerday;
|
||
if (InPeriod(lizhi))
|
||
return standardCqHours == 0 ? 0 : R2(jineQuanqin / standardCqHours * sumCqHours);
|
||
|
||
if (InPeriod(ruzhi))
|
||
{
|
||
if (shijiDays >= qsDays) return jineQuanqin;
|
||
if (shijiDays < 3) return 0;
|
||
return standardCqHours == 0 ? 0 : R2(jineQuanqin / standardCqHours * sumCqHours);
|
||
}
|
||
|
||
var chushi2 = (sumCqHours + daixinQjHours) / hoursPerday >= 15
|
||
? jineQuanqin
|
||
: (standardCqHours == 0 ? 0 : jineQuanqin / standardCqHours * sumCqHours);
|
||
|
||
// 设备办全职:出勤含加班达到前缫出勤天数且不低于标准全勤时长时,请假不扣全勤
|
||
if (dep != null && dep.DutyType == Department_DutyType_Enum.设备办)
|
||
{
|
||
var cqhours = sumCqHours + daixinQjHours + sumJiabanHours;
|
||
if (cqhours / hoursPerday >= qsDays && cqhours >= standardCqHours) return R2(chushi2);
|
||
}
|
||
|
||
if (qqJisuanQjHours >= qqHoursMaxQj) return 0;
|
||
return qqHoursMaxQj == 0 ? 0 : R2(chushi2 - (chushi2 / qqHoursMaxQj) * depHoursQj);
|
||
}
|
||
|
||
/// <summary>本部门在职职工(按工种 / 班次过滤,排除已离职、未入职、工种为 0)</summary>
|
||
private async Task<List<SysEmp.Employee>> LoadEmployeesAsync(long depId, long worktypeId, int banci)
|
||
{
|
||
var all = await _empRepo.GetAllAsync();
|
||
return all.Where(e => e.DepId == depId
|
||
&& e.WorktypeId != 0
|
||
&& e.WorkStatus != Employee_WorkStatus_Enum.离职
|
||
&& (worktypeId <= 0 || e.WorktypeId == worktypeId)
|
||
&& (banci <= 0 || e.Banci == null || (int)e.Banci == banci))
|
||
.OrderBy(e => e.Code).ThenBy(e => e.Name)
|
||
.ToList();
|
||
}
|
||
|
||
/// <summary>出勤时长 = 正常 + 流动 + 代班 + 外派 + 工伤(不含加班)</summary>
|
||
private static decimal CalcHoursCq(List<CqdjItemDto> items)
|
||
=> items.Where(x => x.TypeCq == CqZhengchang || x.TypeCq == CqLiudong || x.TypeCq == CqDaiban
|
||
|| x.TypeCq == CqWaipai || x.TypeCq == CqGongshang)
|
||
.Sum(x => x.Hours);
|
||
|
||
private static decimal Sum(List<CqdjItemDto> items, int typeCq)
|
||
=> items.Where(x => x.TypeCq == typeCq).Sum(x => x.Hours);
|
||
|
||
private static CqdjItemDto ToItemDto(CqdjItem x) => new()
|
||
{
|
||
Id = x.Id,
|
||
DepId = x.DepId,
|
||
WorktypeId = x.WorktypeId,
|
||
TypeCq = x.TypeCq,
|
||
TypeQj = x.TypeQj,
|
||
Hours = x.Hours,
|
||
Jizu = x.Jizu ?? "",
|
||
CtStart = x.CtStart ?? "",
|
||
CtEnd = x.CtEnd ?? "",
|
||
DangcheNum = x.DangcheNum,
|
||
Memo = x.Memo ?? "",
|
||
};
|
||
}
|