新增「车间班次」功能(前缫车间排班配置)

后端
- 新增实体 ChejianBanci(表 ChejianBancis:车间 / 班次 / 日工作小时数)
- 新增 ChejianBanciService 与 ChejianBanciController(api/prod/chejianbanci)
  分页列表、编辑回显、新增、修改、删除、前缫车间下拉;含必填校验与同车间同班次重复校验
- 菜单种子:基础数据 → 车间班次(Timer 图标,权限 base:chejianbanci:list)
- 修正历史数据:名称以「前缫」开头的部门职责类型改为「前缫」(25)。
  迁移时 duty_type 被统一写成「其他」,导致按职责类型筛选车间取不到数据

前端
- 新增 web/src/views/base/chejianbanci/index.vue(列表 / 搜索 / 分页 / 新增 / 编辑 / 删除)
- 注册组件映射 base/chejianbanci/index
This commit is contained in:
yanpeng200212
2026-10-12 16:33:40 +08:00
parent ba5b90d2ce
commit 2985003896
9 changed files with 496 additions and 0 deletions
@@ -0,0 +1,75 @@
using F9WebAiTest.Application.Common;
using F9WebAiTest.Application.Dtos.Prod;
using F9WebAiTest.Application.Services.Prod;
using F9WebAiTest.Domain.Common;
using F9WebAiTest.Domain.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace F9WebAiTest.Api.Controllers.Prod;
/// <summary>车间班次(前缫车间排班配置)</summary>
[ApiController]
[Route("api/prod/chejianbanci")]
[Authorize]
public class ChejianBanciController : ControllerBase
{
private readonly IChejianBanciService _svc;
public ChejianBanciController(IChejianBanciService svc) => _svc = svc;
// 分页列表
[HttpGet]
public async Task<IActionResult> GetPaged([FromQuery] string like = "", [FromQuery] int pageIndex = 1,
[FromQuery] int pageSize = 20)
=> Ok(ApiResult<PageResult<ChejianbanciReadDto_View>>.Ok(
await _svc.GetPagedAsync(pageIndex, pageSize, like ?? string.Empty)));
// 车间下拉(前缫车间)
[HttpGet("qianxian-deps")]
public async Task<IActionResult> GetQianxianDeps()
=> Ok(ApiResult<List<ChejianbanciDepOptionDto>>.Ok(await _svc.GetQianxianDepsAsync()));
// 编辑回显
[HttpGet("{id}")]
public async Task<IActionResult> GetByIdForEdit(long id)
{
try { return Ok(ApiResult<ChejianbanciReadDto_Edit>.Ok(await _svc.GetByIdForEditAsync(id))); }
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
}
// 新增
[HttpPost]
public async Task<IActionResult> Create([FromBody] ChejianbanciReadDto_Edit dto)
{
try
{
await _svc.CreateAsync(dto);
return Ok(ApiResult<object>.Ok(null, "新增成功"));
}
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
}
// 修改
[HttpPut("{id}")]
public async Task<IActionResult> Update(long id, [FromBody] ChejianbanciReadDto_Edit dto)
{
try
{
await _svc.UpdateAsync(id, dto);
return Ok(ApiResult<object>.Ok(null, "修改成功"));
}
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
}
// 删除
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(long id)
{
try
{
await _svc.DeleteAsync(id);
return Ok(ApiResult<object>.Ok(null, "删除成功"));
}
catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult<object>.Fail(ex.Message, ex.Code)); }
}
}
Binary file not shown.
@@ -23,6 +23,7 @@ public static class DependencyInjection
services.AddScoped<ILsjlService, LsjlService>();
services.AddScoped<IXdjyService, XdjyService>();
services.AddScoped<IHbjyService, HbjyService>();
services.AddScoped<IChejianBanciService, ChejianBanciService>();
services.AddScoped<INotificationService, NotificationService>();
// MediatR:注册命令/查询 Handler
@@ -0,0 +1,30 @@
using F9WebAiTest.Domain.Enums.SYS.Employees;
namespace F9WebAiTest.Application.Dtos.Prod;
/// <summary>车间班次读模型(列表)</summary>
public class ChejianbanciReadDto_View
{
public long Id { get; set; }
public long DepId { get; set; }
public string Deptname { get; set; } = "";
public Prod_Banci_Enum Banci { get; set; }
public string BanciName { get; set; } = "";
public double KaoqinDayhours { get; set; }
}
/// <summary>车间班次编辑读模型 / 提交模型</summary>
public class ChejianbanciReadDto_Edit
{
public long Id { get; set; }
public long DepId { get; set; }
public Prod_Banci_Enum Banci { get; set; }
public double KaoqinDayhours { get; set; }
}
/// <summary>车间下拉项</summary>
public class ChejianbanciDepOptionDto
{
public long Id { get; set; }
public string Name { get; set; } = "";
}
@@ -0,0 +1,138 @@
using F9WebAiTest.Application.Common;
using F9WebAiTest.Application.Dtos.Prod;
using F9WebAiTest.Domain.Common;
using F9WebAiTest.Domain.Entities;
using F9WebAiTest.Domain.Entities.Base;
using F9WebAiTest.Domain.Entities.Prod;
using F9WebAiTest.Domain.Enums.SYS.Departments;
using SysDept = F9WebAiTest.Domain.Entities.SYS.Departments;
namespace F9WebAiTest.Application.Services.Prod;
public interface IChejianBanciService
{
Task<PageResult<ChejianbanciReadDto_View>> GetPagedAsync(int pageIndex, int pageSize, string keyword);
Task<ChejianbanciReadDto_Edit> GetByIdForEditAsync(long id);
Task CreateAsync(ChejianbanciReadDto_Edit dto);
Task UpdateAsync(long id, ChejianbanciReadDto_Edit dto);
Task DeleteAsync(long id);
/// <summary>车间下拉:只取职责类型为「前缫」的部门</summary>
Task<List<ChejianbanciDepOptionDto>> GetQianxianDepsAsync();
}
public class ChejianBanciService : IChejianBanciService
{
private readonly IRepository<ChejianBanci> _repo;
private readonly IRepository<SysDept.Department> _deptRepo;
public ChejianBanciService(IRepository<ChejianBanci> repo, IRepository<SysDept.Department> deptRepo)
{
_repo = repo;
_deptRepo = deptRepo;
}
public async Task<PageResult<ChejianbanciReadDto_View>> GetPagedAsync(int pageIndex, int pageSize, string keyword)
{
if (pageIndex <= 0) pageIndex = 1;
if (pageSize <= 0) pageSize = 20;
var depts = await _deptRepo.GetAllAsync();
var deptDict = depts.ToDictionary(d => d.Id, d => d.Name);
var all = await _repo.GetAllAsync();
var q = all.AsQueryable();
if (!string.IsNullOrWhiteSpace(keyword))
{
var kw = keyword.Trim();
var kwDeptIds = depts.Where(d => d.Name.Contains(kw)).Select(d => d.Id).ToHashSet();
q = q.Where(x => kwDeptIds.Contains(x.DeptId) || x.Banci.ToString().Contains(kw));
}
var total = q.LongCount();
var items = q.OrderBy(x => x.DeptId).ThenBy(x => x.Banci)
.Skip((pageIndex - 1) * pageSize).Take(pageSize)
.Select(x => new ChejianbanciReadDto_View
{
Id = x.Id,
DepId = x.DeptId,
Deptname = deptDict.GetValueOrDefault(x.DeptId, ""),
Banci = x.Banci,
BanciName = x.Banci.ToString(),
KaoqinDayhours = x.KaoqinDayhours
}).ToList();
return new PageResult<ChejianbanciReadDto_View>
{
Items = items,
Total = total,
PageIndex = pageIndex,
PageSize = pageSize
};
}
public async Task<ChejianbanciReadDto_Edit> GetByIdForEditAsync(long id)
{
var e = await _repo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
return new ChejianbanciReadDto_Edit
{
Id = e.Id,
DepId = e.DeptId,
Banci = e.Banci,
KaoqinDayhours = e.KaoqinDayhours
};
}
public async Task CreateAsync(ChejianbanciReadDto_Edit dto)
{
await ValidateAsync(dto, 0);
await _repo.AddAsync(new ChejianBanci
{
DeptId = dto.DepId,
Banci = dto.Banci,
KaoqinDayhours = dto.KaoqinDayhours
});
await _repo.SaveChangesAsync();
}
public async Task UpdateAsync(long id, ChejianbanciReadDto_Edit dto)
{
var e = await _repo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
await ValidateAsync(dto, id);
e.DeptId = dto.DepId;
e.Banci = dto.Banci;
e.KaoqinDayhours = dto.KaoqinDayhours;
await _repo.UpdateAsync(e);
await _repo.SaveChangesAsync();
}
public async Task DeleteAsync(long id)
{
var e = await _repo.GetByIdAsync(id) ?? throw new ServiceException("记录不存在", 404);
await _repo.DeleteAsync(e);
await _repo.SaveChangesAsync();
}
public async Task<List<ChejianbanciDepOptionDto>> 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();
}
/// <summary>校验:车间、班次、工时必填,且同一车间的同一班次不可重复</summary>
private async Task ValidateAsync(ChejianbanciReadDto_Edit dto, long excludeId)
{
if (dto.DepId <= 0) throw new ServiceException("请选择车间");
if (dto.KaoqinDayhours <= 0) throw new ServiceException("日工作小时数必须大于 0");
var all = await _repo.GetAllAsync();
if (all.Any(x => x.Id != excludeId && x.DeptId == dto.DepId && x.Banci == dto.Banci))
throw new ServiceException("该车间的此班次已存在,请勿重复配置");
}
}
@@ -0,0 +1,15 @@
using FreeSql.DataAnnotations;
using F9WebAiTest.Domain.Common;
using F9WebAiTest.Domain.Entities.Base;
using F9WebAiTest.Domain.Enums.SYS.Employees;
namespace F9WebAiTest.Domain.Entities.Prod;
/// <summary>车间班次(前缫车间的排班配置:车间 + 班次 + 日工作小时数)</summary>
[Table(Name = "ChejianBancis")]
public class ChejianBanci : BaseEntity
{
public long DeptId { get; set; } // 车间(部门)
public Prod_Banci_Enum Banci { get; set; } // 班次(甲/乙)
public double KaoqinDayhours { get; set; } // 日工作小时数
}
@@ -82,6 +82,10 @@ public static class DbSeeder
// 6.68 生产模块(按工序拆分:生产-前缫 / 生产-后缫)
SeedProdMenus(fsql, snowflake);
// 6.69 车间班次菜单(挂在"基础数据"目录下)+ 修正前缫部门的职责类型
SeedChejianBanciMenus(fsql, snowflake);
FixQianxianDutyType(fsql);
// 6.7 为菜单创建权限单元(Permission),并修正旧菜单权限关联(统一权限模型)
FixMenuPermissions(fsql, snowflake);
@@ -763,6 +767,39 @@ public static class DbSeeder
}
}
/// <summary>车间班次菜单(挂在"基础数据"目录下)</summary>
private static void SeedChejianBanciMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
EnsureBaseDataMenu(fsql, snowflake, "/base/chejianbanci", "车间班次",
"base/chejianbanci/index", "base:chejianbanci:list", "Timer", 11);
Console.WriteLine("[DbSeeder] 车间班次菜单已就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 车间班次菜单种子跳过:{ex}");
}
}
/// <summary>
/// 修正前缫部门的职责类型:历史数据迁移时 duty_type 统一写成了"其他"(5),
/// 而车间班次等模块需要按职责类型(前缫=25)筛选部门。
/// </summary>
private static void FixQianxianDutyType(IFreeSql fsql)
{
try
{
var rows = fsql.Ado.ExecuteNonQuery(
"UPDATE sys_departments SET duty_type = 25 WHERE is_deleted = 0 AND name LIKE '前缫%' AND duty_type <> 25");
if (rows > 0) Console.WriteLine($"[DbSeeder] 已修正 {rows} 个前缫部门的职责类型");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 部门职责类型修正跳过:{ex.Message}");
}
}
/// <summary>确保指定菜单已授权给角色(后续 FixMenuPermissions 会把 permission_id 修正为权限单元 Id)</summary>
private static void EnsureMenuPermission(IFreeSql fsql, ISnowflakeIdGenerator snowflake, long roleId, long menuId)
{