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.Enums.SYS.Employees;
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;
///
/// 看台记录(生产-前缫):按「日期 + 车间 + 班次」维护车头工 / 挡车工的实际看台情况。
/// 对应 F9 Page_Ktjl(列表)、Page_Ktjl_Dcg_Edit / Page_Ktjl_Ctg_Edit(录入弹窗)与
/// Ktjl_Dcg_SaveCommand / Ktjl_Ctg_SaveCommand(整组先删后建)。
///
public interface IKtjlService
{
// 查询
Task> GetCtgAsync(DateTime date, long depId, Prod_Banci_Enum banci, string like);
Task> GetDcgAsync(DateTime date, long depId, Prod_Banci_Enum banci, string like);
// 保存 / 删除(整组)
Task SaveCtgAsync(KtjlctgBatchDto dto);
Task SaveDcgAsync(KtjldcgBatchDto dto);
Task DeleteCtgAsync(DateTime date, long depId, Prod_Banci_Enum banci);
Task DeleteDcgAsync(DateTime date, long depId, Prod_Banci_Enum banci);
/// 录入(新增):按看台排班 + 该车间工种职工,带出默认记录
Task> BuildCtgDefaultsAsync(DateTime date, long depId, Prod_Banci_Enum banci);
Task> BuildDcgDefaultsAsync(DateTime date, long depId, Prod_Banci_Enum banci);
/// 工种下拉(用于筛选/展示)
Task> GetWorktypeOptionsAsync();
}
public class ProdWorktypeOptionDto
{
public long Id { get; set; }
public string Name { get; set; } = "";
}
public class KtjlService : IKtjlService
{
private const decimal DefaultHours = 9m;
private readonly IRepository _ctgRepo;
private readonly IRepository _dcgRepo;
private readonly IRepository _ktpbCtgRepo;
private readonly IRepository _ktpbDcgRepo;
private readonly IRepository _jzctRepo;
private readonly IRepository _deptRepo;
private readonly IRepository _empRepo;
private readonly IRepository _wtRepo;
public KtjlService(IRepository ctgRepo, IRepository dcgRepo,
IRepository ktpbCtgRepo, IRepository ktpbDcgRepo,
IRepository jzctRepo, IRepository deptRepo,
IRepository empRepo, IRepository wtRepo)
{
_ctgRepo = ctgRepo;
_dcgRepo = dcgRepo;
_ktpbCtgRepo = ktpbCtgRepo;
_ktpbDcgRepo = ktpbDcgRepo;
_jzctRepo = jzctRepo;
_deptRepo = deptRepo;
_empRepo = empRepo;
_wtRepo = wtRepo;
}
// ==================== 查询 ====================
public async Task> GetCtgAsync(DateTime date, long depId, Prod_Banci_Enum banci,
string like)
{
var d = date.Date;
var ctx = await BuildContextAsync();
var rows = (await _ctgRepo.FindAsync(x => x.Date == d && x.DeptId == depId && x.Banci == banci))
.OrderBy(x => x.Jizu).ThenBy(x => x.Hours).ToList();
var views = rows.Select(x => ToCtgView(x, ctx)).ToList();
return Filter(views, like, v => $"{v.EmployeeName}{v.WorktypeName}{v.EmployeeDepName}{v.Jizu}");
}
public async Task> GetDcgAsync(DateTime date, long depId, Prod_Banci_Enum banci,
string like)
{
var d = date.Date;
var ctx = await BuildContextAsync();
var rows = (await _dcgRepo.FindAsync(x => x.Date == d && x.DeptId == depId && x.Banci == banci))
.OrderBy(x => x.Jizu).ThenBy(x => x.ChetaiStart).ThenBy(x => x.Hours).ToList();
var views = rows.Select(x => ToDcgView(x, ctx)).ToList();
return Filter(views, like,
v => $"{v.EmployeeName}{v.WorktypeName}{v.EmployeeDepName}{v.Jizu}{v.ChetaiStart}{v.ChetaiEnd}");
}
private static List Filter(List rows, string like, Func selector)
{
if (string.IsNullOrWhiteSpace(like)) return rows;
var kw = like.Trim();
return rows.Where(x => selector(x).Contains(kw, StringComparison.OrdinalIgnoreCase)).ToList();
}
// ==================== 保存 ====================
public async Task SaveCtgAsync(KtjlctgBatchDto dto)
{
if (dto.DepId <= 0) throw new ServiceException("请选择车间");
var items = dto.Items ?? new List();
foreach (var it in items)
{
if (it.EmployeeId <= 0) throw new ServiceException("不能有空数据(存在未选择职工的记录)");
if (it.Hours < 0) throw new ServiceException($"「{it.EmployeeName}」的时长不能为负数");
}
var d = dto.Date.Date;
await DeleteCtgCoreAsync(d, dto.DepId, dto.Banci);
foreach (var it in items)
{
await _ctgRepo.AddAsync(new KtjlCtg
{
Date = d,
DeptId = dto.DepId,
Banci = dto.Banci,
Jizu = it.Jizu,
Hours = it.Hours,
EmployeeId = it.EmployeeId,
EmployeeDepId = it.EmployeeDepId,
WorktypeId = it.WorktypeId,
WorktypeName = it.WorktypeName ?? string.Empty,
NoChanliang = it.NoChanliang
});
}
await _ctgRepo.SaveChangesAsync();
return items.Count;
}
public async Task SaveDcgAsync(KtjldcgBatchDto dto)
{
if (dto.DepId <= 0) throw new ServiceException("请选择车间");
var items = dto.Items ?? new List();
foreach (var it in items)
{
if (it.EmployeeId <= 0) throw new ServiceException("不能有空数据(存在未选择职工的记录)");
if (it.ChetaiStart <= 0) throw new ServiceException($"「{it.EmployeeName}」请填写开始车位");
if (it.ChetaiEnd < it.ChetaiStart)
throw new ServiceException($"「{it.EmployeeName}」结束车位不能小于开始车位");
if (it.Hours < 0) throw new ServiceException($"「{it.EmployeeName}」的时长不能为负数");
}
var d = dto.Date.Date;
var jzcts = (await _jzctRepo.FindAsync(x => x.DepartmentId == dto.DepId)).OrderBy(x => x.Jizu).ToList();
// 挡车数 / 机组 由服务端重算(对齐 F9 CalDangcheNum + CalJizu)
foreach (var it in items)
{
var startShared = items.Any(x => x.EmployeeId != it.EmployeeId && x.ChetaiEnd == it.ChetaiStart);
var endShared = items.Any(x => x.EmployeeId != it.EmployeeId && x.ChetaiStart == it.ChetaiEnd);
it.DangcheNum = it.ChetaiEnd + 1 - it.ChetaiStart
- (startShared ? 0.5m : 0m) - (endShared ? 0.5m : 0m);
var jzct = jzcts.FirstOrDefault(x => x.ChetaiStart <= it.ChetaiEnd && x.ChetaiEnd >= it.ChetaiStart);
if (jzct != null) it.Jizu = jzct.Jizu;
}
await DeleteDcgCoreAsync(d, dto.DepId, dto.Banci);
foreach (var it in items)
{
await _dcgRepo.AddAsync(new KtjlDcg
{
Date = d,
DeptId = dto.DepId,
Banci = dto.Banci,
Jizu = it.Jizu,
ChetaiStart = it.ChetaiStart,
ChetaiEnd = it.ChetaiEnd,
DangcheNum = it.DangcheNum,
Hours = it.Hours,
EmployeeId = it.EmployeeId,
EmployeeDepId = it.EmployeeDepId,
WorktypeId = it.WorktypeId,
WorktypeName = it.WorktypeName ?? string.Empty,
NoChanliang = it.NoChanliang
});
}
await _dcgRepo.SaveChangesAsync();
return items.Count;
}
public async Task DeleteCtgAsync(DateTime date, long depId, Prod_Banci_Enum banci)
{
if (depId <= 0) throw new ServiceException("请选择车间");
await DeleteCtgCoreAsync(date.Date, depId, banci);
await _ctgRepo.SaveChangesAsync();
}
public async Task DeleteDcgAsync(DateTime date, long depId, Prod_Banci_Enum banci)
{
if (depId <= 0) throw new ServiceException("请选择车间");
await DeleteDcgCoreAsync(date.Date, depId, banci);
await _dcgRepo.SaveChangesAsync();
}
private async Task DeleteCtgCoreAsync(DateTime date, long depId, Prod_Banci_Enum banci)
{
var olds = await _ctgRepo.FindAsync(x => x.Date == date && x.DeptId == depId && x.Banci == banci);
if (olds.Count > 0) await _ctgRepo.HardDeleteRangeAsync(olds);
}
private async Task DeleteDcgCoreAsync(DateTime date, long depId, Prod_Banci_Enum banci)
{
var olds = await _dcgRepo.FindAsync(x => x.Date == date && x.DeptId == depId && x.Banci == banci);
if (olds.Count > 0) await _dcgRepo.HardDeleteRangeAsync(olds);
}
// ==================== 默认记录(录入(新增)) ====================
public async Task> BuildCtgDefaultsAsync(DateTime date, long depId,
Prod_Banci_Enum banci)
{
var ctx = await BuildContextAsync();
var deptName = ctx.DeptNames.GetValueOrDefault(depId, "");
var plans = (await _ktpbCtgRepo.FindAsync(x => x.DeptId == depId && x.Banci == banci)).OrderBy(x => x.Jizu);
var result = new List();
foreach (var p in plans)
{
foreach (var ygId in new[] { p.Ygid1, p.Ygid2 })
{
if (ygId <= 0 || !ctx.Emps.TryGetValue(ygId, out var emp)) continue;
result.Add(new KtjlctgReadDto_View
{
Date = date.Date,
DepId = depId,
DepName = deptName,
Banci = banci,
BanciName = banci.ToString(),
Jizu = p.Jizu,
Hours = DefaultHours,
EmployeeId = emp.Id,
EmployeeName = emp.Name,
EmployeeDepId = emp.DepId,
EmployeeDepName = ctx.DeptNames.GetValueOrDefault(emp.DepId, ""),
WorktypeId = emp.WorktypeId,
WorktypeName = ctx.WorktypeNames.GetValueOrDefault(emp.WorktypeId, ""),
NoChanliang = !emp.IsZhuanzheng
});
}
}
return result;
}
public async Task> BuildDcgDefaultsAsync(DateTime date, long depId,
Prod_Banci_Enum banci)
{
var ctx = await BuildContextAsync();
var deptName = ctx.DeptNames.GetValueOrDefault(depId, "");
var plans = (await _ktpbDcgRepo.FindAsync(x => x.DeptId == depId && x.Banci == banci))
.OrderBy(x => x.Jizu).ThenBy(x => x.ChetaiStart);
var result = new List();
foreach (var p in plans)
{
if (p.YgId <= 0 || !ctx.Emps.TryGetValue(p.YgId, out var emp)) continue;
result.Add(new KtjldcgReadDto_View
{
Date = date.Date,
DepId = depId,
DepName = deptName,
Banci = banci,
BanciName = banci.ToString(),
Jizu = p.Jizu,
ChetaiStart = p.ChetaiStart,
ChetaiEnd = p.ChetaiEnd,
DangcheNum = (decimal)p.Dangcheshu,
Hours = DefaultHours,
EmployeeId = emp.Id,
EmployeeName = emp.Name,
EmployeeDepId = emp.DepId,
EmployeeDepName = ctx.DeptNames.GetValueOrDefault(emp.DepId, ""),
WorktypeId = emp.WorktypeId,
WorktypeName = ctx.WorktypeNames.GetValueOrDefault(emp.WorktypeId, ""),
NoChanliang = !emp.IsZhuanzheng
});
}
return result;
}
public async Task> GetWorktypeOptionsAsync()
=> (await _wtRepo.GetAllAsync()).OrderBy(x => x.SortOrder).ThenBy(x => x.Id)
.Select(x => new ProdWorktypeOptionDto { Id = x.Id, Name = x.WorkName })
.ToList();
// ==================== 上下文 ====================
private sealed class Ctx
{
public Dictionary DeptNames { get; init; } = new();
public Dictionary WorktypeNames { get; init; } = new();
public Dictionary Emps { get; init; } = new();
}
private sealed class EmpInfo
{
public long Id { get; init; }
public string Name { get; init; } = "";
public long DepId { get; init; }
public long WorktypeId { get; init; }
public bool IsZhuanzheng { get; init; }
}
private async Task BuildContextAsync()
{
var depts = await _deptRepo.GetAllAsync();
var wts = await _wtRepo.GetAllAsync();
var emps = await _empRepo.GetAllAsync();
return new Ctx
{
DeptNames = depts.ToDictionary(d => d.Id, d => d.Name),
WorktypeNames = wts.ToDictionary(w => w.Id, w => w.WorkName),
Emps = emps.ToDictionary(e => e.Id, e => new EmpInfo
{
Id = e.Id,
Name = e.Name,
DepId = e.DepId,
WorktypeId = e.WorktypeId,
IsZhuanzheng = e.IsZhuanzheng
})
};
}
private static KtjlctgReadDto_View ToCtgView(KtjlCtg x, Ctx ctx) => new()
{
Id = x.Id,
Date = x.Date,
DepId = x.DeptId,
DepName = ctx.DeptNames.GetValueOrDefault(x.DeptId, ""),
Banci = x.Banci,
BanciName = x.Banci.ToString(),
Jizu = x.Jizu,
Hours = x.Hours,
EmployeeId = x.EmployeeId,
EmployeeName = ctx.Emps.GetValueOrDefault(x.EmployeeId)?.Name ?? "",
EmployeeDepId = x.EmployeeDepId,
EmployeeDepName = ctx.DeptNames.GetValueOrDefault(x.EmployeeDepId, ""),
WorktypeId = x.WorktypeId,
WorktypeName = string.IsNullOrWhiteSpace(x.WorktypeName)
? ctx.WorktypeNames.GetValueOrDefault(x.WorktypeId, "")
: x.WorktypeName,
NoChanliang = x.NoChanliang
};
private static KtjldcgReadDto_View ToDcgView(KtjlDcg x, Ctx ctx) => new()
{
Id = x.Id,
Date = x.Date,
DepId = x.DeptId,
DepName = ctx.DeptNames.GetValueOrDefault(x.DeptId, ""),
Banci = x.Banci,
BanciName = x.Banci.ToString(),
Jizu = x.Jizu,
ChetaiStart = x.ChetaiStart,
ChetaiEnd = x.ChetaiEnd,
DangcheNum = x.DangcheNum,
Hours = x.Hours,
EmployeeId = x.EmployeeId,
EmployeeName = ctx.Emps.GetValueOrDefault(x.EmployeeId)?.Name ?? "",
EmployeeDepId = x.EmployeeDepId,
EmployeeDepName = ctx.DeptNames.GetValueOrDefault(x.EmployeeDepId, ""),
WorktypeId = x.WorktypeId,
WorktypeName = string.IsNullOrWhiteSpace(x.WorktypeName)
? ctx.WorktypeNames.GetValueOrDefault(x.WorktypeId, "")
: x.WorktypeName,
NoChanliang = x.NoChanliang
};
}