新增工资/仓储/生产日报等模块,移除旧考勤模块
- 新增 PayRoll(工资)、Wms(仓储)、Prod(车速/缫丝/考勤/丝片检测/丝片台账/纤检日报/质量日报) 模块 - 新增对应 Domain 实体、Application DTO/Service、Api Controller 与前端页面 - 移除旧 Attendance 考勤模块(Controller/Service/Entity/DTO 及 5 个前端页面),由 Prod/Kaoqin 取代 - 调整路由、仪表盘、Lsjl 流丝记录实体与服务 - 更新业务种子数据与 DbSeeder - 补充工作进度文档 14-27
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.Prod;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.Base;
|
||||
using F9WebAiTest.Domain.Entities.Prod;
|
||||
using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.Prod;
|
||||
|
||||
/// <summary>秤大丝:以落丝记录为主表,按车台录入 4 个丝量值并汇总把数/丝量/缫折/台产</summary>
|
||||
public interface ICdsService
|
||||
{
|
||||
Task<PageResult<CdsListItemDto>> GetPagedAsync(int pageIndex, int pageSize, DateTime? date, long? deptId);
|
||||
Task<CdsEditDto> GetEditAsync(long lsjlId);
|
||||
Task SaveAsync(CdsSaveDto dto);
|
||||
Task DeleteAsync(long lsjlId);
|
||||
}
|
||||
|
||||
public class CdsService : ICdsService
|
||||
{
|
||||
private readonly IRepository<LsjlRecord> _lsjlRepo;
|
||||
private readonly IRepository<CdsLsjl> _cdsLsjlRepo;
|
||||
private readonly IRepository<CdsChetai> _cdsChetaiRepo;
|
||||
private readonly IRepository<SysDept.Department> _deptRepo;
|
||||
private readonly IRepository<CraftZhuangKou> _zkRepo;
|
||||
|
||||
public CdsService(
|
||||
IRepository<LsjlRecord> lsjlRepo,
|
||||
IRepository<CdsLsjl> cdsLsjlRepo,
|
||||
IRepository<CdsChetai> cdsChetaiRepo,
|
||||
IRepository<SysDept.Department> deptRepo,
|
||||
IRepository<CraftZhuangKou> zkRepo)
|
||||
{
|
||||
_lsjlRepo = lsjlRepo;
|
||||
_cdsLsjlRepo = cdsLsjlRepo;
|
||||
_cdsChetaiRepo = cdsChetaiRepo;
|
||||
_deptRepo = deptRepo;
|
||||
_zkRepo = zkRepo;
|
||||
}
|
||||
|
||||
public async Task<PageResult<CdsListItemDto>> GetPagedAsync(int pageIndex, int pageSize, DateTime? date, long? deptId)
|
||||
{
|
||||
var lsjls = await _lsjlRepo.GetAllAsync();
|
||||
var q = lsjls.AsQueryable();
|
||||
if (date.HasValue) q = q.Where(x => x.WorkDate.Date == date.Value.Date);
|
||||
if (deptId.HasValue) q = q.Where(x => x.DeptId == deptId.Value);
|
||||
|
||||
var depts = await _deptRepo.GetAllAsync();
|
||||
var deptDict = depts.ToDictionary(d => d.Id, d => d.Name);
|
||||
var zks = await _zkRepo.GetAllAsync();
|
||||
var zkDict = zks.ToDictionary(z => z.Id, z => z.ZhuangKouName);
|
||||
var sums = (await _cdsLsjlRepo.GetAllAsync()).ToDictionary(x => x.LsjlId);
|
||||
|
||||
var total = q.LongCount();
|
||||
var items = q.OrderByDescending(x => x.WorkDate).ThenBy(x => x.Jizu).ThenBy(x => x.Huishu)
|
||||
.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
return new PageResult<CdsListItemDto>
|
||||
{
|
||||
Items = items.Select(x =>
|
||||
{
|
||||
sums.TryGetValue(x.Id, out var sum);
|
||||
return new CdsListItemDto
|
||||
{
|
||||
LsjlId = x.Id,
|
||||
WorkDate = x.WorkDate,
|
||||
DeptId = x.DeptId,
|
||||
DeptName = deptDict.GetValueOrDefault(x.DeptId, ""),
|
||||
Jizu = x.Jizu,
|
||||
Huishu = x.Huishu,
|
||||
Guige = x.Guige,
|
||||
ZhuangKouName = zkDict.GetValueOrDefault(x.GyzkId, ""),
|
||||
Yanse = x.Yanse,
|
||||
Isbanche = x.Isbanche,
|
||||
IsbancheName = x.Isbanche ? "半车" : "整车",
|
||||
Chetaishu = x.Chetaishu,
|
||||
ChetaiStart = x.ChetaiStart,
|
||||
ChetaiEnd = x.ChetaiEnd,
|
||||
Hours = x.Hours,
|
||||
Tongshu = x.Tongshu,
|
||||
Meitongjianliang = x.Meitongjianliang,
|
||||
YingchengNum = YingchengNum(x),
|
||||
IsWeighed = sum != null,
|
||||
Num = sum?.Num ?? 0,
|
||||
Siliang = sum?.Siliang ?? 0,
|
||||
Jianliang = sum?.Jianliang ?? 0,
|
||||
Saozhe = sum?.Saozhe ?? 0,
|
||||
Taichan = sum?.Taichan ?? 0
|
||||
};
|
||||
}).ToList(),
|
||||
Total = total, PageIndex = pageIndex, PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<CdsEditDto> GetEditAsync(long lsjlId)
|
||||
{
|
||||
var lsjl = await _lsjlRepo.GetByIdAsync(lsjlId);
|
||||
if (lsjl == null) throw new ServiceException("落丝记录不存在", 404);
|
||||
|
||||
var dept = await _deptRepo.GetByIdAsync(lsjl.DeptId);
|
||||
var zk = await _zkRepo.GetByIdAsync(lsjl.GyzkId);
|
||||
|
||||
var records = await _cdsChetaiRepo.FindAsync(x => x.LsjlId == lsjlId);
|
||||
List<CdsChetaiDto> chetais;
|
||||
if (records.Count > 0)
|
||||
{
|
||||
chetais = records.OrderBy(r => r.Chetaihao).Select(ToDto).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 首次称重:按车台范围初始化空行
|
||||
chetais = new List<CdsChetaiDto>();
|
||||
for (var i = lsjl.ChetaiStart; i <= lsjl.ChetaiEnd; i++)
|
||||
chetais.Add(new CdsChetaiDto { Chetaihao = i });
|
||||
}
|
||||
|
||||
return new CdsEditDto
|
||||
{
|
||||
LsjlId = lsjl.Id,
|
||||
DeptName = dept?.Name ?? "",
|
||||
Jizu = lsjl.Jizu,
|
||||
Huishu = lsjl.Huishu,
|
||||
ZhuangKouName = zk?.ZhuangKouName ?? "",
|
||||
Yanse = lsjl.Yanse,
|
||||
Isbanche = lsjl.Isbanche,
|
||||
IsbancheName = lsjl.Isbanche ? "半车" : "整车",
|
||||
Guige = lsjl.Guige,
|
||||
ChetaiStart = lsjl.ChetaiStart,
|
||||
ChetaiEnd = lsjl.ChetaiEnd,
|
||||
Chetaishu = lsjl.Chetaishu,
|
||||
YingchengNum = YingchengNum(lsjl),
|
||||
Chetais = chetais
|
||||
};
|
||||
}
|
||||
|
||||
public async Task SaveAsync(CdsSaveDto dto)
|
||||
{
|
||||
if (dto.LsjlId == 0) throw new ServiceException("落丝记录无效");
|
||||
var lsjl = await _lsjlRepo.GetByIdAsync(dto.LsjlId);
|
||||
if (lsjl == null) throw new ServiceException("落丝记录不存在", 404);
|
||||
|
||||
if (dto.Chetais == null || dto.Chetais.Count == 0) throw new ServiceException("请先录入丝量数据");
|
||||
|
||||
var invalid = dto.Chetais.Where(i => i.Chetaihao < lsjl.ChetaiStart || i.Chetaihao > lsjl.ChetaiEnd).ToList();
|
||||
if (invalid.Count > 0) throw new ServiceException($"车台号超出范围 [{lsjl.ChetaiStart}-{lsjl.ChetaiEnd}]");
|
||||
|
||||
// 车台明细:个数 = 丝量 > 0 的个数;小计丝量 = 4 个丝量之和
|
||||
var items = dto.Chetais.Select(i =>
|
||||
{
|
||||
var t = new CdsChetaiDto
|
||||
{
|
||||
Chetaihao = i.Chetaihao,
|
||||
Sl1 = Math.Max(0, i.Sl1), Sl2 = Math.Max(0, i.Sl2),
|
||||
Sl3 = Math.Max(0, i.Sl3), Sl4 = Math.Max(0, i.Sl4)
|
||||
};
|
||||
CalChetai(t);
|
||||
return t;
|
||||
}).OrderBy(i => i.Chetaihao).ToList();
|
||||
|
||||
var num = items.Sum(x => x.Num);
|
||||
if (num == 0) throw new ServiceException("请至少录入一个丝量值");
|
||||
|
||||
// 车台明细先删后建
|
||||
var olds = await _cdsChetaiRepo.FindAsync(x => x.LsjlId == dto.LsjlId);
|
||||
if (olds.Count > 0) await _cdsChetaiRepo.HardDeleteRangeAsync(olds);
|
||||
|
||||
foreach (var t in items)
|
||||
{
|
||||
await _cdsChetaiRepo.AddAsync(new CdsChetai
|
||||
{
|
||||
LsjlId = dto.LsjlId,
|
||||
Chetaihao = t.Chetaihao,
|
||||
Sl1 = t.Sl1, Sl2 = t.Sl2, Sl3 = t.Sl3, Sl4 = t.Sl4,
|
||||
Num = t.Num, Siliang = t.Siliang
|
||||
});
|
||||
}
|
||||
|
||||
var siliang = items.Sum(x => x.Siliang);
|
||||
|
||||
// 汇总:茧量 / 缫折 / 实绩台产
|
||||
var cdsLsjl = await _cdsLsjlRepo.FirstOrDefaultAsync(x => x.LsjlId == dto.LsjlId);
|
||||
if (cdsLsjl == null)
|
||||
{
|
||||
cdsLsjl = new CdsLsjl { LsjlId = dto.LsjlId };
|
||||
await _cdsLsjlRepo.AddAsync(cdsLsjl);
|
||||
}
|
||||
cdsLsjl.Num = num;
|
||||
cdsLsjl.Siliang = siliang;
|
||||
CalZhibiao(cdsLsjl, lsjl);
|
||||
await _cdsLsjlRepo.UpdateAsync(cdsLsjl);
|
||||
}
|
||||
|
||||
/// <summary>清除称重数据(汇总 + 车台明细物理删除,便于重新称重)</summary>
|
||||
public async Task DeleteAsync(long lsjlId)
|
||||
{
|
||||
if (lsjlId == 0) throw new ServiceException("落丝记录无效");
|
||||
var chetais = await _cdsChetaiRepo.FindAsync(x => x.LsjlId == lsjlId);
|
||||
if (chetais.Count > 0) await _cdsChetaiRepo.HardDeleteRangeAsync(chetais);
|
||||
|
||||
var lsjlSum = await _cdsLsjlRepo.FirstOrDefaultAsync(x => x.LsjlId == lsjlId);
|
||||
if (lsjlSum != null) await _cdsLsjlRepo.HardDeleteAsync(lsjlSum);
|
||||
}
|
||||
|
||||
// ==================== 计算口径(对齐 F9 Cds_Lsjl_CalZhibiaoCommand) ====================
|
||||
|
||||
/// <summary>应秤数 = 车台数 × 4 ×(半车 0.5 / 整车 1)</summary>
|
||||
private static int YingchengNum(LsjlRecord lsjl)
|
||||
=> (int)Math.Round(lsjl.Chetaishu * 4 * (lsjl.Isbanche ? 0.5 : 1), MidpointRounding.AwayFromZero);
|
||||
|
||||
private static void CalChetai(CdsChetaiDto item)
|
||||
{
|
||||
item.Num = (item.Sl1 > 0 ? 1 : 0) + (item.Sl2 > 0 ? 1 : 0) + (item.Sl3 > 0 ? 1 : 0) + (item.Sl4 > 0 ? 1 : 0);
|
||||
item.Siliang = item.Sl1 + item.Sl2 + item.Sl3 + item.Sl4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 茧量 = 落丝桶数 × 每桶茧量;缫折 = 茧量 / 丝量 × 100;实绩台产 = 丝量 / 落丝时长 / 车台数。
|
||||
/// F9 口径:缫折 ≤ 100 视为异常数据,不落指标。
|
||||
/// </summary>
|
||||
private static void CalZhibiao(CdsLsjl target, LsjlRecord lsjl)
|
||||
{
|
||||
if (lsjl.Meitongjianliang == 0 || lsjl.Tongshu == 0)
|
||||
{
|
||||
target.Jianliang = 0; target.Saozhe = 0; target.Taichan = 0;
|
||||
return;
|
||||
}
|
||||
if (target.Num <= 0 || target.Siliang <= 0 || lsjl.Hours <= 0 || lsjl.Chetaishu <= 0)
|
||||
{
|
||||
target.Jianliang = 0; target.Saozhe = 0; target.Taichan = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var jianliang = lsjl.Tongshu * lsjl.Meitongjianliang;
|
||||
var saozhe = Math.Round((decimal)jianliang / target.Siliang * 100, 2, MidpointRounding.AwayFromZero);
|
||||
var taichan = Math.Round((decimal)target.Siliang / (decimal)lsjl.Hours / lsjl.Chetaishu, 2, MidpointRounding.AwayFromZero);
|
||||
|
||||
if (saozhe > 100)
|
||||
{
|
||||
target.Jianliang = jianliang;
|
||||
target.Saozhe = saozhe;
|
||||
target.Taichan = taichan;
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Jianliang = jianliang;
|
||||
target.Saozhe = 0;
|
||||
target.Taichan = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static CdsChetaiDto ToDto(CdsChetai r) => new()
|
||||
{
|
||||
Chetaihao = r.Chetaihao,
|
||||
Sl1 = r.Sl1, Sl2 = r.Sl2, Sl3 = r.Sl3, Sl4 = r.Sl4,
|
||||
Num = r.Num, Siliang = r.Siliang
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.Prod;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.Base;
|
||||
using F9WebAiTest.Domain.Entities.Prod;
|
||||
using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.Prod;
|
||||
|
||||
/// <summary>公量纤度:以落丝记录为主表,取纤度检验的平均纤度,由质检录入公量纤度(按日期 + 车间整日批量保存)</summary>
|
||||
public interface IGlxdService
|
||||
{
|
||||
Task<List<GlxdListItemDto>> GetListAsync(DateTime? date, long? deptId);
|
||||
Task SaveAsync(GlxdSaveDto dto);
|
||||
}
|
||||
|
||||
public class GlxdService : IGlxdService
|
||||
{
|
||||
private readonly IRepository<LsjlRecord> _lsjlRepo;
|
||||
private readonly IRepository<Glxd> _glxdRepo;
|
||||
private readonly IRepository<XdjyLsjl> _xdjyLsjlRepo;
|
||||
private readonly IRepository<SysDept.Department> _deptRepo;
|
||||
private readonly IRepository<CraftZhuangKou> _zkRepo;
|
||||
|
||||
public GlxdService(
|
||||
IRepository<LsjlRecord> lsjlRepo,
|
||||
IRepository<Glxd> glxdRepo,
|
||||
IRepository<XdjyLsjl> xdjyLsjlRepo,
|
||||
IRepository<SysDept.Department> deptRepo,
|
||||
IRepository<CraftZhuangKou> zkRepo)
|
||||
{
|
||||
_lsjlRepo = lsjlRepo;
|
||||
_glxdRepo = glxdRepo;
|
||||
_xdjyLsjlRepo = xdjyLsjlRepo;
|
||||
_deptRepo = deptRepo;
|
||||
_zkRepo = zkRepo;
|
||||
}
|
||||
|
||||
public async Task<List<GlxdListItemDto>> GetListAsync(DateTime? date, long? deptId)
|
||||
{
|
||||
var lsjls = await _lsjlRepo.GetAllAsync();
|
||||
var q = lsjls.AsQueryable();
|
||||
if (date.HasValue) q = q.Where(x => x.WorkDate.Date == date.Value.Date);
|
||||
if (deptId.HasValue) q = q.Where(x => x.DeptId == deptId.Value);
|
||||
|
||||
var items = q.OrderBy(x => x.DeptId).ThenBy(x => x.Jizu).ThenBy(x => x.Huishu).ToList();
|
||||
if (items.Count == 0) return new List<GlxdListItemDto>();
|
||||
|
||||
var depts = await _deptRepo.GetAllAsync();
|
||||
var deptDict = depts.ToDictionary(d => d.Id, d => d.Name);
|
||||
var zks = await _zkRepo.GetAllAsync();
|
||||
var zkDict = zks.ToDictionary(z => z.Id, z => z.ZhuangKouName);
|
||||
var glxds = (await _glxdRepo.GetAllAsync()).ToDictionary(x => x.LsjlId);
|
||||
var xdjySums = (await _xdjyLsjlRepo.GetAllAsync()).ToDictionary(x => x.LsjlId);
|
||||
|
||||
return items.Select(x =>
|
||||
{
|
||||
glxds.TryGetValue(x.Id, out var g);
|
||||
xdjySums.TryGetValue(x.Id, out var xs);
|
||||
return new GlxdListItemDto
|
||||
{
|
||||
LsjlId = x.Id,
|
||||
WorkDate = x.WorkDate,
|
||||
DeptId = x.DeptId,
|
||||
DeptName = deptDict.GetValueOrDefault(x.DeptId, ""),
|
||||
Jizu = x.Jizu,
|
||||
Huishu = x.Huishu,
|
||||
Guige = x.Guige,
|
||||
ZhuangKouName = zkDict.GetValueOrDefault(x.GyzkId, ""),
|
||||
Yanse = x.Yanse,
|
||||
Isbanche = x.Isbanche,
|
||||
IsbancheName = x.Isbanche ? "半车" : "整车",
|
||||
Chetaishu = x.Chetaishu,
|
||||
Avgxiandu = xs?.Avgxiandu ?? 0,
|
||||
Gongliangxiandu = g?.Gongliangxiandu ?? 0
|
||||
};
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task SaveAsync(GlxdSaveDto dto)
|
||||
{
|
||||
if (dto.Items == null || dto.Items.Count == 0) throw new ServiceException("没有需要保存的公量纤度");
|
||||
|
||||
// 校验落丝记录存在,并按落丝记录 Id 去重
|
||||
var dict = new Dictionary<long, decimal>();
|
||||
foreach (var it in dto.Items)
|
||||
{
|
||||
if (it.LsjlId == 0) throw new ServiceException("存在无效的落丝记录");
|
||||
var lsjl = await _lsjlRepo.GetByIdAsync(it.LsjlId);
|
||||
if (lsjl == null) throw new ServiceException($"落丝记录 {it.LsjlId} 不存在", 404);
|
||||
dict[it.LsjlId] = it.Gongliangxiandu;
|
||||
}
|
||||
|
||||
foreach (var (lsjlId, value) in dict)
|
||||
{
|
||||
var e = await _glxdRepo.FirstOrDefaultAsync(x => x.LsjlId == lsjlId);
|
||||
if (e == null)
|
||||
{
|
||||
e = new Glxd { LsjlId = lsjlId, Gongliangxiandu = value };
|
||||
await _glxdRepo.AddAsync(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Gongliangxiandu = value;
|
||||
await _glxdRepo.UpdateAsync(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,890 @@
|
||||
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 ?? "",
|
||||
};
|
||||
}
|
||||
@@ -96,6 +96,7 @@ public class LsjlService : ILsjlService
|
||||
e.LuosiTime = d.LuosiTime; e.Yanse = d.Yanse; e.Huishu = d.Huishu;
|
||||
e.ChetaiStart = d.ChetaiStart; e.ChetaiEnd = d.ChetaiEnd; e.Chetaishu = d.Chetaishu;
|
||||
e.Isbanche = d.Isbanche; e.Hours = d.Hours; e.Guige = d.Guige; e.Chesu = d.Chesu;
|
||||
e.Tongshu = d.Tongshu; e.Meitongjianliang = d.Meitongjianliang;
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -110,7 +111,8 @@ public class LsjlService : ILsjlService
|
||||
LuosiTime = e.LuosiTime, Yanse = e.Yanse, Huishu = e.Huishu,
|
||||
ChetaiStart = e.ChetaiStart, ChetaiEnd = e.ChetaiEnd, Chetaishu = e.Chetaishu,
|
||||
Isbanche = e.Isbanche, IsbancheName = e.Isbanche ? "半车" : "整车",
|
||||
Hours = e.Hours, Guige = e.Guige, Chesu = e.Chesu
|
||||
Hours = e.Hours, Tongshu = e.Tongshu, Meitongjianliang = e.Meitongjianliang,
|
||||
Guige = e.Guige, Chesu = e.Chesu
|
||||
};
|
||||
}
|
||||
|
||||
@@ -123,7 +125,8 @@ public class LsjlService : ILsjlService
|
||||
LuosiTime = e.LuosiTime, Yanse = e.Yanse, Huishu = e.Huishu,
|
||||
ChetaiStart = e.ChetaiStart, ChetaiEnd = e.ChetaiEnd, Chetaishu = e.Chetaishu,
|
||||
Isbanche = e.Isbanche, IsbancheName = e.Isbanche ? "半车" : "整车",
|
||||
Hours = e.Hours, Guige = e.Guige, Chesu = e.Chesu
|
||||
Hours = e.Hours, Tongshu = e.Tongshu, Meitongjianliang = e.Meitongjianliang,
|
||||
Guige = e.Guige, Chesu = e.Chesu
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.Prod;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.Base;
|
||||
using F9WebAiTest.Domain.Entities.Prod;
|
||||
using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.Prod;
|
||||
|
||||
/// <summary>丝片检查:按「日期 + 车间 + 工艺庄口 + 规格」记录前缫/后缫疵点片数</summary>
|
||||
public interface ISpjcService
|
||||
{
|
||||
Task<PageResult<SpjcListItemDto>> GetPagedAsync(int pageIndex, int pageSize, DateTime? date, long? deptId, string like);
|
||||
Task<SpjcSaveDto> GetForEditAsync(long id);
|
||||
Task<long> SaveAsync(SpjcSaveDto dto);
|
||||
Task DeleteAsync(long id);
|
||||
}
|
||||
|
||||
public class SpjcService : ISpjcService
|
||||
{
|
||||
private readonly IRepository<Spjc> _spjcRepo;
|
||||
private readonly IRepository<SysDept.Department> _deptRepo;
|
||||
private readonly IRepository<CraftZhuangKou> _zkRepo;
|
||||
|
||||
public SpjcService(
|
||||
IRepository<Spjc> spjcRepo,
|
||||
IRepository<SysDept.Department> deptRepo,
|
||||
IRepository<CraftZhuangKou> zkRepo)
|
||||
{
|
||||
_spjcRepo = spjcRepo;
|
||||
_deptRepo = deptRepo;
|
||||
_zkRepo = zkRepo;
|
||||
}
|
||||
|
||||
public async Task<PageResult<SpjcListItemDto>> GetPagedAsync(int pageIndex, int pageSize, DateTime? date, long? deptId, string like)
|
||||
{
|
||||
var all = await _spjcRepo.GetAllAsync();
|
||||
var q = all.AsQueryable();
|
||||
if (date.HasValue) q = q.Where(x => x.Date.Date == date.Value.Date);
|
||||
if (deptId.HasValue) q = q.Where(x => x.DeptId == deptId.Value);
|
||||
|
||||
var depts = await _deptRepo.GetAllAsync();
|
||||
var deptDict = depts.ToDictionary(d => d.Id, d => d.Name);
|
||||
var zks = await _zkRepo.GetAllAsync();
|
||||
var zkDict = zks.ToDictionary(z => z.Id, z => z.ZhuangKouName);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(like))
|
||||
{
|
||||
var kwDeptIds = depts.Where(d => d.Name.Contains(like)).Select(d => d.Id).ToHashSet();
|
||||
var kwZkIds = zks.Where(z => z.ZhuangKouName.Contains(like)).Select(z => z.Id).ToHashSet();
|
||||
q = q.Where(x => kwDeptIds.Contains(x.DeptId) || kwZkIds.Contains(x.GyzkId) || x.Guige.Contains(like));
|
||||
}
|
||||
|
||||
var total = q.LongCount();
|
||||
var items = q.OrderByDescending(x => x.Date).ThenByDescending(x => x.Id)
|
||||
.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
return new PageResult<SpjcListItemDto>
|
||||
{
|
||||
Items = items.Select(x => ToListDto(x, deptDict, zkDict)).ToList(),
|
||||
Total = total, PageIndex = pageIndex, PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<SpjcSaveDto> GetForEditAsync(long id)
|
||||
{
|
||||
var e = await _spjcRepo.GetByIdAsync(id);
|
||||
if (e == null) throw new ServiceException("丝片检查不存在或已删除", 404);
|
||||
|
||||
var dept = await _deptRepo.GetByIdAsync(e.DeptId);
|
||||
var zk = await _zkRepo.GetByIdAsync(e.GyzkId);
|
||||
|
||||
return new SpjcSaveDto
|
||||
{
|
||||
Id = e.Id,
|
||||
Date = e.Date,
|
||||
DeptId = e.DeptId,
|
||||
DeptName = dept?.Name ?? "",
|
||||
GyzkId = e.GyzkId,
|
||||
GyzkName = zk?.ZhuangKouName ?? "",
|
||||
Guige = e.Guige,
|
||||
Pianshu = e.Pianshu,
|
||||
PianshuXuegong = e.PianshuXuegong,
|
||||
QsJiahua = e.QsJiahua, QsYouwu = e.QsYouwu, QsHeidian = e.QsHeidian,
|
||||
QsShangsi = e.QsShangsi, QsYetiao = e.QsYetiao, QsXiaosipian = e.QsXiaosipian, QsQita = e.QsQita,
|
||||
HsYouwu = e.HsYouwu, HsShangsi = e.HsShangsi, HsShuangsi = e.HsShuangsi, HsZhisi = e.HsZhisi,
|
||||
HsZhantiao = e.HsZhantiao, HsYingjiao = e.HsYingjiao, HsGuojiao = e.HsGuojiao,
|
||||
HsShouchu = e.HsShouchu, HsWuran = e.HsWuran, HsLiansi = e.HsLiansi, HsWdmt = e.HsWdmt,
|
||||
HsMaoluan = e.HsMaoluan, HsQita = e.HsQita,
|
||||
Memo = e.Memo
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<long> SaveAsync(SpjcSaveDto dto)
|
||||
{
|
||||
if (dto.DeptId == 0) throw new ServiceException("请选择车间");
|
||||
if (dto.GyzkId == 0) throw new ServiceException("请选择工艺庄口");
|
||||
if (string.IsNullOrWhiteSpace(dto.Guige)) throw new ServiceException("请选择规格");
|
||||
|
||||
var date = dto.Date.Date;
|
||||
var guige = dto.Guige.Trim();
|
||||
|
||||
Spjc e;
|
||||
if (dto.Id > 0)
|
||||
{
|
||||
e = await _spjcRepo.GetByIdAsync(dto.Id);
|
||||
if (e == null) throw new ServiceException("丝片检查不存在或已删除", 404);
|
||||
|
||||
var dup = await _spjcRepo.FirstOrDefaultAsync(x => x.Id != dto.Id && x.Date.Date == date
|
||||
&& x.DeptId == dto.DeptId && x.GyzkId == dto.GyzkId
|
||||
&& x.Guige == guige);
|
||||
if (dup != null) throw new ServiceException("已存在相同的丝片检查!");
|
||||
}
|
||||
else
|
||||
{
|
||||
var exists = await _spjcRepo.AnyAsync(x => x.Date.Date == date && x.DeptId == dto.DeptId
|
||||
&& x.GyzkId == dto.GyzkId && x.Guige == guige);
|
||||
if (exists) throw new ServiceException("已存在相同的丝片检查!");
|
||||
e = new Spjc();
|
||||
await _spjcRepo.AddAsync(e);
|
||||
}
|
||||
|
||||
e.Date = date;
|
||||
e.DeptId = dto.DeptId;
|
||||
e.GyzkId = dto.GyzkId;
|
||||
e.Guige = guige;
|
||||
e.Pianshu = dto.Pianshu;
|
||||
e.PianshuXuegong = dto.PianshuXuegong;
|
||||
e.QsJiahua = dto.QsJiahua;
|
||||
e.QsYouwu = dto.QsYouwu;
|
||||
e.QsHeidian = dto.QsHeidian;
|
||||
e.QsShangsi = dto.QsShangsi;
|
||||
e.QsYetiao = dto.QsYetiao;
|
||||
e.QsXiaosipian = dto.QsXiaosipian;
|
||||
e.QsQita = dto.QsQita;
|
||||
e.HsYouwu = dto.HsYouwu;
|
||||
e.HsShangsi = dto.HsShangsi;
|
||||
e.HsShuangsi = dto.HsShuangsi;
|
||||
e.HsZhisi = dto.HsZhisi;
|
||||
e.HsZhantiao = dto.HsZhantiao;
|
||||
e.HsYingjiao = dto.HsYingjiao;
|
||||
e.HsGuojiao = dto.HsGuojiao;
|
||||
e.HsShouchu = dto.HsShouchu;
|
||||
e.HsWuran = dto.HsWuran;
|
||||
e.HsLiansi = dto.HsLiansi;
|
||||
e.HsWdmt = dto.HsWdmt;
|
||||
e.HsMaoluan = dto.HsMaoluan;
|
||||
e.HsQita = dto.HsQita;
|
||||
e.Memo = dto.Memo ?? "";
|
||||
|
||||
// 合计由服务端重算(与 F9 的 Qs_heji / Hs_heji 一致)
|
||||
e.QsHeji = e.QsJiahua + e.QsYouwu + e.QsHeidian + e.QsShangsi + e.QsYetiao + e.QsXiaosipian + e.QsQita;
|
||||
e.HsHeji = e.HsYouwu + e.HsShangsi + e.HsShuangsi + e.HsZhisi + e.HsZhantiao + e.HsYingjiao
|
||||
+ e.HsGuojiao + e.HsShouchu + e.HsWuran + e.HsLiansi + e.HsWdmt + e.HsMaoluan + e.HsQita;
|
||||
|
||||
await _spjcRepo.UpdateAsync(e);
|
||||
return e.Id;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(long id)
|
||||
{
|
||||
var e = await _spjcRepo.GetByIdAsync(id);
|
||||
if (e == null) throw new ServiceException("丝片检查不存在或已删除", 404);
|
||||
// 业务键带唯一索引,物理删除避免「删除后重建」撞索引(与煮茧日报同一策略)
|
||||
await _spjcRepo.HardDeleteAsync(e);
|
||||
}
|
||||
|
||||
private static SpjcListItemDto ToListDto(Spjc x, Dictionary<long, string> deptDict, Dictionary<long, string> zkDict) => new()
|
||||
{
|
||||
Id = x.Id,
|
||||
Date = x.Date,
|
||||
DeptId = x.DeptId,
|
||||
DeptName = deptDict.GetValueOrDefault(x.DeptId, ""),
|
||||
GyzkId = x.GyzkId,
|
||||
GyzkName = zkDict.GetValueOrDefault(x.GyzkId, ""),
|
||||
Guige = x.Guige,
|
||||
Pianshu = x.Pianshu,
|
||||
PianshuXuegong = x.PianshuXuegong,
|
||||
QsJiahua = x.QsJiahua, QsYouwu = x.QsYouwu, QsHeidian = x.QsHeidian,
|
||||
QsShangsi = x.QsShangsi, QsYetiao = x.QsYetiao, QsXiaosipian = x.QsXiaosipian,
|
||||
QsQita = x.QsQita, QsHeji = x.QsHeji,
|
||||
HsYouwu = x.HsYouwu, HsShangsi = x.HsShangsi, HsShuangsi = x.HsShuangsi, HsZhisi = x.HsZhisi,
|
||||
HsZhantiao = x.HsZhantiao, HsYingjiao = x.HsYingjiao, HsGuojiao = x.HsGuojiao,
|
||||
HsShouchu = x.HsShouchu, HsWuran = x.HsWuran, HsLiansi = x.HsLiansi, HsWdmt = x.HsWdmt,
|
||||
HsMaoluan = x.HsMaoluan, HsQita = x.HsQita, HsHeji = x.HsHeji,
|
||||
Memo = x.Memo
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.Prod;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.Prod;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.Prod;
|
||||
|
||||
/// <summary>丝片台帐:每日一条,记录浸丝 / 返丝 / 编丝 / 整理片数</summary>
|
||||
public interface ISptzService
|
||||
{
|
||||
Task<PageResult<SptzListItemDto>> GetPagedAsync(int pageIndex, int pageSize, DateTime dateFrom, DateTime dateTo);
|
||||
Task<SptzSaveDto> GetForEditAsync(long id);
|
||||
Task<SptzDefaultsDto> GetDefaultsAsync(DateTime date);
|
||||
Task<long> SaveAsync(SptzSaveDto dto);
|
||||
Task DeleteAsync(long id);
|
||||
}
|
||||
|
||||
public class SptzService : ISptzService
|
||||
{
|
||||
private readonly IRepository<Sptz> _sptzRepo;
|
||||
private readonly IRepository<Spjc> _spjcRepo;
|
||||
|
||||
public SptzService(IRepository<Sptz> sptzRepo, IRepository<Spjc> spjcRepo)
|
||||
{
|
||||
_sptzRepo = sptzRepo;
|
||||
_spjcRepo = spjcRepo;
|
||||
}
|
||||
|
||||
public async Task<PageResult<SptzListItemDto>> GetPagedAsync(int pageIndex, int pageSize, DateTime dateFrom, DateTime dateTo)
|
||||
{
|
||||
var all = await _sptzRepo.GetAllAsync();
|
||||
var from = dateFrom.Date;
|
||||
var to = dateTo.Date;
|
||||
var q = all.AsQueryable().Where(x => x.Date.Date >= from && x.Date.Date <= to);
|
||||
|
||||
var total = q.LongCount();
|
||||
var items = q.OrderBy(x => x.Date).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
return new PageResult<SptzListItemDto>
|
||||
{
|
||||
Items = items.Select(ToListDto).ToList(),
|
||||
Total = total, PageIndex = pageIndex, PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<SptzSaveDto> GetForEditAsync(long id)
|
||||
{
|
||||
var e = await _sptzRepo.GetByIdAsync(id);
|
||||
if (e == null) throw new ServiceException("丝片台帐不存在或已删除", 404);
|
||||
|
||||
return new SptzSaveDto
|
||||
{
|
||||
Id = e.Id,
|
||||
Date = e.Date,
|
||||
DateLast = e.DateLast,
|
||||
JsShangcun = e.JsShangcun,
|
||||
JsXiasi = e.JsXiasi,
|
||||
JsJinsi = e.JsJinsi,
|
||||
FsFsps = e.FsFsps,
|
||||
BsBsps = e.BsBsps,
|
||||
ZlZhengpin = e.ZlZhengpin,
|
||||
ZlTichu = e.ZlTichu,
|
||||
Memo = e.Memo
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增默认值:上一本台账日期 + 其上存(= 上一日今存),并按当日「丝片检查」汇总出整理正品/剔除片数。
|
||||
/// </summary>
|
||||
public async Task<SptzDefaultsDto> GetDefaultsAsync(DateTime date)
|
||||
{
|
||||
var target = date.Date;
|
||||
var all = await _sptzRepo.GetAllAsync();
|
||||
|
||||
var latest = all.Where(x => x.Date.Date < target).OrderByDescending(x => x.Date).FirstOrDefault();
|
||||
var latestAny = all.OrderByDescending(x => x.Date).FirstOrDefault();
|
||||
|
||||
var spjcs = (await _spjcRepo.GetAllAsync()).Where(x => x.Date.Date == target).ToList();
|
||||
var tichu = spjcs.Sum(x => x.QsJiahua + x.QsYouwu + x.QsHeidian + x.QsShangsi
|
||||
+ x.QsYetiao + x.QsXiaosipian + x.QsQita);
|
||||
var zhengpin = spjcs.Sum(x => x.Pianshu + x.PianshuXuegong) - tichu;
|
||||
|
||||
return new SptzDefaultsDto
|
||||
{
|
||||
Date = target,
|
||||
DateLast = latest?.Date,
|
||||
JsShangcun = latest?.JsJincun ?? 0,
|
||||
LatestDate = latestAny?.Date,
|
||||
ZlTichu = tichu,
|
||||
ZlZhengpin = zhengpin,
|
||||
HasSpjc = spjcs.Count > 0
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<long> SaveAsync(SptzSaveDto dto)
|
||||
{
|
||||
var date = dto.Date.Date;
|
||||
|
||||
Sptz e;
|
||||
if (dto.Id > 0)
|
||||
{
|
||||
e = await _sptzRepo.GetByIdAsync(dto.Id);
|
||||
if (e == null) throw new ServiceException("丝片台帐不存在或已删除", 404);
|
||||
|
||||
var dup = await _sptzRepo.FirstOrDefaultAsync(x => x.Id != dto.Id && x.Date.Date == date);
|
||||
if (dup != null) throw new ServiceException($"已存在 {date:yyyy-MM-dd} 的丝片台帐!");
|
||||
}
|
||||
else
|
||||
{
|
||||
var exists = await _sptzRepo.AnyAsync(x => x.Date.Date == date);
|
||||
if (exists) throw new ServiceException($"已存在 {date:yyyy-MM-dd} 的丝片台帐!");
|
||||
e = new Sptz();
|
||||
await _sptzRepo.AddAsync(e);
|
||||
}
|
||||
|
||||
e.Date = date;
|
||||
e.DateLast = dto.DateLast == default ? date.AddDays(-1) : dto.DateLast.Date;
|
||||
e.JsShangcun = dto.JsShangcun;
|
||||
e.JsXiasi = dto.JsXiasi;
|
||||
e.JsJinsi = dto.JsJinsi;
|
||||
e.FsFsps = dto.FsFsps;
|
||||
e.BsBsps = dto.BsBsps;
|
||||
e.ZlZhengpin = dto.ZlZhengpin;
|
||||
e.ZlTichu = dto.ZlTichu;
|
||||
e.Memo = dto.Memo ?? "";
|
||||
|
||||
// 派生值由服务端重算(与 F9 一致):上日遗留/今日新增 = 上存 − 浸丝;今存 = 下丝 + 上日遗留/今日新增
|
||||
e.JsSrylJrxz = e.JsShangcun - e.JsJinsi;
|
||||
e.JsJincun = e.JsXiasi + e.JsSrylJrxz;
|
||||
|
||||
await _sptzRepo.UpdateAsync(e);
|
||||
return e.Id;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(long id)
|
||||
{
|
||||
var e = await _sptzRepo.GetByIdAsync(id);
|
||||
if (e == null) throw new ServiceException("丝片台帐不存在或已删除", 404);
|
||||
// 日期带唯一索引,物理删除避免「删除后重建」撞索引(与煮茧日报同一策略)
|
||||
await _sptzRepo.HardDeleteAsync(e);
|
||||
}
|
||||
|
||||
private static SptzListItemDto ToListDto(Sptz x) => new()
|
||||
{
|
||||
Id = x.Id,
|
||||
Date = x.Date,
|
||||
DateLast = x.DateLast,
|
||||
JsShangcun = x.JsShangcun,
|
||||
JsXiasi = x.JsXiasi,
|
||||
JsJinsi = x.JsJinsi,
|
||||
JsSrylJrxz = x.JsSrylJrxz,
|
||||
JsJincun = x.JsJincun,
|
||||
FsFsps = x.FsFsps,
|
||||
BsBsps = x.BsBsps,
|
||||
ZlZhengpin = x.ZlZhengpin,
|
||||
ZlTichu = x.ZlTichu,
|
||||
Memo = x.Memo
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,496 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.Prod;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.Base;
|
||||
using F9WebAiTest.Domain.Entities.Prod;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.Prod;
|
||||
|
||||
/// <summary>
|
||||
/// 选剥茧(对应 F9 生产-选剥茧 XBJ):选茧日报(含盈亏/联统率)、选茧计划及其明细、选茧班组及职工。
|
||||
/// </summary>
|
||||
public interface IXjrbService
|
||||
{
|
||||
// ---- 选茧日报 ----
|
||||
Task<PageResult<XjrbListDto>> GetPagedAsync(DateTime? start, DateTime? end, string like, int pageIndex, int pageSize);
|
||||
Task<XjrbSaveDto> GetByIdAsync(long id);
|
||||
Task<long> SaveAsync(XjrbSaveDto dto);
|
||||
Task DeleteAsync(long id);
|
||||
Task<XjrbYingkuiDto> CountYingkuiAsync(XjrbSaveDto dto);
|
||||
|
||||
// ---- 选茧计划 ----
|
||||
Task<PageResult<XjrbPlanListDto>> GetPlanPagedAsync(DateTime? start, DateTime? end, string like, int pageIndex, int pageSize);
|
||||
Task<XjrbPlanSaveDto> GetPlanByIdAsync(long id);
|
||||
Task<long> SavePlanAsync(XjrbPlanSaveDto dto);
|
||||
Task DeletePlanAsync(long id);
|
||||
|
||||
// ---- 选茧计划明细 ----
|
||||
Task<List<XjrbItemDto>> GetPlanItemsAsync(long planId);
|
||||
Task<long> SavePlanItemAsync(XjrbItemSaveDto dto);
|
||||
Task DeletePlanItemAsync(long id);
|
||||
Task<XjrbYingkuiDto> CountPlanYingkuiAsync(XjrbItemSaveDto dto);
|
||||
|
||||
// ---- 选茧班组 ----
|
||||
Task<PageResult<XjpbBanzuDto>> GetBanzuPagedAsync(string banci, int? zu, int pageIndex, int pageSize);
|
||||
Task<List<XjpbBanzuZhigongDto>> GetBanzuZhigongAsync(long banzuId);
|
||||
Task SaveBanzuZhigongAsync(List<XjpbBanzuZhigongSaveDto> dtos);
|
||||
}
|
||||
|
||||
public class XjrbService : IXjrbService
|
||||
{
|
||||
private readonly IRepository<XjrbRecord> _xjrbRepo;
|
||||
private readonly IRepository<XjrbPlan> _planRepo;
|
||||
private readonly IRepository<XjrbItem> _itemRepo;
|
||||
private readonly IRepository<XjpbBanzu> _banzuRepo;
|
||||
private readonly IRepository<XjpbBanzuZhigong> _zgRepo;
|
||||
private readonly IRepository<CraftZhuangKou> _gyzkRepo;
|
||||
|
||||
public XjrbService(
|
||||
IRepository<XjrbRecord> xjrbRepo, IRepository<XjrbPlan> planRepo, IRepository<XjrbItem> itemRepo,
|
||||
IRepository<XjpbBanzu> banzuRepo, IRepository<XjpbBanzuZhigong> zgRepo, IRepository<CraftZhuangKou> gyzkRepo)
|
||||
{
|
||||
_xjrbRepo = xjrbRepo; _planRepo = planRepo; _itemRepo = itemRepo;
|
||||
_banzuRepo = banzuRepo; _zgRepo = zgRepo; _gyzkRepo = gyzkRepo;
|
||||
}
|
||||
|
||||
private static decimal Round2(decimal v) => Math.Round(v, 2);
|
||||
|
||||
private async Task<Dictionary<long, string>> GyzkDictAsync()
|
||||
=> (await _gyzkRepo.GetAllAsync()).ToDictionary(x => x.Id, x => x.ZhuangKouName);
|
||||
|
||||
// ==================== 选茧日报 ====================
|
||||
|
||||
public async Task<PageResult<XjrbListDto>> GetPagedAsync(DateTime? start, DateTime? end, string like, int pageIndex, int pageSize)
|
||||
{
|
||||
var all = await _xjrbRepo.GetAllAsync();
|
||||
var q = all.AsQueryable();
|
||||
if (start.HasValue) q = q.Where(x => x.Date.Date >= start.Value.Date);
|
||||
if (end.HasValue) q = q.Where(x => x.Date.Date <= end.Value.Date);
|
||||
|
||||
var gyzkDict = await GyzkDictAsync();
|
||||
if (!string.IsNullOrWhiteSpace(like))
|
||||
{
|
||||
var key = like.Trim();
|
||||
q = q.Where(x => x.GyzkId > 0 && gyzkDict.ContainsKey(x.GyzkId) && gyzkDict[x.GyzkId].Contains(key));
|
||||
}
|
||||
|
||||
var total = q.LongCount();
|
||||
var items = q.OrderByDescending(x => x.Date).ThenByDescending(x => x.Id)
|
||||
.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
return new PageResult<XjrbListDto>
|
||||
{
|
||||
Items = items.Select(ToDto).ToList(),
|
||||
Total = total, PageIndex = pageIndex, PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
private static XjrbListDto ToDto(XjrbRecord x) => new()
|
||||
{
|
||||
Id = x.Id, Date = x.Date, GyzkId = x.GyzkId,
|
||||
YlLrBaoshu = x.YlLrBaoshu, YlLrZhongliang = x.YlLrZhongliang,
|
||||
LtjBaoshu = x.LtjBaoshu, LtjZhongliang = x.LtjZhongliang,
|
||||
HwjjBaoshu = x.HwjjBaoshu, HwjjZhongliang = x.HwjjZhongliang,
|
||||
LjjBaoshu = x.LjjBaoshu, LjjZhongliang = x.LjjZhongliang,
|
||||
MjBaoshu = x.MjBaoshu, MjZhongliang = x.MjZhongliang,
|
||||
SgjBaoshu = x.SgjBaoshu, SgjZhongliang = x.SgjZhongliang,
|
||||
XjjBaoshu = x.XjjBaoshu, XjjZhongliang = x.XjjZhongliang,
|
||||
JyBaoshu = x.JyBaoshu, JyZhongliang = x.JyZhongliang,
|
||||
JhBaoshu = x.JhBaoshu, JhZhongliang = x.JhZhongliang,
|
||||
TotalZhongliang = x.TotalZhongliang,
|
||||
ThBaoshu = x.ThBaoshu, ThZhongliang = x.ThZhongliang,
|
||||
LiantongRate = x.LiantongRate, Yingkui = x.Yingkui
|
||||
};
|
||||
|
||||
public async Task<XjrbSaveDto> GetByIdAsync(long id)
|
||||
{
|
||||
var x = await _xjrbRepo.GetByIdAsync(id);
|
||||
if (x == null) throw new ServiceException("选茧日报不存在", 404);
|
||||
return new XjrbSaveDto
|
||||
{
|
||||
Id = x.Id, Date = x.Date, GyzkId = x.GyzkId,
|
||||
YlLrBaoshu = x.YlLrBaoshu, YlLrZhongliang = x.YlLrZhongliang,
|
||||
LtjBaoshu = x.LtjBaoshu, LtjZhongliang = x.LtjZhongliang,
|
||||
HwjjBaoshu = x.HwjjBaoshu, HwjjZhongliang = x.HwjjZhongliang,
|
||||
LjjBaoshu = x.LjjBaoshu, LjjZhongliang = x.LjjZhongliang,
|
||||
MjBaoshu = x.MjBaoshu, MjZhongliang = x.MjZhongliang,
|
||||
SgjBaoshu = x.SgjBaoshu, SgjZhongliang = x.SgjZhongliang,
|
||||
XjjBaoshu = x.XjjBaoshu, XjjZhongliang = x.XjjZhongliang,
|
||||
JyBaoshu = x.JyBaoshu, JyZhongliang = x.JyZhongliang,
|
||||
JhBaoshu = x.JhBaoshu, JhZhongliang = x.JhZhongliang,
|
||||
ThBaoshu = x.ThBaoshu, ThZhongliang = x.ThZhongliang,
|
||||
LiantongRate = x.LiantongRate, Yingkui = x.Yingkui
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<long> SaveAsync(XjrbSaveDto dto)
|
||||
{
|
||||
if (dto.Date == default) throw new ServiceException("请选择选茧日期");
|
||||
if (dto.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
|
||||
XjrbRecord entity;
|
||||
var isNew = dto.Id == 0;
|
||||
if (isNew) entity = new XjrbRecord();
|
||||
else
|
||||
{
|
||||
entity = await _xjrbRepo.GetByIdAsync(dto.Id);
|
||||
if (entity == null) throw new ServiceException("选茧日报不存在", 404);
|
||||
}
|
||||
|
||||
entity.Date = dto.Date.Date;
|
||||
entity.GyzkId = dto.GyzkId;
|
||||
entity.YlLrBaoshu = dto.YlLrBaoshu;
|
||||
entity.YlLrZhongliang = dto.YlLrZhongliang;
|
||||
entity.LtjBaoshu = dto.LtjBaoshu; entity.LtjZhongliang = dto.LtjZhongliang;
|
||||
entity.HwjjBaoshu = dto.HwjjBaoshu; entity.HwjjZhongliang = dto.HwjjZhongliang;
|
||||
entity.LjjBaoshu = dto.LjjBaoshu; entity.LjjZhongliang = dto.LjjZhongliang;
|
||||
entity.MjBaoshu = dto.MjBaoshu; entity.MjZhongliang = dto.MjZhongliang;
|
||||
entity.SgjBaoshu = dto.SgjBaoshu; entity.SgjZhongliang = dto.SgjZhongliang;
|
||||
entity.XjjBaoshu = dto.XjjBaoshu; entity.XjjZhongliang = dto.XjjZhongliang;
|
||||
entity.JyBaoshu = dto.JyBaoshu; entity.JyZhongliang = dto.JyZhongliang;
|
||||
entity.JhBaoshu = dto.JhBaoshu; entity.JhZhongliang = dto.JhZhongliang;
|
||||
entity.ThBaoshu = dto.ThBaoshu; entity.ThZhongliang = dto.ThZhongliang;
|
||||
entity.LiantongRate = dto.LiantongRate;
|
||||
entity.Yingkui = dto.Yingkui;
|
||||
// 合计重量 = 8 类茧重量之和(不含退回、不含领入)
|
||||
entity.TotalZhongliang = Round2(entity.LtjZhongliang + entity.HwjjZhongliang + entity.LjjZhongliang
|
||||
+ entity.MjZhongliang + entity.SgjZhongliang + entity.XjjZhongliang + entity.JyZhongliang + entity.JhZhongliang);
|
||||
|
||||
if (isNew) await _xjrbRepo.AddAsync(entity); else await _xjrbRepo.UpdateAsync(entity);
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(long id)
|
||||
{
|
||||
var x = await _xjrbRepo.GetByIdAsync(id);
|
||||
if (x == null) throw new ServiceException("选茧日报不存在", 404);
|
||||
await _xjrbRepo.HardDeleteAsync(x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算盈亏 / 联统率(对齐 F9 GetXjrbCountYingkuiQuery + GetXjrbLingruStartDateQuery)。
|
||||
/// 起始日期:由近及远跳过「联统率 = 0(未结算)」的日报,直到碰到已结算的一条为止;
|
||||
/// 累计区间 = [起始日期, 当前日期];累加当前录入值 + 区间内历史日报(排除自身)。
|
||||
/// 联统率 = 累计联统茧重量 ÷ 累计干茧领入重量 × 100;盈亏 = 累计选出总重量 − 累计干茧领入重量。
|
||||
/// </summary>
|
||||
public async Task<XjrbYingkuiDto> CountYingkuiAsync(XjrbSaveDto dto)
|
||||
{
|
||||
if (dto.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
|
||||
var date = dto.Date.Date;
|
||||
var all = await _xjrbRepo.GetAllAsync();
|
||||
var history = all.Where(x => x.GyzkId == dto.GyzkId && x.Date.Date < date && x.Id != dto.Id)
|
||||
.OrderByDescending(x => x.Date).ToList();
|
||||
|
||||
// 步骤 1:往回跳过未结算(LiantongRate == 0)的记录,确定起始日期
|
||||
var startDate = date;
|
||||
foreach (var item in history)
|
||||
{
|
||||
if (item.LiantongRate != 0) break;
|
||||
startDate = item.Date.Date;
|
||||
}
|
||||
|
||||
// 步骤 2:累计区间(闭区间,排除自身)
|
||||
var range = history.Where(x => x.Date.Date >= startDate && x.Date.Date <= date).ToList();
|
||||
|
||||
// 步骤 3:累加(初始值 = 当前表单输入值)
|
||||
decimal ltj = dto.LtjZhongliang;
|
||||
decimal yllr = dto.YlLrZhongliang;
|
||||
decimal allW = dto.LtjZhongliang + dto.HwjjZhongliang + dto.LjjZhongliang + dto.MjZhongliang
|
||||
+ dto.SgjZhongliang + dto.XjjZhongliang + dto.JyZhongliang + dto.JhZhongliang;
|
||||
foreach (var item in range)
|
||||
{
|
||||
ltj += item.LtjZhongliang;
|
||||
yllr += item.YlLrZhongliang;
|
||||
allW += item.LtjZhongliang + item.HwjjZhongliang + item.LjjZhongliang + item.MjZhongliang
|
||||
+ item.SgjZhongliang + item.XjjZhongliang + item.JyZhongliang + item.JhZhongliang;
|
||||
}
|
||||
|
||||
// 步骤 4:公式
|
||||
var rate = yllr != 0 ? Round2(ltj / yllr * 100) : 0;
|
||||
var yingkui = Round2(allW - yllr);
|
||||
|
||||
return new XjrbYingkuiDto { LiantongRate = rate, Yingkui = yingkui, StartDate = startDate };
|
||||
}
|
||||
|
||||
// ==================== 选茧计划 ====================
|
||||
|
||||
public async Task<PageResult<XjrbPlanListDto>> GetPlanPagedAsync(DateTime? start, DateTime? end, string like, int pageIndex, int pageSize)
|
||||
{
|
||||
var all = await _planRepo.GetAllAsync();
|
||||
var q = all.AsQueryable();
|
||||
if (start.HasValue) q = q.Where(x => x.Date.Date >= start.Value.Date);
|
||||
if (end.HasValue) q = q.Where(x => x.Date.Date <= end.Value.Date);
|
||||
|
||||
var gyzkDict = await GyzkDictAsync();
|
||||
if (!string.IsNullOrWhiteSpace(like))
|
||||
{
|
||||
var key = like.Trim();
|
||||
q = q.Where(x => x.GyzkId > 0 && gyzkDict.ContainsKey(x.GyzkId) && gyzkDict[x.GyzkId].Contains(key));
|
||||
}
|
||||
|
||||
var total = q.LongCount();
|
||||
var items = q.OrderByDescending(x => x.Date).ThenByDescending(x => x.Id)
|
||||
.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
return new PageResult<XjrbPlanListDto>
|
||||
{
|
||||
Items = items.Select(ToPlanDto).ToList(),
|
||||
Total = total, PageIndex = pageIndex, PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
private static XjrbPlanListDto ToPlanDto(XjrbPlan x) => new()
|
||||
{
|
||||
Id = x.Id, Date = x.Date, GyzkId = x.GyzkId, YllingliaodanId = x.YllingliaodanId,
|
||||
YlBaoshu = x.YlBaoshu, YlZhongliang = x.YlZhongliang,
|
||||
LtjBaoshu = x.LtjBaoshu, LtjZhongliang = x.LtjZhongliang,
|
||||
HwjjBaoshu = x.HwjjBaoshu, HwjjZhongliang = x.HwjjZhongliang,
|
||||
WjjBaoshu = x.WjjBaoshu, WjjZhongliang = x.WjjZhongliang,
|
||||
SgjBaoshu = x.SgjBaoshu, SgjZhongliang = x.SgjZhongliang,
|
||||
XjjBaoshu = x.XjjBaoshu, XjjZhongliang = x.XjjZhongliang,
|
||||
JyBaoshu = x.JyBaoshu, JyZhongliang = x.JyZhongliang,
|
||||
JhBaoshu = x.JhBaoshu, JhZhongliang = x.JhZhongliang,
|
||||
TotalZhongliang = x.TotalZhongliang, LiantongRate = x.LiantongRate,
|
||||
Yingkui = x.Yingkui, IsFinish = x.IsFinish
|
||||
};
|
||||
|
||||
public async Task<XjrbPlanSaveDto> GetPlanByIdAsync(long id)
|
||||
{
|
||||
var x = await _planRepo.GetByIdAsync(id);
|
||||
if (x == null) throw new ServiceException("选茧计划不存在", 404);
|
||||
return new XjrbPlanSaveDto
|
||||
{
|
||||
Id = x.Id, Date = x.Date, GyzkId = x.GyzkId, YllingliaodanId = x.YllingliaodanId,
|
||||
YlBaoshu = x.YlBaoshu, YlZhongliang = x.YlZhongliang,
|
||||
LtjBaoshu = x.LtjBaoshu, LtjZhongliang = x.LtjZhongliang,
|
||||
HwjjBaoshu = x.HwjjBaoshu, HwjjZhongliang = x.HwjjZhongliang,
|
||||
WjjBaoshu = x.WjjBaoshu, WjjZhongliang = x.WjjZhongliang,
|
||||
SgjBaoshu = x.SgjBaoshu, SgjZhongliang = x.SgjZhongliang,
|
||||
XjjBaoshu = x.XjjBaoshu, XjjZhongliang = x.XjjZhongliang,
|
||||
JyBaoshu = x.JyBaoshu, JyZhongliang = x.JyZhongliang,
|
||||
JhBaoshu = x.JhBaoshu, JhZhongliang = x.JhZhongliang
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<long> SavePlanAsync(XjrbPlanSaveDto dto)
|
||||
{
|
||||
if (dto.Date == default) throw new ServiceException("请选择日期");
|
||||
if (dto.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
|
||||
XjrbPlan entity;
|
||||
var isNew = dto.Id == 0;
|
||||
if (isNew) entity = new XjrbPlan();
|
||||
else
|
||||
{
|
||||
entity = await _planRepo.GetByIdAsync(dto.Id);
|
||||
if (entity == null) throw new ServiceException("选茧计划不存在", 404);
|
||||
if (entity.IsFinish) throw new ServiceException("该计划已选茧完成,不能修改");
|
||||
}
|
||||
|
||||
entity.Date = dto.Date.Date;
|
||||
entity.GyzkId = dto.GyzkId;
|
||||
entity.YllingliaodanId = dto.YllingliaodanId;
|
||||
entity.YlBaoshu = dto.YlBaoshu; entity.YlZhongliang = dto.YlZhongliang;
|
||||
entity.LtjBaoshu = dto.LtjBaoshu; entity.LtjZhongliang = dto.LtjZhongliang;
|
||||
entity.HwjjBaoshu = dto.HwjjBaoshu; entity.HwjjZhongliang = dto.HwjjZhongliang;
|
||||
entity.WjjBaoshu = dto.WjjBaoshu; entity.WjjZhongliang = dto.WjjZhongliang;
|
||||
entity.SgjBaoshu = dto.SgjBaoshu; entity.SgjZhongliang = dto.SgjZhongliang;
|
||||
entity.XjjBaoshu = dto.XjjBaoshu; entity.XjjZhongliang = dto.XjjZhongliang;
|
||||
entity.JyBaoshu = dto.JyBaoshu; entity.JyZhongliang = dto.JyZhongliang;
|
||||
entity.JhBaoshu = dto.JhBaoshu; entity.JhZhongliang = dto.JhZhongliang;
|
||||
entity.TotalZhongliang = Round2(entity.LtjZhongliang + entity.HwjjZhongliang + entity.WjjZhongliang
|
||||
+ entity.SgjZhongliang + entity.XjjZhongliang + entity.JyZhongliang + entity.JhZhongliang);
|
||||
|
||||
if (isNew) await _planRepo.AddAsync(entity); else await _planRepo.UpdateAsync(entity);
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
public async Task DeletePlanAsync(long id)
|
||||
{
|
||||
var x = await _planRepo.GetByIdAsync(id);
|
||||
if (x == null) throw new ServiceException("选茧计划不存在", 404);
|
||||
if (x.IsFinish) throw new ServiceException("该计划已选茧完成,不能删除");
|
||||
var items = await _itemRepo.FindAsync(i => i.XjrbPlanId == id);
|
||||
if (items.Count > 0) await _itemRepo.HardDeleteRangeAsync(items);
|
||||
await _planRepo.HardDeleteAsync(x);
|
||||
}
|
||||
|
||||
// ==================== 选茧计划明细 ====================
|
||||
|
||||
public async Task<List<XjrbItemDto>> GetPlanItemsAsync(long planId)
|
||||
{
|
||||
var items = await _itemRepo.FindAsync(x => x.XjrbPlanId == planId);
|
||||
return items.OrderBy(x => x.Date).ThenBy(x => x.Id).Select(x => new XjrbItemDto
|
||||
{
|
||||
Id = x.Id, XjrbPlanId = x.XjrbPlanId, Date = x.Date,
|
||||
LtjBaoshu = x.LtjBaoshu, LtjZhongliang = x.LtjZhongliang,
|
||||
HwjjBaoshu = x.HwjjBaoshu, HwjjZhongliang = x.HwjjZhongliang,
|
||||
WjjBaoshu = x.WjjBaoshu, WjjZhongliang = x.WjjZhongliang,
|
||||
SgjBaoshu = x.SgjBaoshu, SgjZhongliang = x.SgjZhongliang,
|
||||
XjjBaoshu = x.XjjBaoshu, XjjZhongliang = x.XjjZhongliang,
|
||||
JyBaoshu = x.JyBaoshu, JyZhongliang = x.JyZhongliang,
|
||||
JhBaoshu = x.JhBaoshu, JhZhongliang = x.JhZhongliang,
|
||||
TotalZhongliang = x.TotalZhongliang, IsFinish = x.IsFinish
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<long> SavePlanItemAsync(XjrbItemSaveDto dto)
|
||||
{
|
||||
if (dto.XjrbPlanId <= 0) throw new ServiceException("计划无效");
|
||||
if (dto.Date == default) throw new ServiceException("请选择选茧日期");
|
||||
|
||||
var plan = await _planRepo.GetByIdAsync(dto.XjrbPlanId);
|
||||
if (plan == null) throw new ServiceException("选茧计划不存在", 404);
|
||||
if (plan.IsFinish) throw new ServiceException("该计划已选茧完成,不能修改明细");
|
||||
|
||||
XjrbItem entity;
|
||||
var isNew = dto.Id == 0;
|
||||
if (isNew) entity = new XjrbItem { XjrbPlanId = dto.XjrbPlanId };
|
||||
else
|
||||
{
|
||||
entity = await _itemRepo.GetByIdAsync(dto.Id);
|
||||
if (entity == null) throw new ServiceException("选茧明细不存在", 404);
|
||||
}
|
||||
|
||||
entity.Date = dto.Date.Date;
|
||||
entity.LtjBaoshu = dto.LtjBaoshu; entity.LtjZhongliang = dto.LtjZhongliang;
|
||||
entity.HwjjBaoshu = dto.HwjjBaoshu; entity.HwjjZhongliang = dto.HwjjZhongliang;
|
||||
entity.WjjBaoshu = dto.WjjBaoshu; entity.WjjZhongliang = dto.WjjZhongliang;
|
||||
entity.SgjBaoshu = dto.SgjBaoshu; entity.SgjZhongliang = dto.SgjZhongliang;
|
||||
entity.XjjBaoshu = dto.XjjBaoshu; entity.XjjZhongliang = dto.XjjZhongliang;
|
||||
entity.JyBaoshu = dto.JyBaoshu; entity.JyZhongliang = dto.JyZhongliang;
|
||||
entity.JhBaoshu = dto.JhBaoshu; entity.JhZhongliang = dto.JhZhongliang;
|
||||
entity.TotalZhongliang = Round2(entity.LtjZhongliang + entity.HwjjZhongliang + entity.WjjZhongliang
|
||||
+ entity.SgjZhongliang + entity.XjjZhongliang + entity.JyZhongliang + entity.JhZhongliang);
|
||||
entity.IsFinish = dto.IsFinish;
|
||||
|
||||
if (isNew) await _itemRepo.AddAsync(entity); else await _itemRepo.UpdateAsync(entity);
|
||||
|
||||
// 勾选「选茧完成」时把盈亏回写计划(对齐 F9 UpdateXjrbPlanYingkuiCommand)
|
||||
if (dto.IsFinish)
|
||||
{
|
||||
var yk = await CountPlanYingkuiAsync(dto);
|
||||
plan.TotalZhongliang = Round2(plan.TotalZhongliang + entity.TotalZhongliang);
|
||||
plan.Yingkui = yk.Yingkui;
|
||||
plan.LiantongRate = yk.LiantongRate;
|
||||
plan.IsFinish = true;
|
||||
await _planRepo.UpdateAsync(plan);
|
||||
}
|
||||
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
public async Task DeletePlanItemAsync(long id)
|
||||
{
|
||||
var x = await _itemRepo.GetByIdAsync(id);
|
||||
if (x == null) throw new ServiceException("选茧明细不存在", 404);
|
||||
var plan = await _planRepo.GetByIdAsync(x.XjrbPlanId);
|
||||
if (plan != null && plan.IsFinish) throw new ServiceException("该计划已选茧完成,不能删除明细");
|
||||
await _itemRepo.HardDeleteAsync(x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计划维度盈亏(对齐 F9 GetXjrbPlanYingkuiQuery,口径与日报不同):
|
||||
/// 联统率按「包数」:联统茧包数 ÷ 各类茧包数之和 × 100;盈亏 = 累计选出总重量 − 计划原料领入重量。
|
||||
/// 注:老系统仓储里 wjjBaoshu / wjjZhongliang 源字段交叉写反(疑似 bug),此处已按正确字段聚合。
|
||||
/// </summary>
|
||||
public async Task<XjrbYingkuiDto> CountPlanYingkuiAsync(XjrbItemSaveDto dto)
|
||||
{
|
||||
var plan = await _planRepo.GetByIdAsync(dto.XjrbPlanId);
|
||||
if (plan == null) throw new ServiceException("选茧计划不存在", 404);
|
||||
|
||||
var items = (await _itemRepo.FindAsync(x => x.XjrbPlanId == dto.XjrbPlanId && x.Id != dto.Id)).ToList();
|
||||
|
||||
decimal ltjZ = dto.LtjZhongliang, hwjjZ = dto.HwjjZhongliang, wjjZ = dto.WjjZhongliang,
|
||||
sgjZ = dto.SgjZhongliang, xjjZ = dto.XjjZhongliang, jyZ = dto.JyZhongliang, jhZ = dto.JhZhongliang;
|
||||
decimal ltjB = dto.LtjBaoshu, hwjjB = dto.HwjjBaoshu, wjjB = dto.WjjBaoshu,
|
||||
sgjB = dto.SgjBaoshu, xjjB = dto.XjjBaoshu, jyB = dto.JyBaoshu, jhB = dto.JhBaoshu;
|
||||
|
||||
foreach (var it in items)
|
||||
{
|
||||
ltjZ += it.LtjZhongliang; hwjjZ += it.HwjjZhongliang; wjjZ += it.WjjZhongliang;
|
||||
sgjZ += it.SgjZhongliang; xjjZ += it.XjjZhongliang; jyZ += it.JyZhongliang; jhZ += it.JhZhongliang;
|
||||
ltjB += it.LtjBaoshu; hwjjB += it.HwjjBaoshu; wjjB += it.WjjBaoshu;
|
||||
sgjB += it.SgjBaoshu; xjjB += it.XjjBaoshu; jyB += it.JyBaoshu; jhB += it.JhBaoshu;
|
||||
}
|
||||
|
||||
var totalZhongliang = Round2(ltjZ + hwjjZ + wjjZ + sgjZ + xjjZ + jyZ + jhZ);
|
||||
var baoshuSum = ltjB + hwjjB + wjjB + sgjB + xjjB + jyB + jhB;
|
||||
var rate = baoshuSum != 0 ? Round2(ltjB / (decimal)baoshuSum * 100) : 0;
|
||||
|
||||
return new XjrbYingkuiDto { LiantongRate = rate, Yingkui = Round2(totalZhongliang - plan.YlZhongliang), StartDate = plan.Date };
|
||||
}
|
||||
|
||||
// ==================== 选茧班组 ====================
|
||||
|
||||
public async Task<PageResult<XjpbBanzuDto>> GetBanzuPagedAsync(string banci, int? zu, int pageIndex, int pageSize)
|
||||
{
|
||||
var all = await _banzuRepo.GetAllAsync();
|
||||
var q = all.AsQueryable();
|
||||
if (!string.IsNullOrWhiteSpace(banci)) q = q.Where(x => x.Banci == banci);
|
||||
if (zu.HasValue) q = q.Where(x => x.Zu == zu.Value);
|
||||
|
||||
var total = q.LongCount();
|
||||
var items = q.OrderBy(x => x.Banci).ThenBy(x => x.Zu)
|
||||
.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
return new PageResult<XjpbBanzuDto>
|
||||
{
|
||||
Items = items.Select(x => new XjpbBanzuDto { Id = x.Id, Banci = x.Banci, Zu = x.Zu }).ToList(),
|
||||
Total = total, PageIndex = pageIndex, PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<XjpbBanzuZhigongDto>> GetBanzuZhigongAsync(long banzuId)
|
||||
{
|
||||
if (banzuId <= 0) return new List<XjpbBanzuZhigongDto>();
|
||||
var list = await _zgRepo.FindAsync(x => x.BanzuId == banzuId);
|
||||
return list.OrderBy(x => x.Sort).ThenBy(x => x.Id).Select(x => new XjpbBanzuZhigongDto
|
||||
{
|
||||
Id = x.Id, BanzuId = x.BanzuId, Banci = x.Banci, Zu = x.Zu,
|
||||
YgId = x.YgId, YgName = x.YgName, YgPhone = x.YgPhone, Sort = x.Sort
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 班组「编辑录入」批量保存(对齐 F9 XjpbBanzuZhigong_SaveCommand):
|
||||
/// 按班次整体覆盖——先清该班次旧职工与班组,再把「已勾选且有组别」的职工写入,并重建班组。
|
||||
/// </summary>
|
||||
public async Task SaveBanzuZhigongAsync(List<XjpbBanzuZhigongSaveDto> dtos)
|
||||
{
|
||||
var list = dtos ?? new List<XjpbBanzuZhigongSaveDto>();
|
||||
var banci = list.FirstOrDefault()?.Banci ?? "";
|
||||
if (string.IsNullOrWhiteSpace(banci)) throw new ServiceException("班次无效");
|
||||
|
||||
// 清理该班次旧数据
|
||||
var oldZg = await _zgRepo.FindAsync(x => x.Banci == banci);
|
||||
if (oldZg.Count > 0) await _zgRepo.HardDeleteRangeAsync(oldZg);
|
||||
var oldBanzu = await _banzuRepo.FindAsync(x => x.Banci == banci);
|
||||
if (oldBanzu.Count > 0) await _banzuRepo.HardDeleteRangeAsync(oldBanzu);
|
||||
|
||||
var picked = list.Where(x => x.Selected && x.Zu > 0).ToList();
|
||||
if (picked.Count == 0) return;
|
||||
|
||||
// 重建班组(班次 + 组 去重)
|
||||
var banzuDict = new Dictionary<int, long>();
|
||||
foreach (var zu in picked.Select(x => x.Zu).Distinct().OrderBy(x => x))
|
||||
{
|
||||
var banzu = new XjpbBanzu { Banci = banci, Zu = zu };
|
||||
await _banzuRepo.AddAsync(banzu);
|
||||
banzuDict[zu] = banzu.Id;
|
||||
}
|
||||
|
||||
var sort = 0;
|
||||
foreach (var p in picked)
|
||||
{
|
||||
await _zgRepo.AddAsync(new XjpbBanzuZhigong
|
||||
{
|
||||
BanzuId = banzuDict.TryGetValue(p.Zu, out var bid) ? bid : 0,
|
||||
Banci = banci, Zu = p.Zu,
|
||||
YgId = p.YgId, YgName = p.YgName ?? "", YgPhone = p.YgPhone ?? "",
|
||||
Sort = p.Sort > 0 ? p.Sort : ++sort
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,609 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.Prod;
|
||||
using F9WebAiTest.Domain.Common;
|
||||
using F9WebAiTest.Domain.Entities.Base;
|
||||
using F9WebAiTest.Domain.Entities.Prod;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Registries;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments;
|
||||
|
||||
namespace F9WebAiTest.Application.Services.Prod;
|
||||
|
||||
/// <summary>
|
||||
/// 煮茧日报(生产-前缫):某日某车间某庄口某规格一条日报,含「机组」「班次」「机组班次」三层明细。
|
||||
/// 对应 F9 Page_Zjrb(列表)、Page_Zjrb_EditV2(录入)、Zjrb_SaveCommand / Zjrb_DelCommand /
|
||||
/// CalculateJizuSaosiHoursCommand(计算缫丝时长)。
|
||||
///
|
||||
/// 计算口径(与 F9 前端 Page_Zjrb_EditV2 一致,服务端保存时重算):
|
||||
/// 机组.今缫桶数 = 甲班 + 乙班
|
||||
/// 机组.今存桶数 = 上存 + 今缫 − 今落(今落为 0 且余量够一个标准桶时自动补「工艺落丝桶数」)
|
||||
/// 班次.今缫重量 = 该班次机组今缫桶数 × 每桶茧量 ÷ 1000
|
||||
/// 班次.领入/退回重量 = 包数 × 联统茧标重(标重未配置则保留录入值)
|
||||
/// 班次.今缫包数 = 今缫重量 ÷ 联统茧标重(四舍五入取整)
|
||||
/// 班次.今存 = 上存 + 领入 − 今缫 − 退回
|
||||
/// 主表 = 甲班 + 乙班
|
||||
/// </summary>
|
||||
public interface IZjrbService
|
||||
{
|
||||
/// <summary>日报列表(账期起止 + 车间 + 关键字)</summary>
|
||||
Task<PageResult<ZjrbReadDto_View>> GetPagedAsync(DateTime dateFrom, DateTime dateTo, long depId,
|
||||
string like, int pageIndex, int pageSize);
|
||||
|
||||
/// <summary>某条日报的机组明细(列表页下半表)</summary>
|
||||
Task<List<ZjrbJizuDto>> GetJizuListAsync(long zjrbId);
|
||||
|
||||
/// <summary>修改回填:主表 + 班次 + 机组 + 机组班次</summary>
|
||||
Task<ZjrbSaveDto> GetForEditAsync(long id);
|
||||
|
||||
/// <summary>新增:按上一日结存带出上存、每桶茧量、工艺落丝桶数,并按机组车台生成机组行</summary>
|
||||
Task<ZjrbSaveDto> BuildDefaultsAsync(DateTime date, long depId, long gyzkId, string guige);
|
||||
|
||||
/// <summary>保存(整条替换:主表 upsert + 三层明细先删后建)</summary>
|
||||
Task<long> SaveAsync(ZjrbSaveDto dto);
|
||||
|
||||
/// <summary>删除(四张表级联物理删除)</summary>
|
||||
Task DeleteAsync(long id);
|
||||
|
||||
/// <summary>计算缫丝时长(整日整车间重算:单班 9 小时 / 双班 18 小时,按茧量比例分配)</summary>
|
||||
Task<int> CalculateJizuSaosiHoursAsync(DateTime date, long depId);
|
||||
}
|
||||
|
||||
public class ZjrbService : IZjrbService
|
||||
{
|
||||
/// <summary>单班 / 双班的机组标准时长</summary>
|
||||
private const decimal SingleBanciHours = 9m;
|
||||
private const decimal TwoBanciHours = 18m;
|
||||
|
||||
/// <summary>联统茧标重的参数配置位置(与 F9 一致)</summary>
|
||||
private const string RegistryCategory = @"生产\前缫\煮茧";
|
||||
private const string RegistryKey = "联统茧标重";
|
||||
|
||||
private readonly IRepository<ZjrbRecord> _zjrbRepo;
|
||||
private readonly IRepository<ZjrbJizu> _jizuRepo;
|
||||
private readonly IRepository<ZjrbBanci> _banciRepo;
|
||||
private readonly IRepository<ZjrbJizuBanci> _jizuBanciRepo;
|
||||
private readonly IRepository<JizuChetai> _jzctRepo;
|
||||
private readonly IRepository<ChejianBanci> _cjbcRepo;
|
||||
private readonly IRepository<CraftZhuangKou> _gyzkRepo;
|
||||
private readonly IRepository<Registry> _registryRepo;
|
||||
private readonly IRepository<SysDept.Department> _deptRepo;
|
||||
|
||||
public ZjrbService(IRepository<ZjrbRecord> zjrbRepo, IRepository<ZjrbJizu> jizuRepo,
|
||||
IRepository<ZjrbBanci> banciRepo, IRepository<ZjrbJizuBanci> jizuBanciRepo,
|
||||
IRepository<JizuChetai> jzctRepo, IRepository<ChejianBanci> cjbcRepo,
|
||||
IRepository<CraftZhuangKou> gyzkRepo, IRepository<Registry> registryRepo,
|
||||
IRepository<SysDept.Department> deptRepo)
|
||||
{
|
||||
_zjrbRepo = zjrbRepo;
|
||||
_jizuRepo = jizuRepo;
|
||||
_banciRepo = banciRepo;
|
||||
_jizuBanciRepo = jizuBanciRepo;
|
||||
_jzctRepo = jzctRepo;
|
||||
_cjbcRepo = cjbcRepo;
|
||||
_gyzkRepo = gyzkRepo;
|
||||
_registryRepo = registryRepo;
|
||||
_deptRepo = deptRepo;
|
||||
}
|
||||
|
||||
// ==================== 查询 ====================
|
||||
|
||||
public async Task<PageResult<ZjrbReadDto_View>> GetPagedAsync(DateTime dateFrom, DateTime dateTo, long depId,
|
||||
string like, int pageIndex, int pageSize)
|
||||
{
|
||||
if (pageIndex <= 0) pageIndex = 1;
|
||||
if (pageSize <= 0) pageSize = 20;
|
||||
|
||||
var from = dateFrom.Date;
|
||||
var to = dateTo.Date;
|
||||
if (to < from) (from, to) = (to, from);
|
||||
|
||||
var ctx = await BuildContextAsync();
|
||||
|
||||
var rows = await _zjrbRepo.FindAsync(x => x.Date >= from && x.Date <= to);
|
||||
if (depId > 0) rows = rows.Where(x => x.DeptId == depId).ToList();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(like))
|
||||
{
|
||||
var kw = like.Trim();
|
||||
rows = rows.Where(x =>
|
||||
ctx.GyzkNames.GetValueOrDefault(x.GyzkId, "").Contains(kw, StringComparison.OrdinalIgnoreCase)
|
||||
|| (x.Guige ?? "").Contains(kw, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var total = rows.Count;
|
||||
var pageRows = rows
|
||||
.OrderByDescending(x => x.Date)
|
||||
.ThenBy(x => ctx.DeptNames.GetValueOrDefault(x.DeptId, ""))
|
||||
.ThenBy(x => x.GyzkId)
|
||||
.ThenBy(x => x.Guige)
|
||||
.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
var ids = pageRows.Select(x => x.Id).ToList();
|
||||
var jizus = ids.Count == 0
|
||||
? new List<ZjrbJizu>()
|
||||
: await _jizuRepo.FindAsync(x => ids.Contains(x.ZjrbId));
|
||||
|
||||
var items = pageRows.Select(x =>
|
||||
{
|
||||
var jz = jizus.Where(j => j.ZjrbId == x.Id).ToList();
|
||||
return new ZjrbReadDto_View
|
||||
{
|
||||
Id = x.Id,
|
||||
Date = x.Date,
|
||||
DepId = x.DeptId,
|
||||
DepName = ctx.DeptNames.GetValueOrDefault(x.DeptId, ""),
|
||||
GyzkId = x.GyzkId,
|
||||
GyzkName = ctx.GyzkNames.GetValueOrDefault(x.GyzkId, ""),
|
||||
Guige = x.Guige ?? "",
|
||||
LastDate = x.LastDate,
|
||||
StandardLuositongshu = x.StandardLuositongshu,
|
||||
MeitongJianliang = x.MeitongJianliang,
|
||||
BaoshuShangcun = x.BaoshuShangcun,
|
||||
BaoshuLingru = x.BaoshuLingru,
|
||||
BaoshuSaosi = x.BaoshuSaosi,
|
||||
BaoshuTuihui = x.BaoshuTuihui,
|
||||
BaoshuJincun = x.BaoshuJincun,
|
||||
JianliangShangcun = x.JianliangShangcun,
|
||||
JianliangLingru = x.JianliangLingru,
|
||||
JianliangSaosi = x.JianliangSaosi,
|
||||
JianliangTuihui = x.JianliangTuihui,
|
||||
JianliangJincun = x.JianliangJincun,
|
||||
TongshuShangcun = jz.Sum(j => j.TongshuShangcun),
|
||||
TongshuJinsao = jz.Sum(j => j.TongshuJinsao),
|
||||
TongshuJinluo = jz.Sum(j => j.TongshuJinluo),
|
||||
TongshuJincun = jz.Sum(j => j.TongshuJincun),
|
||||
JizuSaosiHours = jz.Sum(j => j.JizuSaosiHours)
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
return new PageResult<ZjrbReadDto_View>
|
||||
{
|
||||
Items = items,
|
||||
Total = total,
|
||||
PageIndex = pageIndex,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<ZjrbJizuDto>> GetJizuListAsync(long zjrbId)
|
||||
{
|
||||
var jizus = (await _jizuRepo.FindAsync(x => x.ZjrbId == zjrbId)).OrderBy(x => x.Jizu).ToList();
|
||||
var jb = await _jizuBanciRepo.FindAsync(x => x.ZjrbId == zjrbId);
|
||||
return jizus.Select(j => ToJizuDto(j, jb)).ToList();
|
||||
}
|
||||
|
||||
public async Task<ZjrbSaveDto> GetForEditAsync(long id)
|
||||
{
|
||||
var master = await _zjrbRepo.GetByIdAsync(id);
|
||||
if (master == null) throw new ServiceException("煮茧日报不存在或已删除");
|
||||
|
||||
var ctx = await BuildContextAsync();
|
||||
var banciRows = (await _banciRepo.FindAsync(x => x.ZjrbId == id)).OrderBy(x => x.Banci).ToList();
|
||||
var jizuRows = (await _jizuRepo.FindAsync(x => x.ZjrbId == id)).OrderBy(x => x.Jizu).ToList();
|
||||
var jbRows = await _jizuBanciRepo.FindAsync(x => x.ZjrbId == id);
|
||||
|
||||
var dto = new ZjrbSaveDto
|
||||
{
|
||||
Id = master.Id,
|
||||
Date = master.Date,
|
||||
DepId = master.DeptId,
|
||||
DepName = ctx.DeptNames.GetValueOrDefault(master.DeptId, ""),
|
||||
GyzkId = master.GyzkId,
|
||||
GyzkName = ctx.GyzkNames.GetValueOrDefault(master.GyzkId, ""),
|
||||
Guige = master.Guige ?? "",
|
||||
LastDate = master.LastDate,
|
||||
StandardLuositongshu = master.StandardLuositongshu,
|
||||
MeitongJianliang = master.MeitongJianliang,
|
||||
LiantongBiaozhong = await GetLiantongBiaozhongAsync(),
|
||||
BaoshuShangcun = master.BaoshuShangcun,
|
||||
BaoshuLingru = master.BaoshuLingru,
|
||||
BaoshuSaosi = master.BaoshuSaosi,
|
||||
BaoshuTuihui = master.BaoshuTuihui,
|
||||
BaoshuJincun = master.BaoshuJincun,
|
||||
JianliangShangcun = master.JianliangShangcun,
|
||||
JianliangLingru = master.JianliangLingru,
|
||||
JianliangSaosi = master.JianliangSaosi,
|
||||
JianliangTuihui = master.JianliangTuihui,
|
||||
JianliangJincun = master.JianliangJincun,
|
||||
Bancis = banciRows.Select(ToBanciDto).ToList(),
|
||||
Jizus = jizuRows.Select(j => ToJizuDto(j, jbRows)).ToList()
|
||||
};
|
||||
|
||||
// 历史数据可能缺班次行(或车间班次配置调整过),按配置补齐甲乙班
|
||||
var need = (await GetBanciKindsAsync(master.DeptId));
|
||||
foreach (var b in need)
|
||||
{
|
||||
if (dto.Bancis.All(x => x.Banci != b))
|
||||
dto.Bancis.Add(new ZjrbBanciDto { Banci = b, BanciName = b.ToString() });
|
||||
}
|
||||
dto.Bancis = dto.Bancis.OrderBy(x => x.Banci).ToList();
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<ZjrbSaveDto> BuildDefaultsAsync(DateTime date, long depId, long gyzkId, string guige)
|
||||
{
|
||||
if (depId <= 0) throw new ServiceException("请选择车间");
|
||||
|
||||
var ctx = await BuildContextAsync();
|
||||
var d = date.Date;
|
||||
var g = (guige ?? string.Empty).Trim();
|
||||
|
||||
// 上一日:同车间 + 同庄口(+ 同规格)的最近一条日报
|
||||
var candidates = await _zjrbRepo.FindAsync(x => x.Date < d && x.DeptId == depId && x.GyzkId == gyzkId);
|
||||
var last = candidates
|
||||
.Where(x => string.IsNullOrEmpty(g) || (x.Guige ?? "") == g)
|
||||
.OrderByDescending(x => x.Date)
|
||||
.FirstOrDefault();
|
||||
|
||||
var dto = new ZjrbSaveDto
|
||||
{
|
||||
Date = d,
|
||||
DepId = depId,
|
||||
DepName = ctx.DeptNames.GetValueOrDefault(depId, ""),
|
||||
GyzkId = gyzkId,
|
||||
GyzkName = ctx.GyzkNames.GetValueOrDefault(gyzkId, ""),
|
||||
Guige = g,
|
||||
LiantongBiaozhong = await GetLiantongBiaozhongAsync()
|
||||
};
|
||||
|
||||
List<ZjrbBanci> lastBancis = new();
|
||||
List<ZjrbJizu> lastJizus = new();
|
||||
if (last != null)
|
||||
{
|
||||
dto.LastDate = last.Date;
|
||||
dto.StandardLuositongshu = last.StandardLuositongshu;
|
||||
dto.MeitongJianliang = last.MeitongJianliang;
|
||||
lastBancis = await _banciRepo.FindAsync(x => x.ZjrbId == last.Id);
|
||||
lastJizus = await _jizuRepo.FindAsync(x => x.ZjrbId == last.Id);
|
||||
}
|
||||
|
||||
// 班次行:按车间班次配置;未配置则固定甲乙两班
|
||||
foreach (var b in await GetBanciKindsAsync(depId))
|
||||
{
|
||||
var prev = lastBancis.FirstOrDefault(x => x.Banci == b);
|
||||
dto.Bancis.Add(new ZjrbBanciDto
|
||||
{
|
||||
Banci = b,
|
||||
BanciName = b.ToString(),
|
||||
BaoshuShangcun = prev?.BaoshuJincun ?? 0,
|
||||
JianliangShangcun = prev?.JianliangJincun ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
// 机组行:按机组车台配置;上存桶数取上一日该机组今存桶数
|
||||
var jizus = (await _jzctRepo.FindAsync(x => x.DepartmentId == depId))
|
||||
.Select(x => x.Jizu).Distinct().OrderBy(x => x).ToList();
|
||||
foreach (var jz in jizus)
|
||||
{
|
||||
var prev = lastJizus.FirstOrDefault(x => x.Jizu == jz);
|
||||
dto.Jizus.Add(new ZjrbJizuDto
|
||||
{
|
||||
Jizu = jz,
|
||||
TongshuShangcun = prev?.TongshuJincun ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
// ==================== 保存 / 删除 ====================
|
||||
|
||||
public async Task<long> SaveAsync(ZjrbSaveDto dto)
|
||||
{
|
||||
if (dto == null) throw new ServiceException("参数不能为空");
|
||||
if (dto.Date == default) throw new ServiceException("请选择日期");
|
||||
if (dto.DepId <= 0) throw new ServiceException("请选择车间");
|
||||
if (dto.GyzkId <= 0) throw new ServiceException("请选择工艺庄口");
|
||||
if (dto.MeitongJianliang <= 0) throw new ServiceException("每桶茧量必须大于 0");
|
||||
if (dto.StandardLuositongshu <= 0) throw new ServiceException("工艺落丝桶数必须大于 0");
|
||||
if (dto.Bancis == null || dto.Bancis.Count == 0) throw new ServiceException("请至少保留一个班次行");
|
||||
|
||||
var d = dto.Date.Date;
|
||||
var guige = (dto.Guige ?? string.Empty).Trim();
|
||||
|
||||
// 唯一性:同 日期 + 车间 + 庄口 + 规格
|
||||
var exists = await _zjrbRepo.AnyAsync(x =>
|
||||
x.Id != dto.Id && x.Date == d && x.DeptId == dto.DepId && x.GyzkId == dto.GyzkId && x.Guige == guige);
|
||||
if (exists) throw new ServiceException("已存在相同的煮茧日报!");
|
||||
|
||||
Normalize(dto, guige);
|
||||
|
||||
long id = dto.Id;
|
||||
if (id > 0)
|
||||
{
|
||||
var master = await _zjrbRepo.GetByIdAsync(id);
|
||||
if (master == null) throw new ServiceException("煮茧日报不存在或已删除");
|
||||
CopyMaster(dto, master);
|
||||
await _zjrbRepo.UpdateAsync(master);
|
||||
await DeleteChildrenAsync(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
var master = new ZjrbRecord { LastDate = dto.LastDate };
|
||||
CopyMaster(dto, master);
|
||||
await _zjrbRepo.AddAsync(master);
|
||||
id = master.Id;
|
||||
}
|
||||
|
||||
// 班次
|
||||
foreach (var b in dto.Bancis)
|
||||
{
|
||||
await _banciRepo.AddAsync(new ZjrbBanci
|
||||
{
|
||||
ZjrbId = id,
|
||||
Banci = b.Banci,
|
||||
BaoshuShangcun = b.BaoshuShangcun,
|
||||
BaoshuLingru = b.BaoshuLingru,
|
||||
BaoshuSaosi = b.BaoshuSaosi,
|
||||
BaoshuTuihui = b.BaoshuTuihui,
|
||||
BaoshuJincun = b.BaoshuJincun,
|
||||
JianliangShangcun = b.JianliangShangcun,
|
||||
JianliangLingru = b.JianliangLingru,
|
||||
JianliangSaosi = b.JianliangSaosi,
|
||||
JianliangTuihui = b.JianliangTuihui,
|
||||
JianliangJincun = b.JianliangJincun
|
||||
});
|
||||
}
|
||||
|
||||
// 机组 + 机组班次(机组班次挂在刚生成的机组 Id 上)
|
||||
foreach (var j in dto.Jizus)
|
||||
{
|
||||
var jizu = await _jizuRepo.AddAsync(new ZjrbJizu
|
||||
{
|
||||
ZjrbId = id,
|
||||
Jizu = j.Jizu,
|
||||
JizuSaosiHours = j.JizuSaosiHours,
|
||||
TongshuShangcun = j.TongshuShangcun,
|
||||
TongshuJinsao = j.TongshuJinsaoJiaban + j.TongshuJinsaoYiban,
|
||||
TongshuJinluo = j.TongshuJinluo,
|
||||
TongshuJincun = j.TongshuJincun
|
||||
});
|
||||
|
||||
foreach (var b in dto.Bancis)
|
||||
{
|
||||
await _jizuBanciRepo.AddAsync(new ZjrbJizuBanci
|
||||
{
|
||||
ZjrbId = id,
|
||||
ZjrbJizuId = jizu.Id,
|
||||
Jizu = j.Jizu,
|
||||
Banci = b.Banci,
|
||||
TongshuJinsao = b.Banci == Prod_Banci_Enum.甲 ? j.TongshuJinsaoJiaban : j.TongshuJinsaoYiban
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await _zjrbRepo.SaveChangesAsync();
|
||||
return id;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(long id)
|
||||
{
|
||||
var master = await _zjrbRepo.GetByIdAsync(id);
|
||||
if (master == null) throw new ServiceException("煮茧日报不存在或已删除");
|
||||
await DeleteChildrenAsync(id);
|
||||
await _zjrbRepo.HardDeleteAsync(master);
|
||||
}
|
||||
|
||||
private async Task DeleteChildrenAsync(long zjrbId)
|
||||
{
|
||||
var jizuRows = await _jizuRepo.FindAsync(x => x.ZjrbId == zjrbId);
|
||||
if (jizuRows.Count > 0) await _jizuRepo.HardDeleteRangeAsync(jizuRows);
|
||||
|
||||
var banciRows = await _banciRepo.FindAsync(x => x.ZjrbId == zjrbId);
|
||||
if (banciRows.Count > 0) await _banciRepo.HardDeleteRangeAsync(banciRows);
|
||||
|
||||
var jbRows = await _jizuBanciRepo.FindAsync(x => x.ZjrbId == zjrbId);
|
||||
if (jbRows.Count > 0) await _jizuBanciRepo.HardDeleteRangeAsync(jbRows);
|
||||
}
|
||||
|
||||
// ==================== 计算缫丝时长 ====================
|
||||
|
||||
public async Task<int> CalculateJizuSaosiHoursAsync(DateTime date, long depId)
|
||||
{
|
||||
if (depId <= 0) throw new ServiceException("请选择车间");
|
||||
var d = date.Date;
|
||||
|
||||
var reports = await _zjrbRepo.FindAsync(x => x.Date == d && x.DeptId == depId);
|
||||
if (reports.Count == 0) throw new ServiceException("该日该车间没有煮茧日报,无法计算缫丝时长");
|
||||
|
||||
var reportMap = reports.ToDictionary(x => x.Id);
|
||||
var ids = reportMap.Keys.ToList();
|
||||
var jizus = await _jizuRepo.FindAsync(x => ids.Contains(x.ZjrbId));
|
||||
if (jizus.Count == 0) throw new ServiceException("该日该车间没有机组明细,无法计算缫丝时长");
|
||||
var jbRows = await _jizuBanciRepo.FindAsync(x => ids.Contains(x.ZjrbId));
|
||||
|
||||
// 双班判据:该机组「今缫桶数 > 0」的班次个数为 2 → 18 小时,否则 9 小时
|
||||
var hasTwoBanci = jizus.Select(x => x.Jizu).Distinct().ToDictionary(
|
||||
jz => jz,
|
||||
jz => jbRows.Where(x => x.Jizu == jz && x.TongshuJinsao > 0)
|
||||
.Select(x => x.Banci).Distinct().Count() == 2);
|
||||
|
||||
// 机组总茧量 = Σ(每桶茧量 × 机组今缫桶数)
|
||||
var totalJianliang = jizus.GroupBy(x => x.Jizu).ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.Sum(x => (decimal)reportMap[x.ZjrbId].MeitongJianliang * x.TongshuJinsao));
|
||||
|
||||
var changed = 0;
|
||||
var groups = jizus.GroupBy(x => new
|
||||
{
|
||||
reportMap[x.ZjrbId].GyzkId,
|
||||
Guige = reportMap[x.ZjrbId].Guige ?? "",
|
||||
x.Jizu
|
||||
});
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var jz = group.Key.Jizu;
|
||||
var totalHours = hasTwoBanci.GetValueOrDefault(jz) ? TwoBanciHours : SingleBanciHours;
|
||||
var jizuTotal = totalJianliang.GetValueOrDefault(jz);
|
||||
var current = group.Sum(x => (decimal)reportMap[x.ZjrbId].MeitongJianliang * x.TongshuJinsao);
|
||||
var ratio = jizuTotal > 0 ? current / jizuTotal : 0m;
|
||||
var newHours = Math.Round(ratio * totalHours, 1, MidpointRounding.AwayFromZero);
|
||||
|
||||
foreach (var item in group)
|
||||
{
|
||||
if (item.JizuSaosiHours == newHours) continue;
|
||||
item.JizuSaosiHours = newHours;
|
||||
await _jizuRepo.UpdateAsync(item);
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
// ==================== 内部:口径重算 / 上下文 ====================
|
||||
|
||||
/// <summary>按 F9 编辑页口径重算派生字段(机组今存、班次重量与今存、主表合计)</summary>
|
||||
private static void Normalize(ZjrbSaveDto dto, string guige)
|
||||
{
|
||||
dto.Guige = guige;
|
||||
|
||||
foreach (var j in dto.Jizus)
|
||||
{
|
||||
if (j.JizuSaosiHours < 0) throw new ServiceException($"机组{j.Jizu}的缫丝时长不能为负数");
|
||||
if (j.JizuSaosiHours > 20) throw new ServiceException($"机组{j.Jizu}缫丝时长不能大于 20");
|
||||
j.TongshuJinsao = j.TongshuJinsaoJiaban + j.TongshuJinsaoYiban;
|
||||
if (j.TongshuJinsao > 0 && j.JizuSaosiHours <= 0)
|
||||
throw new ServiceException($"机组{j.Jizu}今缫桶数大于 0 时,请输入缫丝时长");
|
||||
|
||||
var pre = j.TongshuShangcun + j.TongshuJinsao - j.TongshuJinluo;
|
||||
if (j.TongshuJinluo == 0 && dto.StandardLuositongshu > 0 && pre >= dto.StandardLuositongshu)
|
||||
{
|
||||
j.TongshuJinluo = dto.StandardLuositongshu;
|
||||
pre -= dto.StandardLuositongshu;
|
||||
}
|
||||
j.TongshuJincun = pre;
|
||||
}
|
||||
|
||||
foreach (var b in dto.Bancis)
|
||||
{
|
||||
if (b.BaoshuShangcun > 0 && b.JianliangShangcun <= 0)
|
||||
throw new ServiceException($"{b.Banci}班上存包数大于 0 时,请填写上存重量");
|
||||
|
||||
var tongshu = dto.Jizus.Sum(j => b.Banci == Prod_Banci_Enum.甲
|
||||
? j.TongshuJinsaoJiaban
|
||||
: j.TongshuJinsaoYiban);
|
||||
|
||||
b.JianliangSaosi = Math.Round(tongshu * (decimal)dto.MeitongJianliang / 1000m, 1,
|
||||
MidpointRounding.AwayFromZero);
|
||||
|
||||
if (dto.LiantongBiaozhong > 0)
|
||||
{
|
||||
b.JianliangLingru = Math.Round(b.BaoshuLingru * dto.LiantongBiaozhong, 1,
|
||||
MidpointRounding.AwayFromZero);
|
||||
b.JianliangTuihui = Math.Round(b.BaoshuTuihui * dto.LiantongBiaozhong, 1,
|
||||
MidpointRounding.AwayFromZero);
|
||||
b.BaoshuSaosi = (int)Math.Round(b.JianliangSaosi / dto.LiantongBiaozhong, 0,
|
||||
MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
b.JianliangJincun = b.JianliangShangcun + b.JianliangLingru - b.JianliangSaosi - b.JianliangTuihui;
|
||||
b.BaoshuJincun = b.BaoshuShangcun + b.BaoshuLingru - b.BaoshuSaosi - b.BaoshuTuihui;
|
||||
}
|
||||
|
||||
// 主表 = 甲班 + 乙班
|
||||
dto.BaoshuShangcun = dto.Bancis.Sum(x => x.BaoshuShangcun);
|
||||
dto.BaoshuLingru = dto.Bancis.Sum(x => x.BaoshuLingru);
|
||||
dto.BaoshuSaosi = dto.Bancis.Sum(x => x.BaoshuSaosi);
|
||||
dto.BaoshuTuihui = dto.Bancis.Sum(x => x.BaoshuTuihui);
|
||||
dto.BaoshuJincun = dto.Bancis.Sum(x => x.BaoshuJincun);
|
||||
dto.JianliangShangcun = dto.Bancis.Sum(x => x.JianliangShangcun);
|
||||
dto.JianliangLingru = dto.Bancis.Sum(x => x.JianliangLingru);
|
||||
dto.JianliangSaosi = dto.Bancis.Sum(x => x.JianliangSaosi);
|
||||
dto.JianliangTuihui = dto.Bancis.Sum(x => x.JianliangTuihui);
|
||||
dto.JianliangJincun = dto.Bancis.Sum(x => x.JianliangJincun);
|
||||
}
|
||||
|
||||
private static void CopyMaster(ZjrbSaveDto dto, ZjrbRecord master)
|
||||
{
|
||||
master.Date = dto.Date.Date;
|
||||
master.DeptId = dto.DepId;
|
||||
master.GyzkId = dto.GyzkId;
|
||||
master.Guige = dto.Guige ?? "";
|
||||
master.LastDate = dto.LastDate;
|
||||
master.StandardLuositongshu = dto.StandardLuositongshu;
|
||||
master.MeitongJianliang = dto.MeitongJianliang;
|
||||
master.BaoshuShangcun = dto.BaoshuShangcun;
|
||||
master.BaoshuLingru = dto.BaoshuLingru;
|
||||
master.BaoshuSaosi = dto.BaoshuSaosi;
|
||||
master.BaoshuTuihui = dto.BaoshuTuihui;
|
||||
master.BaoshuJincun = dto.BaoshuJincun;
|
||||
master.JianliangShangcun = dto.JianliangShangcun;
|
||||
master.JianliangLingru = dto.JianliangLingru;
|
||||
master.JianliangSaosi = dto.JianliangSaosi;
|
||||
master.JianliangTuihui = dto.JianliangTuihui;
|
||||
master.JianliangJincun = dto.JianliangJincun;
|
||||
}
|
||||
|
||||
private static ZjrbBanciDto ToBanciDto(ZjrbBanci x) => new()
|
||||
{
|
||||
Id = x.Id,
|
||||
ZjrbId = x.ZjrbId,
|
||||
Banci = x.Banci,
|
||||
BanciName = x.Banci.ToString(),
|
||||
BaoshuShangcun = x.BaoshuShangcun,
|
||||
BaoshuLingru = x.BaoshuLingru,
|
||||
BaoshuSaosi = x.BaoshuSaosi,
|
||||
BaoshuTuihui = x.BaoshuTuihui,
|
||||
BaoshuJincun = x.BaoshuJincun,
|
||||
JianliangShangcun = x.JianliangShangcun,
|
||||
JianliangLingru = x.JianliangLingru,
|
||||
JianliangSaosi = x.JianliangSaosi,
|
||||
JianliangTuihui = x.JianliangTuihui,
|
||||
JianliangJincun = x.JianliangJincun
|
||||
};
|
||||
|
||||
private static ZjrbJizuDto ToJizuDto(ZjrbJizu x, List<ZjrbJizuBanci> jbRows) => new()
|
||||
{
|
||||
Id = x.Id,
|
||||
ZjrbId = x.ZjrbId,
|
||||
Jizu = x.Jizu,
|
||||
JizuSaosiHours = x.JizuSaosiHours,
|
||||
TongshuShangcun = x.TongshuShangcun,
|
||||
TongshuJinsaoJiaban = jbRows.FirstOrDefault(b => b.ZjrbJizuId == x.Id && b.Banci == Prod_Banci_Enum.甲)
|
||||
?.TongshuJinsao ?? 0,
|
||||
TongshuJinsaoYiban = jbRows.FirstOrDefault(b => b.ZjrbJizuId == x.Id && b.Banci == Prod_Banci_Enum.乙)
|
||||
?.TongshuJinsao ?? 0,
|
||||
TongshuJinsao = x.TongshuJinsao,
|
||||
TongshuJinluo = x.TongshuJinluo,
|
||||
TongshuJincun = x.TongshuJincun
|
||||
};
|
||||
|
||||
/// <summary>车间班次配置;未配置时固定甲乙两班</summary>
|
||||
private async Task<List<Prod_Banci_Enum>> GetBanciKindsAsync(long depId)
|
||||
{
|
||||
var cfgs = await _cjbcRepo.FindAsync(x => x.DeptId == depId);
|
||||
if (cfgs.Count == 0) return new List<Prod_Banci_Enum> { Prod_Banci_Enum.甲, Prod_Banci_Enum.乙 };
|
||||
return cfgs.Select(x => x.Banci).Distinct().OrderBy(x => x).ToList();
|
||||
}
|
||||
|
||||
private async Task<decimal> GetLiantongBiaozhongAsync()
|
||||
{
|
||||
var row = await _registryRepo.FirstOrDefaultAsync(x =>
|
||||
x.Category == RegistryCategory && x.KeyName == RegistryKey);
|
||||
if (row == null || string.IsNullOrWhiteSpace(row.Value)) return 0m;
|
||||
return decimal.TryParse(row.Value, out var v) ? v : 0m;
|
||||
}
|
||||
|
||||
private sealed class Ctx
|
||||
{
|
||||
public Dictionary<long, string> DeptNames { get; init; } = new();
|
||||
public Dictionary<long, string> GyzkNames { get; init; } = new();
|
||||
}
|
||||
|
||||
private async Task<Ctx> BuildContextAsync()
|
||||
{
|
||||
var depts = await _deptRepo.GetAllAsync();
|
||||
var gyzks = await _gyzkRepo.GetAllAsync();
|
||||
return new Ctx
|
||||
{
|
||||
DeptNames = depts.ToDictionary(d => d.Id, d => d.Name),
|
||||
GyzkNames = gyzks.ToDictionary(z => z.Id, z => z.ProductionDisplayName is { Length: > 0 } n
|
||||
? n
|
||||
: z.ZhuangKouName)
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user