diff --git a/backend/F9WebAiTest.Api/Controllers/Prod/ChejianBanciController.cs b/backend/F9WebAiTest.Api/Controllers/Prod/ChejianBanciController.cs new file mode 100644 index 0000000..99eaf9f --- /dev/null +++ b/backend/F9WebAiTest.Api/Controllers/Prod/ChejianBanciController.cs @@ -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; + +/// 车间班次(前缫车间排班配置) +[ApiController] +[Route("api/prod/chejianbanci")] +[Authorize] +public class ChejianBanciController : ControllerBase +{ + private readonly IChejianBanciService _svc; + public ChejianBanciController(IChejianBanciService svc) => _svc = svc; + + // 分页列表 + [HttpGet] + public async Task GetPaged([FromQuery] string like = "", [FromQuery] int pageIndex = 1, + [FromQuery] int pageSize = 20) + => Ok(ApiResult>.Ok( + await _svc.GetPagedAsync(pageIndex, pageSize, like ?? string.Empty))); + + // 车间下拉(前缫车间) + [HttpGet("qianxian-deps")] + public async Task GetQianxianDeps() + => Ok(ApiResult>.Ok(await _svc.GetQianxianDepsAsync())); + + // 编辑回显 + [HttpGet("{id}")] + public async Task GetByIdForEdit(long id) + { + try { return Ok(ApiResult.Ok(await _svc.GetByIdForEditAsync(id))); } + catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult.Fail(ex.Message, ex.Code)); } + } + + // 新增 + [HttpPost] + public async Task Create([FromBody] ChejianbanciReadDto_Edit dto) + { + try + { + await _svc.CreateAsync(dto); + return Ok(ApiResult.Ok(null, "新增成功")); + } + catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult.Fail(ex.Message, ex.Code)); } + } + + // 修改 + [HttpPut("{id}")] + public async Task Update(long id, [FromBody] ChejianbanciReadDto_Edit dto) + { + try + { + await _svc.UpdateAsync(id, dto); + return Ok(ApiResult.Ok(null, "修改成功")); + } + catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult.Fail(ex.Message, ex.Code)); } + } + + // 删除 + [HttpDelete("{id}")] + public async Task Delete(long id) + { + try + { + await _svc.DeleteAsync(id); + return Ok(ApiResult.Ok(null, "删除成功")); + } + catch (ServiceException ex) { return StatusCode(ex.Code, ApiResult.Fail(ex.Message, ex.Code)); } + } +} diff --git a/backend/F9WebAiTest.Api/f9_web_ai_test_rbac.db b/backend/F9WebAiTest.Api/f9_web_ai_test_rbac.db index e73e072..5831c94 100644 Binary files a/backend/F9WebAiTest.Api/f9_web_ai_test_rbac.db and b/backend/F9WebAiTest.Api/f9_web_ai_test_rbac.db differ diff --git a/backend/F9WebAiTest.Application/DependencyInjection.cs b/backend/F9WebAiTest.Application/DependencyInjection.cs index 3c822dd..8ad078a 100644 --- a/backend/F9WebAiTest.Application/DependencyInjection.cs +++ b/backend/F9WebAiTest.Application/DependencyInjection.cs @@ -23,6 +23,7 @@ public static class DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); // MediatR:注册命令/查询 Handler diff --git a/backend/F9WebAiTest.Application/Dtos/Prod/ChejianBanciDtos.cs b/backend/F9WebAiTest.Application/Dtos/Prod/ChejianBanciDtos.cs new file mode 100644 index 0000000..4d9b08d --- /dev/null +++ b/backend/F9WebAiTest.Application/Dtos/Prod/ChejianBanciDtos.cs @@ -0,0 +1,30 @@ +using F9WebAiTest.Domain.Enums.SYS.Employees; + +namespace F9WebAiTest.Application.Dtos.Prod; + +/// 车间班次读模型(列表) +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; } +} + +/// 车间班次编辑读模型 / 提交模型 +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; } +} + +/// 车间下拉项 +public class ChejianbanciDepOptionDto +{ + public long Id { get; set; } + public string Name { get; set; } = ""; +} diff --git a/backend/F9WebAiTest.Application/Services/Prod/ChejianBanciService.cs b/backend/F9WebAiTest.Application/Services/Prod/ChejianBanciService.cs new file mode 100644 index 0000000..e920101 --- /dev/null +++ b/backend/F9WebAiTest.Application/Services/Prod/ChejianBanciService.cs @@ -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> GetPagedAsync(int pageIndex, int pageSize, string keyword); + Task GetByIdForEditAsync(long id); + Task CreateAsync(ChejianbanciReadDto_Edit dto); + Task UpdateAsync(long id, ChejianbanciReadDto_Edit dto); + Task DeleteAsync(long id); + + /// 车间下拉:只取职责类型为「前缫」的部门 + Task> GetQianxianDepsAsync(); +} + +public class ChejianBanciService : IChejianBanciService +{ + private readonly IRepository _repo; + private readonly IRepository _deptRepo; + + public ChejianBanciService(IRepository repo, IRepository deptRepo) + { + _repo = repo; + _deptRepo = deptRepo; + } + + public async Task> 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 + { + Items = items, + Total = total, + PageIndex = pageIndex, + PageSize = pageSize + }; + } + + public async Task 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> 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(); + } + + /// 校验:车间、班次、工时必填,且同一车间的同一班次不可重复 + 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("该车间的此班次已存在,请勿重复配置"); + } +} diff --git a/backend/F9WebAiTest.Domain/Entities/Prod/ChejianBanci.cs b/backend/F9WebAiTest.Domain/Entities/Prod/ChejianBanci.cs new file mode 100644 index 0000000..e6ef2aa --- /dev/null +++ b/backend/F9WebAiTest.Domain/Entities/Prod/ChejianBanci.cs @@ -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; + +/// 车间班次(前缫车间的排班配置:车间 + 班次 + 日工作小时数) +[Table(Name = "ChejianBancis")] +public class ChejianBanci : BaseEntity +{ + public long DeptId { get; set; } // 车间(部门) + public Prod_Banci_Enum Banci { get; set; } // 班次(甲/乙) + public double KaoqinDayhours { get; set; } // 日工作小时数 +} diff --git a/backend/F9WebAiTest.Infrastructure/Data/DbSeeder.cs b/backend/F9WebAiTest.Infrastructure/Data/DbSeeder.cs index 5bb70a2..b313243 100644 --- a/backend/F9WebAiTest.Infrastructure/Data/DbSeeder.cs +++ b/backend/F9WebAiTest.Infrastructure/Data/DbSeeder.cs @@ -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 } } + /// 车间班次菜单(挂在"基础数据"目录下) + 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}"); + } + } + + /// + /// 修正前缫部门的职责类型:历史数据迁移时 duty_type 统一写成了"其他"(5), + /// 而车间班次等模块需要按职责类型(前缫=25)筛选部门。 + /// + 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}"); + } + } + /// 确保指定菜单已授权给角色(后续 FixMenuPermissions 会把 permission_id 修正为权限单元 Id) private static void EnsureMenuPermission(IFreeSql fsql, ISnowflakeIdGenerator snowflake, long roleId, long menuId) { diff --git a/web/src/router/index.js b/web/src/router/index.js index 1be628e..dd004fa 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -54,6 +54,7 @@ export const componentMap = { 'base/jizuchetai/index': () => import('../views/base/jizuchetai/index.vue'), 'base/worktype/index': () => import('../views/base/worktype/index.vue'), 'base/zhangqi/index': () => import('../views/base/zhangqi/index.vue'), + 'base/chejianbanci/index': () => import('../views/base/chejianbanci/index.vue'), 'base/registry/index': () => import('../views/base/registry/index.vue'), 'base/yuanliaozhuangkou/index': () => import('../views/base/yuanliaozhuangkou/index.vue'), 'base/wuzimulu/index': () => import('../views/base/wuzimulu/index.vue'), diff --git a/web/src/views/base/chejianbanci/index.vue b/web/src/views/base/chejianbanci/index.vue new file mode 100644 index 0000000..c9ac71f --- /dev/null +++ b/web/src/views/base/chejianbanci/index.vue @@ -0,0 +1,199 @@ + + + + +