From 2985003896c0fb277b5991c70e9418dfe7e51c95 Mon Sep 17 00:00:00 2001 From: yanpeng200212 <32453@DESKTOP-HTJ2T0B> Date: Mon, 12 Oct 2026 16:33:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E3=80=8C=E8=BD=A6=E9=97=B4?= =?UTF-8?q?=E7=8F=AD=E6=AC=A1=E3=80=8D=E5=8A=9F=E8=83=BD=EF=BC=88=E5=89=8D?= =?UTF-8?q?=E7=BC=AB=E8=BD=A6=E9=97=B4=E6=8E=92=E7=8F=AD=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端 - 新增实体 ChejianBanci(表 ChejianBancis:车间 / 班次 / 日工作小时数) - 新增 ChejianBanciService 与 ChejianBanciController(api/prod/chejianbanci) 分页列表、编辑回显、新增、修改、删除、前缫车间下拉;含必填校验与同车间同班次重复校验 - 菜单种子:基础数据 → 车间班次(Timer 图标,权限 base:chejianbanci:list) - 修正历史数据:名称以「前缫」开头的部门职责类型改为「前缫」(25)。 迁移时 duty_type 被统一写成「其他」,导致按职责类型筛选车间取不到数据 前端 - 新增 web/src/views/base/chejianbanci/index.vue(列表 / 搜索 / 分页 / 新增 / 编辑 / 删除) - 注册组件映射 base/chejianbanci/index --- .../Prod/ChejianBanciController.cs | 75 +++++++ .../F9WebAiTest.Api/f9_web_ai_test_rbac.db | Bin 1150976 -> 1159168 bytes .../DependencyInjection.cs | 1 + .../Dtos/Prod/ChejianBanciDtos.cs | 30 +++ .../Services/Prod/ChejianBanciService.cs | 138 ++++++++++++ .../Entities/Prod/ChejianBanci.cs | 15 ++ .../Data/DbSeeder.cs | 37 ++++ web/src/router/index.js | 1 + web/src/views/base/chejianbanci/index.vue | 199 ++++++++++++++++++ 9 files changed, 496 insertions(+) create mode 100644 backend/F9WebAiTest.Api/Controllers/Prod/ChejianBanciController.cs create mode 100644 backend/F9WebAiTest.Application/Dtos/Prod/ChejianBanciDtos.cs create mode 100644 backend/F9WebAiTest.Application/Services/Prod/ChejianBanciService.cs create mode 100644 backend/F9WebAiTest.Domain/Entities/Prod/ChejianBanci.cs create mode 100644 web/src/views/base/chejianbanci/index.vue 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 e73e0725e3a58700f9a051242e19683415e2ed17..5831c9482f7c84ba04e2e8d454b41000f4fd7890 100644 GIT binary patch delta 2639 zcmaJ@eP~iTe?%Fxb{+f!!xr zvLknUa6r2HanA4le&-y$+>74ab?*cp{>qHZw5Dl1+%rNzkg~AEI zo&eAW1SevhJg~DO2YE?|Mlz~1RwfhG$v9^dnFaFG=eB>6Yc6bGpobr_(AMiX5qZ@5 zjrErGdg0I&`l{88PkxMx<-fX%ixqCYLFYQjRxNdt?yGC9?@?^p_jl;y+SMauz4rH; z^x?YJx{s9h@?pmg-A1++x_4+ViO;=CYw5gj`7?SvPCrn0Tr&&f(olJxClUF(Z;8flEGkJJv2sQ04##Cg#-nonQa#Gj03k^OtY@@+Ini%H+`R z?AU+j0|$;oYEsRXwP~&u=lu4l0Y@E(~B7~PL-Ql@xb`S-Wc`2=oEDy+YAUr5Lx&{Zs{k?EuF@yZvgaW3-gX50Vj~{GT>MVx+X@r-rzJy#Ca~BnD(W{V=-Rk9g+F? zP%@@2hca?1+~w&D`GX2KKC?7DIidD1X2R(xVBN{H-o@0)++ zPA1*o!md;k0TH0^THBgU8yT&;Z?!yGP;_#V9mxWM66cEgeQK5izkoQltQVuF&1u0&8cdM@qFs)<-k znHn3OKA=~rrWqIIc@|+q+t%7klnSpYOrWVAkExf~#skH~JHIjf#qg3LVF2{o^mFuS zx{dmT+M+V#o&`M$gkde&d6koz%143z6uox_?Dx?)gHtDW^T| zpvM+;_nz|D9^U_>wsxCp*Is#y%4t9U5!r6gSFzS|$Z*thGLk-*NcBaQr_+n`8LdMj z?>~R(4uhi+hY3d$4l@o54r}4ko#xOBoY}NQJYX^s)_1Jy){xa^{)c79a>bIh^q7BT ze#U&#d|%UFn>LzeOvWZ;x?}o{=?A9o;Oks#ENQ*9`P+&@`hLb63{RcRf-w1MQuq^= z5i5k3;8(grtq6~C86*Qiz$)T(eFbAIUBuz7%@6RRY$QCT?az=PJ<|yPyPgW@ zU$r2>vPkE&*YR5$w`)KG?sriK?REUt#)mZ^eNAzD9bcNA;+rK9R~;~k1(3&_c1_3A zdpKWw_n@Kpu#$dvG-VM&v8-^#(0uWawIGN@7SAtEUokXata`PZNsyK2$16W{LUp!?)^;ZP$hY zX+^2U86o-Esd>c=^CuhV{pAy8L>6tf)^E4gX9QxV?biCtCli>B8MjVnpTL|BG{&8A z`__rf{&GA_{BJ>4PUHW@|8}~-0~VF-CL5U7@K1m6o`rY&+x^T9>Wr+@^PV%iu(Gl_ zI4exAeZj0c{hS0N+w@1znMGMyS?@T@Lj)CHFiSJCZnu5G?8XQbcXO5mi*H~1k~usL zsC3J8_5c=bCiVkB>l;|xw}i3)F^FaZV)pG@LOB9nF^e$Ina=LOsR>k*!_3ks-6%hu z(V0_x`*vqeFCI3=YzAjW6QDVqX^PKSZn7+7$z(BPUdLRFFmbP7;C;)z0_dzc-0drVa{@6J5QEq}K+Frod_c^(); 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 @@ + + + + +