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.Departments;
using F9WebAiTest.Domain.Enums.SYS.Employees;
using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments;
using SysEmp = F9WebAiTest.Domain.Entities.SYS.Employees;
namespace F9WebAiTest.Application.Services.Prod;
///
/// 看台排班(生产-前缫):车头工 / 挡车工 / 值班长三类排班的查询与维护。
/// 对应 F9 WinClient\F9.Business.Prod\QS\KTPB 下的 Page_KTPB 与三个编辑页。
///
public interface IKtpbService
{
/// 车间下拉(职责类型=前缫)
Task> GetQianxianDepsAsync();
/// 指定车间的机组下拉(含车台范围,来源于机组车台)
Task> GetJizuOptionsAsync(long depId);
// 车头工
Task> GetCtgPagedAsync(long depId, Prod_Banci_Enum? banci, int? jizu,
string keyword, int pageIndex, int pageSize);
Task GetCtgByIdAsync(long id);
Task CreateCtgAsync(KtpbctgReadDto_View dto);
Task UpdateCtgAsync(long id, KtpbctgReadDto_View dto);
Task DeleteCtgAsync(long id);
// 挡车工
Task> GetDcgPagedAsync(long depId, Prod_Banci_Enum? banci, int? jizu,
string keyword, int pageIndex, int pageSize);
/// 取某个「车间+班次+机组」下的整组排班(批量编辑弹窗用)
Task> GetDcgGroupAsync(long depId, Prod_Banci_Enum banci, int jizu);
/// 整组替换:先删除该组旧排班,再按提交内容重建(挡车数由服务端重算)
Task SaveDcgBatchAsync(KtpbdcgBatchDto dto);
Task DeleteDcgAsync(long id);
// 值班长
Task> GetZbzPagedAsync(long depId, Prod_Banci_Enum? banci,
string keyword, int pageIndex, int pageSize);
Task GetZbzByIdAsync(long id);
Task CreateZbzAsync(KtpbzbzReadDto_View dto);
Task UpdateZbzAsync(long id, KtpbzbzReadDto_View dto);
Task DeleteZbzAsync(long id);
}
public class KtpbService : IKtpbService
{
private readonly IRepository _ctgRepo;
private readonly IRepository _dcgRepo;
private readonly IRepository _zbzRepo;
private readonly IRepository _jzctRepo;
private readonly IRepository _deptRepo;
private readonly IRepository _empRepo;
public KtpbService(IRepository ctgRepo, IRepository dcgRepo, IRepository zbzRepo,
IRepository jzctRepo, IRepository deptRepo,
IRepository empRepo)
{
_ctgRepo = ctgRepo;
_dcgRepo = dcgRepo;
_zbzRepo = zbzRepo;
_jzctRepo = jzctRepo;
_deptRepo = deptRepo;
_empRepo = empRepo;
}
// ==================== 下拉 ====================
public async Task> GetQianxianDepsAsync()
{
var depts = await _deptRepo.GetAllAsync();
return depts
.Where(d => d.DutyType == Department_DutyType_Enum.前缫)
.OrderBy(d => d.SortOrder).ThenBy(d => d.Id)
.Select(d => new ChejianbanciDepOptionDto { Id = d.Id, Name = d.Name })
.ToList();
}
public async Task> GetJizuOptionsAsync(long depId)
{
var list = await _jzctRepo.FindAsync(x => x.DepartmentId == depId);
return list.OrderBy(x => x.Jizu)
.Select(x => new KtpbJizuOptionDto
{
Jizu = x.Jizu,
ChetaiStart = x.ChetaiStart,
ChetaiEnd = x.ChetaiEnd
})
.ToList();
}
// ==================== 车头工 ====================
public async Task> GetCtgPagedAsync(long depId, Prod_Banci_Enum? banci,
int? jizu, string keyword, int pageIndex, int pageSize)
{
if (pageIndex <= 0) pageIndex = 1;
if (pageSize <= 0) pageSize = 20;
var deptDict = await GetDeptDictAsync();
var empDict = await GetEmpDictAsync();
var all = await _ctgRepo.GetAllAsync();
var q = all.AsEnumerable();
if (depId > 0) q = q.Where(x => x.DeptId == depId);
if (banci.HasValue) q = q.Where(x => x.Banci == banci.Value);
if (jizu.HasValue && jizu.Value > 0) q = q.Where(x => x.Jizu == jizu.Value);
var views = q.OrderBy(x => x.DeptId).ThenBy(x => x.Banci).ThenBy(x => x.Jizu)
.Select(x => ToCtgView(x, deptDict, empDict)).ToList();
if (!string.IsNullOrWhiteSpace(keyword))
{
var kw = keyword.Trim();
views = views.Where(x => x.DeptName.Contains(kw) || x.BanciName.Contains(kw)
|| x.Jizu.ToString().Contains(kw) || x.Ygid1Name.Contains(kw) || x.Ygid2Name.Contains(kw)).ToList();
}
return Paged(views, pageIndex, pageSize);
}
public async Task GetCtgByIdAsync(long id)
{
var e = await _ctgRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
return ToCtgView(e, await GetDeptDictAsync(), await GetEmpDictAsync());
}
public async Task CreateCtgAsync(KtpbctgReadDto_View dto)
{
await ValidateGroupAsync(dto.DepId, dto.Banci, dto.Jizu, 0);
if (dto.Ygid1 <= 0) throw new ServiceException("请选择车头工1");
await _ctgRepo.AddAsync(new KtpbCtg
{
DeptId = dto.DepId,
Banci = dto.Banci,
Jizu = dto.Jizu,
Ygid1 = dto.Ygid1,
Ygid2 = dto.Ygid2
});
await _ctgRepo.SaveChangesAsync();
}
public async Task UpdateCtgAsync(long id, KtpbctgReadDto_View dto)
{
var e = await _ctgRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
await ValidateGroupAsync(dto.DepId, dto.Banci, dto.Jizu, id);
if (dto.Ygid1 <= 0) throw new ServiceException("请选择车头工1");
e.DeptId = dto.DepId;
e.Banci = dto.Banci;
e.Jizu = dto.Jizu;
e.Ygid1 = dto.Ygid1;
e.Ygid2 = dto.Ygid2;
await _ctgRepo.UpdateAsync(e);
await _ctgRepo.SaveChangesAsync();
}
public async Task DeleteCtgAsync(long id)
{
var e = await _ctgRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
await _ctgRepo.DeleteAsync(e);
await _ctgRepo.SaveChangesAsync();
}
// ==================== 挡车工 ====================
public async Task> GetDcgPagedAsync(long depId, Prod_Banci_Enum? banci,
int? jizu, string keyword, int pageIndex, int pageSize)
{
if (pageIndex <= 0) pageIndex = 1;
if (pageSize <= 0) pageSize = 20;
var deptDict = await GetDeptDictAsync();
var empDict = await GetEmpDictAsync();
var all = await _dcgRepo.GetAllAsync();
var q = all.AsEnumerable();
if (depId > 0) q = q.Where(x => x.DeptId == depId);
if (banci.HasValue) q = q.Where(x => x.Banci == banci.Value);
if (jizu.HasValue && jizu.Value > 0) q = q.Where(x => x.Jizu == jizu.Value);
var views = q.OrderBy(x => x.DeptId).ThenBy(x => x.Banci).ThenBy(x => x.Jizu)
.ThenBy(x => x.ChetaiStart)
.Select(x => ToDcgView(x, deptDict, empDict)).ToList();
if (!string.IsNullOrWhiteSpace(keyword))
{
var kw = keyword.Trim();
views = views.Where(x => x.DeptName.Contains(kw) || x.BanciName.Contains(kw)
|| x.YgName.Contains(kw) || x.Jizu.ToString().Contains(kw)
|| x.ChetaiStart.ToString().Contains(kw) || x.ChetaiEnd.ToString().Contains(kw)).ToList();
}
return Paged(views, pageIndex, pageSize);
}
public async Task> GetDcgGroupAsync(long depId, Prod_Banci_Enum banci, int jizu)
{
if (depId <= 0) throw new ServiceException("请选择车间");
var deptDict = await GetDeptDictAsync();
var empDict = await GetEmpDictAsync();
var list = await _dcgRepo.FindAsync(x => x.DeptId == depId && x.Banci == banci && x.Jizu == jizu);
return list.OrderBy(x => x.ChetaiStart).ThenBy(x => x.ChetaiEnd)
.Select(x => ToDcgView(x, deptDict, empDict)).ToList();
}
public async Task SaveDcgBatchAsync(KtpbdcgBatchDto dto)
{
if (dto.DepId <= 0) throw new ServiceException("请选择车间");
if (dto.Jizu <= 0) throw new ServiceException("请选择机组");
var items = dto.Items ?? new List();
foreach (var it in items)
{
if (it.YgId <= 0) throw new ServiceException("存在未选择挡车工的记录");
if (it.ChetaiStart <= 0) throw new ServiceException($"挡车工「{it.YgName}」的起始车台必须大于 0");
if (it.ChetaiEnd < it.ChetaiStart)
throw new ServiceException($"挡车工「{it.YgName}」的结束车台不能小于起始车台");
}
var jzct = await _jzctRepo.FirstOrDefaultAsync(x => x.DepartmentId == dto.DepId && x.Jizu == dto.Jizu);
if (jzct != null)
{
foreach (var it in items)
{
if (it.ChetaiStart < jzct.ChetaiStart || it.ChetaiEnd > jzct.ChetaiEnd)
throw new ServiceException(
$"挡车工「{it.YgName}」的车台范围 {it.ChetaiStart}-{it.ChetaiEnd} 超出该机组车台范围 " +
$"{jzct.ChetaiStart}-{jzct.ChetaiEnd}");
}
}
// 整组替换:先删后建(对齐 F9 BatchReplaceKtpbdcgCommand)
var olds = await _dcgRepo.FindAsync(x => x.DeptId == dto.DepId && x.Banci == dto.Banci && x.Jizu == dto.Jizu);
if (olds.Count > 0) await _dcgRepo.HardDeleteRangeAsync(olds);
var dangche = RecalcDangcheshu(items);
foreach (var it in items)
{
await _dcgRepo.AddAsync(new KtpbDcg
{
DeptId = dto.DepId,
Banci = dto.Banci,
Jizu = dto.Jizu,
ChetaiStart = it.ChetaiStart,
ChetaiEnd = it.ChetaiEnd,
YgId = it.YgId,
Dangcheshu = dangche.GetValueOrDefault(BuildDcgKey(it.ChetaiStart, it.ChetaiEnd, it.YgId), 0)
});
}
await _dcgRepo.SaveChangesAsync();
return items.Count;
}
public async Task DeleteDcgAsync(long id)
{
var e = await _dcgRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
await _dcgRepo.DeleteAsync(e);
await _dcgRepo.SaveChangesAsync();
}
///
/// 挡车数计算(移植 F9 Page_KTPBDCG_Edit.CalculateSingleDangcheshu):
/// 每个车台默认权重 1;若该车台被多条记录共享,且同时是某记录的起始点与某记录的结束点(接壤车台),权重取 0.5。
///
private static Dictionary RecalcDangcheshu(List records)
{
var result = new Dictionary();
if (records.Count == 0) return result;
var counts = new Dictionary();
var starts = new HashSet();
var ends = new HashSet();
foreach (var r in records)
{
starts.Add(r.ChetaiStart);
ends.Add(r.ChetaiEnd);
for (var i = r.ChetaiStart; i <= r.ChetaiEnd; i++)
counts[i] = counts.GetValueOrDefault(i) + 1;
}
foreach (var r in records)
{
double total = 0;
for (var i = r.ChetaiStart; i <= r.ChetaiEnd; i++)
{
double weight = 1;
if (counts.GetValueOrDefault(i) > 1 && starts.Contains(i) && ends.Contains(i)) weight = 0.5;
total += weight;
}
result[BuildDcgKey(r.ChetaiStart, r.ChetaiEnd, r.YgId)] = Math.Round(total, 2);
}
return result;
}
private static string BuildDcgKey(int start, int end, long ygId) => $"{start}-{end}-{ygId}";
// ==================== 值班长 ====================
public async Task> GetZbzPagedAsync(long depId, Prod_Banci_Enum? banci,
string keyword, int pageIndex, int pageSize)
{
if (pageIndex <= 0) pageIndex = 1;
if (pageSize <= 0) pageSize = 20;
var deptDict = await GetDeptDictAsync();
var empDict = await GetEmpDictAsync();
var all = await _zbzRepo.GetAllAsync();
var q = all.AsEnumerable();
if (depId > 0) q = q.Where(x => x.DeptId == depId);
if (banci.HasValue) q = q.Where(x => x.Banci == banci.Value);
var views = q.OrderBy(x => x.DeptId).ThenBy(x => x.Banci).ThenBy(x => x.Id)
.Select(x => ToZbzView(x, deptDict, empDict)).ToList();
if (!string.IsNullOrWhiteSpace(keyword))
{
var kw = keyword.Trim();
views = views.Where(x => x.DeptName.Contains(kw) || x.BanciName.Contains(kw) || x.YgName.Contains(kw))
.ToList();
}
return Paged(views, pageIndex, pageSize);
}
public async Task GetZbzByIdAsync(long id)
{
var e = await _zbzRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
return ToZbzView(e, await GetDeptDictAsync(), await GetEmpDictAsync());
}
public async Task CreateZbzAsync(KtpbzbzReadDto_View dto)
{
if (dto.DepId <= 0) throw new ServiceException("请选择车间");
if (dto.YgId <= 0) throw new ServiceException("请选择值班长");
var dup = await _zbzRepo.AnyAsync(x => x.DeptId == dto.DepId && x.Banci == dto.Banci && x.YgId == dto.YgId);
if (dup) throw new ServiceException("该车间该班次已存在此值班长排班");
await _zbzRepo.AddAsync(new KtpbZbz { DeptId = dto.DepId, Banci = dto.Banci, YgId = dto.YgId });
await _zbzRepo.SaveChangesAsync();
}
public async Task UpdateZbzAsync(long id, KtpbzbzReadDto_View dto)
{
var e = await _zbzRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
if (dto.DepId <= 0) throw new ServiceException("请选择车间");
if (dto.YgId <= 0) throw new ServiceException("请选择值班长");
var dup = await _zbzRepo.AnyAsync(x => x.Id != id && x.DeptId == dto.DepId
&& x.Banci == dto.Banci && x.YgId == dto.YgId);
if (dup) throw new ServiceException("该车间该班次已存在此值班长排班");
e.DeptId = dto.DepId;
e.Banci = dto.Banci;
e.YgId = dto.YgId;
await _zbzRepo.UpdateAsync(e);
await _zbzRepo.SaveChangesAsync();
}
public async Task DeleteZbzAsync(long id)
{
var e = await _zbzRepo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
await _zbzRepo.DeleteAsync(e);
await _zbzRepo.SaveChangesAsync();
}
// ==================== 公共 ====================
/// 车头工:同一「车间+班次+机组」只能有一条排班
private async Task ValidateGroupAsync(long depId, Prod_Banci_Enum banci, int jizu, long excludeId)
{
if (depId <= 0) throw new ServiceException("请选择车间");
if (jizu <= 0) throw new ServiceException("请选择机组");
var dup = await _ctgRepo.AnyAsync(x => x.Id != excludeId && x.DeptId == depId
&& x.Banci == banci && x.Jizu == jizu);
if (dup) throw new ServiceException("该车间该班次该机组已有车头工排班");
}
private async Task> GetDeptDictAsync()
=> (await _deptRepo.GetAllAsync()).ToDictionary(d => d.Id, d => d.Name);
private async Task> GetEmpDictAsync()
=> (await _empRepo.GetAllAsync()).ToDictionary(e => e.Id, e => e.Name);
private static KtpbctgReadDto_View ToCtgView(KtpbCtg x, Dictionary deptDict,
Dictionary empDict) => new()
{
Id = x.Id,
DepId = x.DeptId,
DeptName = deptDict.GetValueOrDefault(x.DeptId, ""),
Banci = x.Banci,
BanciName = x.Banci.ToString(),
Jizu = x.Jizu,
Ygid1 = x.Ygid1,
Ygid1Name = empDict.GetValueOrDefault(x.Ygid1, ""),
Ygid2 = x.Ygid2,
Ygid2Name = empDict.GetValueOrDefault(x.Ygid2, "")
};
private static KtpbdcgReadDto_View ToDcgView(KtpbDcg x, Dictionary deptDict,
Dictionary empDict) => new()
{
Id = x.Id,
DepId = x.DeptId,
DeptName = deptDict.GetValueOrDefault(x.DeptId, ""),
Banci = x.Banci,
BanciName = x.Banci.ToString(),
Jizu = x.Jizu,
ChetaiStart = x.ChetaiStart,
ChetaiEnd = x.ChetaiEnd,
YgId = x.YgId,
YgName = empDict.GetValueOrDefault(x.YgId, ""),
Dangcheshu = x.Dangcheshu
};
private static KtpbzbzReadDto_View ToZbzView(KtpbZbz x, Dictionary deptDict,
Dictionary empDict) => new()
{
Id = x.Id,
DepId = x.DeptId,
DeptName = deptDict.GetValueOrDefault(x.DeptId, ""),
Banci = x.Banci,
BanciName = x.Banci.ToString(),
YgId = x.YgId,
YgName = empDict.GetValueOrDefault(x.YgId, "")
};
private static PageResult Paged(List all, int pageIndex, int pageSize) => new()
{
Items = all.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList(),
Total = all.Count,
PageIndex = pageIndex,
PageSize = pageSize
};
}