diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/CreateDepartmentUserInfoDto.cs b/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/CreateDepartmentUserInfoDto.cs new file mode 100644 index 0000000..a7481c5 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/CreateDepartmentUserInfoDto.cs @@ -0,0 +1,17 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NetCorePal.D3Shop.Admin.Shared.Dtos.Identity +{ + + /// + /// 创建部门用户信息 + /// + /// + /// + public record CreateDepartmentUserInfoDto(AdminUserId UserId, string UserName); +} diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/UpdateDepartmentUserInfoDto.cs b/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/UpdateDepartmentUserInfoDto.cs new file mode 100644 index 0000000..7d9f839 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Dtos/Identity/UpdateDepartmentUserInfoDto.cs @@ -0,0 +1,17 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NetCorePal.D3Shop.Admin.Shared.Dtos.Identity +{ + + /// + /// 更新部门用户信息 + /// + /// + /// + public record UpdateDepartmentUserInfoDto(AdminUserId UserId, string UserName); +} diff --git a/src/NetCorePal.D3Shop.Admin.Shared/NetCorePal.D3Shop.Admin.Shared.csproj b/src/NetCorePal.D3Shop.Admin.Shared/NetCorePal.D3Shop.Admin.Shared.csproj index 335f1a5..d9b64e0 100644 --- a/src/NetCorePal.D3Shop.Admin.Shared/NetCorePal.D3Shop.Admin.Shared.csproj +++ b/src/NetCorePal.D3Shop.Admin.Shared/NetCorePal.D3Shop.Admin.Shared.csproj @@ -7,13 +7,13 @@ - - - + + + - + diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Permission/PermissionCodes.cs b/src/NetCorePal.D3Shop.Admin.Shared/Permission/PermissionCodes.cs index a48ff55..65e96bd 100644 --- a/src/NetCorePal.D3Shop.Admin.Shared/Permission/PermissionCodes.cs +++ b/src/NetCorePal.D3Shop.Admin.Shared/Permission/PermissionCodes.cs @@ -25,4 +25,13 @@ public static class PermissionCodes public const string RoleView = nameof(RoleView); #endregion + + #region DepartmentManagement + public const string DepartmentManagement = nameof(DepartmentManagement); + public const string DepartmentCreate = nameof(DepartmentCreate); + public const string DepartmentEdit = nameof(DepartmentEdit); + public const string DepartmentView = nameof(DepartmentView); + public const string DepartmentDelete = nameof(DepartmentDelete); + + #endregion } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateDepartmentRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateDepartmentRequest.cs new file mode 100644 index 0000000..ecfd7b7 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Requests/CreateDepartmentRequest.cs @@ -0,0 +1,17 @@ +using NetCorePal.D3Shop.Admin.Shared.Dtos.Identity; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using System.ComponentModel.DataAnnotations; + +namespace NetCorePal.D3Shop.Admin.Shared.Requests; + +public class CreateDepartmentRequest +{ + [Required] public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public DeptId ParentId { get; set; } = new DeptId(0); + + public IEnumerable Users { get; set; } = []; +} + diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Requests/DepartmentQueryRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/DepartmentQueryRequest.cs new file mode 100644 index 0000000..8c1d0c0 --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Requests/DepartmentQueryRequest.cs @@ -0,0 +1,8 @@ +using NetCorePal.Extensions.Dto; + +namespace NetCorePal.D3Shop.Admin.Shared.Requests; + +public class DepartmentQueryRequest : PageRequest +{ + public string? Name { get; set; } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateDepartmentInfoRequest.cs b/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateDepartmentInfoRequest.cs new file mode 100644 index 0000000..2d9ca1e --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Requests/UpdateDepartmentInfoRequest.cs @@ -0,0 +1,12 @@ +using NetCorePal.D3Shop.Admin.Shared.Dtos.Identity; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using System.ComponentModel.DataAnnotations; + +namespace NetCorePal.D3Shop.Admin.Shared.Requests; + +public class UpdateDepartmentInfoRequest +{ + [Required] public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public IEnumerable Users { get; set; } = []; +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Admin.Shared/Responses/DepartmentResponse.cs b/src/NetCorePal.D3Shop.Admin.Shared/Responses/DepartmentResponse.cs new file mode 100644 index 0000000..65599dc --- /dev/null +++ b/src/NetCorePal.D3Shop.Admin.Shared/Responses/DepartmentResponse.cs @@ -0,0 +1,11 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; + +namespace NetCorePal.D3Shop.Admin.Shared.Responses; + +public class DepartmentResponse(DeptId id, string name, string description) +{ + public DeptId Id { get; } = id; + public string Name { get; set; } = name; + public string Description { get; set; } = description; +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/AdminUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/AdminUser.cs index 7887420..e3aaeb3 100644 --- a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/AdminUser.cs +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/AdminUser.cs @@ -1,4 +1,5 @@ -using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using NetCorePal.Extensions.Domain; using NetCorePal.Extensions.Primitives; @@ -19,6 +20,11 @@ protected AdminUser() public string Password { get; private set; } = string.Empty; public DateTime CreatedAt { get; init; } public virtual ICollection Roles { get; } = []; + + public virtual ICollection UserDepts { get; } = []; + + + public virtual ICollection Permissions { get; } = []; public bool IsDeleted { get; private set; } public DateTime? DeletedAt { get; private set; } @@ -47,6 +53,12 @@ public void UpdateRoleInfo(RoleId roleId, string roleName) savedRole?.UpdateRoleInfo(roleName); } + public void SetUserDepts(DeptId deptId, string deptName) + { + var savedDept = UserDepts.FirstOrDefault(r => r.DeptId == deptId); + savedDept?.UpdateDeptInfo(deptName); + } + public void UpdateRoles(IEnumerable rolesToBeAssigned, IEnumerable permissions) { diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/UserDept.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/UserDept.cs new file mode 100644 index 0000000..0dc315d --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/AdminUserAggregate/UserDept.cs @@ -0,0 +1,30 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate +{ + public class UserDept + { + protected UserDept() { } + + public AdminUserId AdminUserId { get; private set; } = default!; + public DeptId DeptId { get; private set; } = default!; + public string DeptName { get; private set; } = string.Empty; + + public UserDept(DeptId deptId, string deptName) + { + DeptId = deptId; + DeptName = deptName; + } + + public void UpdateDeptInfo(string deptName) + { + DeptName = deptName; + } + } +} diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/Department.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/Department.cs new file mode 100644 index 0000000..71d8410 --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/Department.cs @@ -0,0 +1,108 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.DomainEvents.Identity; +using NetCorePal.Extensions.Domain; +using NetCorePal.Extensions.Primitives; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate +{ + public partial record DeptId : IInt64StronglyTypedId; + + /// + /// 部门 + /// + public class Department : Entity, IAggregateRoot + { + + /// + /// 部门名称 + /// + + public string Name { get; private set; } = string.Empty; + + /// + /// 描述 + /// + public string Description { get; private set; } = string.Empty; + + /// + /// 父部门id + /// + + public DeptId ParentId { get; private set; } = new DeptId(0); + + public DateTime CreatedAt { get; init; } + + public bool IsDeleted { get; private set; } + public DateTime? DeletedAt { get; private set; } + + public virtual ICollection Users { get; } = []; + + + protected Department() + { + + } + + public Department(string name, string description, DeptId parentId, IEnumerable deptUsers) + { + Name = name; + Description = description; + ParentId = parentId; + CreatedAt = DateTime.Now; + foreach (var user in deptUsers) + { + Users.Add(user); + } + } + + /// + /// 修改部门信息 + /// + /// + /// + /// + public void UpdateDepartInfo(string name, string description, IEnumerable deptUsers) + { + Name = name; + Description = description; + + var currentUserMap = Users.ToDictionary(r => r.UserId); + var targetUserMap = deptUsers.ToDictionary(r => r.UserId); + + var userIdsToRemove = currentUserMap.Keys.Except(targetUserMap.Keys); + foreach (var userId in userIdsToRemove) + { + Users.Remove(currentUserMap[userId]); + } + + var userIdsToAdd = targetUserMap.Keys.Except(currentUserMap.Keys); + foreach (var userId in userIdsToAdd) + { + var targetUser = targetUserMap[userId]; + Users.Add(targetUser); + } + + AddDomainEvent(new DepartmentInfoChangedDomainEvent(this)); + } + + + public void UpdateDepartmentUserName(AdminUserId userId, string userName) + { + var savedUser = Users.FirstOrDefault(r => r.UserId == userId); + savedUser?.UpdateUserInfo(userName); + } + + + public void Delete() + { + if (IsDeleted) throw new KnownException("部门已经被删除!"); + IsDeleted = true; + DeletedAt = DateTime.Now; + } + } +} diff --git a/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/DepartmentUser.cs b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/DepartmentUser.cs new file mode 100644 index 0000000..3019c27 --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/AggregatesModel/Identity/DepartmentAggregate/DepartmentUser.cs @@ -0,0 +1,47 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate +{ + + /// + /// 部门用户 + /// + public class DepartmentUser + { + protected DepartmentUser() + { + + } + + /// + /// 部门id + /// + public DeptId DeptId { get; set; } = default!; + + /// + /// 用户名称 + /// + public string UserName { get; private set; } = string.Empty; + + /// + /// 部门id + /// + public AdminUserId UserId { get; set; } = default!; + + public DepartmentUser(string userName, AdminUserId userId) + { + UserName = userName; + UserId = userId; + } + + public void UpdateUserInfo(string userName) + { + UserName = userName; + } + } +} diff --git a/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/DepartmentInfoChangedDomainEvent.cs b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/DepartmentInfoChangedDomainEvent.cs new file mode 100644 index 0000000..cde1d6d --- /dev/null +++ b/src/NetCorePal.D3Shop.Domain/DomainEvents/Identity/DepartmentInfoChangedDomainEvent.cs @@ -0,0 +1,7 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.Extensions.Domain; + +namespace NetCorePal.D3Shop.Domain.DomainEvents.Identity; + +public record DepartmentInfoChangedDomainEvent(Department Department) : IDomainEvent; \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/ApplicationDbContext.cs b/src/NetCorePal.D3Shop.Infrastructure/ApplicationDbContext.cs index 3d6ffb8..c221a6e 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/ApplicationDbContext.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/ApplicationDbContext.cs @@ -6,6 +6,7 @@ using NetCorePal.D3Shop.Domain.AggregatesModel.DeliverAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; namespace NetCorePal.D3Shop.Infrastructure { @@ -37,6 +38,8 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura #region Identity public DbSet AdminUsers => Set(); public DbSet Roles => Set(); + + public DbSet Departments => Set(); #endregion } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs index 2bf434e..85b167c 100644 --- a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/AdminUserConfiguration.cs @@ -28,6 +28,13 @@ public void Configure(EntityTypeBuilder builder) .OnDelete(DeleteBehavior.ClientCascade); builder.Navigation(au => au.Permissions).AutoInclude(); + //配置 AdminUser 与 UserDept 的一对多关系 + builder.HasMany(au => au.UserDepts) + .WithOne() + .HasForeignKey(aup => aup.AdminUserId) + .OnDelete(DeleteBehavior.ClientCascade); + builder.Navigation(au => au.UserDepts).AutoInclude(); + builder.HasQueryFilter(au => !au.IsDeleted); } } @@ -41,6 +48,17 @@ public void Configure(EntityTypeBuilder builder) } } + internal class UserDeptConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("userDepts"); + builder.HasKey(aur => new { aur.AdminUserId, aur.DeptId }); + + + } + } + internal class AdminUserPermissionConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) diff --git a/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs new file mode 100644 index 0000000..d27aec2 --- /dev/null +++ b/src/NetCorePal.D3Shop.Infrastructure/EntityConfigurations/Identity/DepartmentConfiguration.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; + +namespace NetCorePal.D3Shop.Infrastructure.EntityConfigurations.Identity +{ + + internal class DepartmentConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("departments"); + builder.HasKey(r => r.Id); + builder.Property(r => r.Id).ValueGeneratedOnAdd().UseSnowFlakeValueGenerator(); + + //配置 Department 与 DepartmentUser 的一对多关系 + builder.HasMany(au => au.Users) + .WithOne() + .HasForeignKey(aup => aup.DeptId) + .OnDelete(DeleteBehavior.ClientCascade); + builder.Navigation(au => au.Users).AutoInclude(); + } + + internal class DepartmentUserConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("departmentUser"); + builder.HasKey(aur => new { aur.UserId, aur.DeptId }); + } + } + } +} diff --git a/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/IDepartmentRepository.cs b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/IDepartmentRepository.cs new file mode 100644 index 0000000..d4f3dcf --- /dev/null +++ b/src/NetCorePal.D3Shop.Infrastructure/Repositories/Identity/IDepartmentRepository.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.Extensions.Repository; +using NetCorePal.Extensions.Repository.EntityFrameworkCore; + +namespace NetCorePal.D3Shop.Infrastructure.Repositories.Identity +{ + public interface IDepartmentRepository : IRepository; + + public class DepartmentRepository(ApplicationDbContext context) : RepositoryBase(context), IDepartmentRepository + { + } +} diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/AddDept.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/AddDept.razor new file mode 100644 index 0000000..836b211 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/AddDept.razor @@ -0,0 +1,30 @@ +@using NetCorePal.D3Shop.Web.Admin.Client.Components.Identity + + + + + + +
+ + + + + + +
+
+ +
+
+ diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/AddDept.razor.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/AddDept.razor.cs new file mode 100644 index 0000000..1ae578d --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/AddDept.razor.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Components.Forms; + +namespace NetCorePal.D3Shop.Web.Admin.Client.Components.Identity.Dept; + +public partial class AddDept +{ + [Inject] private IDepartmentService DepartmentService { get; set; } = default!; + [Inject] private MessageService Message { get; set; } = default!; + + [Parameter] public EventCallback OnItemAdded { get; set; } + + private Form _form = default!; + + private Tabs _tabs = default!; + + // private List _assignedPermissionCodes = []; + + private CreateDepartmentRequest _newRoleModel = new(); + + private bool _modalVisible; + private bool _modalConfirmLoading; + + private void ShowModal() + { + _modalVisible = true; + } + + + private void CloseModal() + { + _modalVisible = false; + _newRoleModel = new CreateDepartmentRequest(); + // _assignedPermissionCodes.Clear(); + _tabs.GoTo(0); + } + + private async Task Form_OnFinish(EditContext editContext) + { + _modalConfirmLoading = true; + StateHasChanged(); + // _newRoleModel.PermissionCodes = _assignedPermissionCodes; + var response = await DepartmentService.CreateDepartment(_newRoleModel); + if (response.Success) + { + _ = Message.Success("创建成功!"); + CloseModal(); + await OnItemAdded.InvokeAsync(); + } + else + { + _ = Message.Error(response.Message); + } + + _modalConfirmLoading = false; + } + + private void Form_OnFinishFailed(EditContext editContext) + { + _tabs.GoTo(0); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/EditDeptInfo.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/EditDeptInfo.razor new file mode 100644 index 0000000..ed682ab --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Components/Identity/Dept/EditDeptInfo.razor @@ -0,0 +1,60 @@ +编辑 + + +
+ + + + + + +
+
+ +@code { + [CascadingParameter] public DepartmentResponse Row { get; set; } = default!; + [Inject] private IDepartmentService DepartmentService { get; set; } = default!; + [Inject] private MessageService Message { get; set; } = default!; + [Parameter] public EventCallback OnRowUpdated { get; set; } + + private bool _modalVisible; + private bool _modalConfirmLoading; + private Form _form = default!; + private UpdateDepartmentInfoRequest _deptInfoModel = new(); + + private void ShowModal() + { + _modalVisible = true; + _deptInfoModel.Name = Row.Name; + _deptInfoModel.Description = Row.Description; + } + + private async Task Form_OnFinish(EditContext editContext) + { + _modalConfirmLoading = true; + StateHasChanged(); + var response = await DepartmentService.UpdateDepartmentInfo(Row.Id, _deptInfoModel); + _modalConfirmLoading = false; + if (response.Success) + { + _modalVisible = false; + _ = Message.Success("更新成功!"); + Row.Name = _deptInfoModel.Name; + Row.Description = _deptInfoModel.Description; + await OnRowUpdated.InvokeAsync(); + } + else + { + _ = Message.Error(response.Message); + } + } + +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Layouts/BasicLayout.razor.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Layouts/BasicLayout.razor.cs index a553373..8540ed4 100644 --- a/src/NetCorePal.D3Shop.Web.Admin.Client/Layouts/BasicLayout.razor.cs +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Layouts/BasicLayout.razor.cs @@ -43,6 +43,14 @@ protected override async Task OnInitializedAsync() Key = "roles", Icon = "crown", BoundPermissionCode = PermissionCodes.RoleManagement + }, + new PermissionMenuDataItem + { + Path = "/admin/dept", + Name = "部门管理", + Key = "depts", + Icon = "crown", + BoundPermissionCode = PermissionCodes.DepartmentManagement } ]; diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Dept.razor b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Dept.razor new file mode 100644 index 0000000..1d20ed7 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Dept.razor @@ -0,0 +1,64 @@ +@page "/admin/dept" +@using NetCorePal.D3Shop.Web.Admin.Client.Extensions +@using NetCorePal.D3Shop.Web.Admin.Client.Components.Identity.Dept + + +@attribute [ClientPermission(PermissionCodes.AdminUserManagement)] + + + + + + + + + 部门列表 + + + + + + + @if (context.CheckPermission(PermissionCodes.DepartmentCreate)) + { + + } + + + + + + + + + + @if (context.CheckPermission(PermissionCodes.DepartmentEdit)) + { + + + + } + + + + @if (context.CheckPermission(PermissionCodes.DepartmentDelete)) + { + 删除 + } + + + +
\ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Dept.razor.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Dept.razor.cs new file mode 100644 index 0000000..ff2cf4e --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Pages/Dept.razor.cs @@ -0,0 +1,79 @@ +using AntDesign.TableModels; + +namespace NetCorePal.D3Shop.Web.Admin.Client.Pages; + +public sealed partial class Dept +{ + [Inject] private IDepartmentService DepartmentService { get; set; } = default!; + [Inject] private MessageService Message { get; set; } = default!; + [Inject] private ConfirmService ConfirmService { get; set; } = default!; + + private PagedData _pagedDepartments = new(default!, default, default, default); + + private Table _table = default!; + + private readonly DepartmentQueryRequest _departmentQueryRequest = new() { CountTotal = true }; + + private bool _loading; + + protected override void OnAfterRender(bool firstRender) + { + if (!firstRender) return; + _table.ReloadData(1, 10); + } + + private async Task GetPagedDepartments() + { + var response = await DepartmentService.GetAllDepartments(_departmentQueryRequest); + if (response.Success) + { + _pagedDepartments = response.Data; + _departmentQueryRequest.PageIndex = response.Data.PageIndex; + _departmentQueryRequest.PageSize = response.Data.PageSize; + } + else _ = Message.Error(response.Message); + } + + private async Task HandleItemAdded() + { + await GetPagedDepartments(); + } + + private void HandleItemUpdated() + { + _table.ReloadData(); + } + private async Task Delete(DepartmentResponse row) + { + if (!await Confirm($"确认删除部门:{row.Name}?")) + return; + var response = await DepartmentService.DeleteDepartment(row.Id); + if (response.Success) + { + _ = Message.Success("删除成功!"); + await GetPagedDepartments(); + } + else + { + _ = Message.Error(response.Message); + } + } + + private async Task Confirm(string message) + { + return await ConfirmService.Show(message, "警告", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes; + } + + private async Task OnSearch() + { + _departmentQueryRequest.PageIndex = 1; + await GetPagedDepartments(); + } + + private async Task Table_OnChange(QueryModel obj) + { + _loading = true; + await GetPagedDepartments(); + _loading = false; + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web.Admin.Client/Services/IDepartmentService.cs b/src/NetCorePal.D3Shop.Web.Admin.Client/Services/IDepartmentService.cs new file mode 100644 index 0000000..d1861ed --- /dev/null +++ b/src/NetCorePal.D3Shop.Web.Admin.Client/Services/IDepartmentService.cs @@ -0,0 +1,23 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Web.Admin.Client.Attributes; + +namespace NetCorePal.D3Shop.Web.Admin.Client.Services; + +[RefitService] +public interface IDepartmentService +{ + [Post("/api/Department/CreateDepartment")] + Task> CreateDepartment([Body] CreateDepartmentRequest request); + + [Get("/api/Department/GetAllDepartments")] + Task>> GetAllDepartments([Query] DepartmentQueryRequest request); + + [Put("/api/Department/UpdateDepartmentInfo/{id}")] + Task UpdateDepartmentInfo(DeptId id, [Body] UpdateDepartmentInfoRequest request); + + [Delete("/api/Department/DeleteDepartment/{id}")] + Task DeleteDepartment(DeptId id); + + +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateDepartmentCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateDepartmentCommand.cs new file mode 100644 index 0000000..cd316dd --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/CreateDepartmentCommand.cs @@ -0,0 +1,45 @@ +using FluentValidation; +using NetCorePal.D3Shop.Admin.Shared.Dtos.Identity; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.D3Shop.Web.Admin.Client.Pages; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Dto; +using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; + +public record CreateDepartmentCommand( + string Name, + string Description, + IEnumerable Users, + DeptId ParentId) + : ICommand; + +public class CreateDepartmentCommandValidator : AbstractValidator +{ + public CreateDepartmentCommandValidator(DepartmentQuery departmentQuery) + { + RuleFor(u => u.Name).NotEmpty().WithMessage("部门名称不能为空"); + //RuleFor(u => u.Description).NotEmpty().WithMessage("不能为空"); + RuleFor(u => u.Name).MustAsync(async (n, ct) => !await departmentQuery.DoesDepartmentExist(n, ct)) + .WithMessage(u => $"该部门已存在,Name={u.Name}"); + } +} + +public class CreateDepartmentCommandHandler(IDepartmentRepository departmentRepository) + : ICommandHandler +{ + public async Task Handle(CreateDepartmentCommand request, CancellationToken cancellationToken) + { + List departmentUsers = []; + foreach (var user in request.Users) + { + departmentUsers.Add(new DepartmentUser(user.UserName, user.UserId)); + } + var department = new Department(request.Name, request.Description, request.ParentId, departmentUsers); + await departmentRepository.AddAsync(department, cancellationToken); + return department.Id; + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteDepartmentCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteDepartmentCommand.cs new file mode 100644 index 0000000..c2a92c3 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/DeleteDepartmentCommand.cs @@ -0,0 +1,21 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; + +public record DeleteDepartmentCommand(DeptId DeptId) : ICommand; + +public class DeleteDepartmentCommandHandler(IDepartmentRepository departmentRepository) + : ICommandHandler +{ + public async Task Handle(DeleteDepartmentCommand request, CancellationToken cancellationToken) + { + var depart = await departmentRepository.GetAsync(request.DeptId, cancellationToken) ?? + throw new KnownException($"部门不存在,DeptId={request.DeptId}"); + + + depart.Delete(); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateDepartmrntInfoCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateDepartmrntInfoCommand.cs new file mode 100644 index 0000000..bfb386b --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateDepartmrntInfoCommand.cs @@ -0,0 +1,41 @@ +using FluentValidation; +using NetCorePal.D3Shop.Admin.Shared.Dtos.Identity; +using NetCorePal.D3Shop.Admin.Shared.Requests; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; + +public record UpdateDepartmrntInfoCommand(DeptId DepartmentId, string Name, string Description, IEnumerable Users) : ICommand; + + + +public class UpdateDepartmentCommandValidator : AbstractValidator +{ + public UpdateDepartmentCommandValidator(DepartmentQuery departmentQuery) + { + RuleFor(u => u.Name).NotEmpty().WithMessage("部门名称不能为空"); + } +} + +public class UpdateDepartmentInfoCommandHandler(DepartmentRepository departmentRepository) + : ICommandHandler +{ + public async Task Handle(UpdateDepartmrntInfoCommand request, CancellationToken cancellationToken) + { + var department = await departmentRepository.GetAsync(request.DepartmentId, cancellationToken) ?? + throw new KnownException($"未找到部门,DepartId = {request.DepartmentId}"); + + List departmentUsers = []; + foreach (var user in request.Users) + { + departmentUsers.Add(new DepartmentUser(user.UserName, user.UserId)); + } + + department.UpdateDepartInfo(request.Name, request.Description, departmentUsers); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateUserDeptInfoCommand.cs b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateUserDeptInfoCommand.cs new file mode 100644 index 0000000..efb443f --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Commands/Identity/UpdateUserDeptInfoCommand.cs @@ -0,0 +1,21 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Infrastructure.Repositories.Identity; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Commands.Identity; + +public record UpdateUserDeptInfoCommand(AdminUserId AdminUserId, DeptId DeptId, string DeptName) : ICommand; + +public class UpdateUserDeptInfoCommandHandler(AdminUserRepository adminUserRepository) + : ICommandHandler +{ + public async Task Handle(UpdateUserDeptInfoCommand request, CancellationToken cancellationToken) + { + var user = await adminUserRepository.GetAsync(request.AdminUserId, cancellationToken) ?? + throw new KnownException($"未找到用户,AdminUserId = {request.AdminUserId}"); + + user.SetUserDepts(request.DeptId, request.DeptName); + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/DepartmentInfoChangedDomainEventHandler.cs b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/DepartmentInfoChangedDomainEventHandler.cs new file mode 100644 index 0000000..a183b86 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/DomainEventHandlers/Identity/DepartmentInfoChangedDomainEventHandler.cs @@ -0,0 +1,22 @@ +using MediatR; +using NetCorePal.D3Shop.Domain.DomainEvents.Identity; +using NetCorePal.D3Shop.Web.Application.Commands.Identity; +using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.Extensions.Domain; + +namespace NetCorePal.D3Shop.Web.Application.DomainEventHandlers.Identity; + +public class DepartmentInfoChangedDomainEventHandler(IMediator mediator, AdminUserQuery adminUserQuery) + : IDomainEventHandler +{ + public async Task Handle(DepartmentInfoChangedDomainEvent notification, CancellationToken cancellationToken) + { + var department = notification.Department; + var adminUserIds = await adminUserQuery.GetUserIdsByDeptIdAsync(department.Id, cancellationToken); + foreach (var adminUserId in adminUserIds) + { + await mediator.Send(new UpdateUserDeptInfoCommand(adminUserId, department.Id, department.Name), + cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs index b02d7c3..feb26c8 100644 --- a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/AdminUserQuery.cs @@ -3,6 +3,7 @@ using NetCorePal.D3Shop.Admin.Shared.Requests; using NetCorePal.D3Shop.Admin.Shared.Responses; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; using NetCorePal.D3Shop.Web.Const; using NetCorePal.D3Shop.Web.Controllers.Identity.Dto; @@ -79,6 +80,13 @@ public async Task DoesAdminUserExist(string userName, CancellationToken ca .AnyAsync(au => au.Name == userName, cancellationToken); } + public async Task> GetUserIdsByDeptIdAsync(DeptId deptId, CancellationToken cancellationToken) + { + return await AdminUserSet.AsNoTracking() + .Where(x => x.UserDepts.Any(r => r.DeptId == deptId)) + .Select(x => x.Id) + .ToListAsync(cancellationToken); + } public async Task DoesAdminUserExist(RoleId roleId, CancellationToken cancellationToken) { return await AdminUserSet.AsNoTracking() diff --git a/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/DepartmentQuery.cs b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/DepartmentQuery.cs new file mode 100644 index 0000000..e8cad67 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Application/Queries/Identity/DepartmentQuery.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using NetCorePal.D3Shop.Admin.Shared.Requests; +using NetCorePal.D3Shop.Admin.Shared.Responses; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Web.Const; +using NetCorePal.D3Shop.Web.Controllers.Identity.Dto; +using NetCorePal.D3Shop.Web.Extensions; +using NetCorePal.Extensions.Dto; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Application.Queries.Identity; + +public class DepartmentQuery(ApplicationDbContext applicationDbContext) : IQuery +{ + private DbSet DepartmentSet { get; } = applicationDbContext.Departments; + + public async Task DoesDepartmentExist(string name, CancellationToken cancellationToken) + { + return await DepartmentSet.AsNoTracking() + .AnyAsync(r => r.Name == name, cancellationToken: cancellationToken); + } + + + public async Task> GetAllDepartmentsAsync(DepartmentQueryRequest queryRequest, + CancellationToken cancellationToken) + { + var departments = await DepartmentSet.AsNoTracking() + .WhereIf(!queryRequest.Name.IsNullOrWhiteSpace(), dt => dt.Name.Contains(queryRequest.Name!)) + .OrderBy(dt => dt.Id) + .Select(dt => new DepartmentResponse(dt.Id, dt.Name, dt.Description)) + .ToPagedDataAsync(queryRequest, cancellationToken); + return departments; + } + + +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs b/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs index 7c5d718..0139868 100644 --- a/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs +++ b/src/NetCorePal.D3Shop.Web/Blazor/BlazorServiceExtensions.cs @@ -9,5 +9,7 @@ public static void AddClientServices(this IServiceCollection services) { services.AddScoped(); services.AddScoped(); + services.AddScoped(); + } } \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Controllers/Identity/DepartmentController.cs b/src/NetCorePal.D3Shop.Web/Controllers/Identity/DepartmentController.cs new file mode 100644 index 0000000..70d6163 --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Controllers/Identity/DepartmentController.cs @@ -0,0 +1,73 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; +using NetCorePal.D3Shop.Admin.Shared.Permission; +using NetCorePal.D3Shop.Admin.Shared.Requests; +using NetCorePal.D3Shop.Admin.Shared.Responses; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using NetCorePal.D3Shop.Web.Admin.Client.Services; +using NetCorePal.D3Shop.Web.Application.Commands.Identity; +using NetCorePal.D3Shop.Web.Application.Commands.Identity.Dto; +using NetCorePal.D3Shop.Web.Application.Queries.Identity; +using NetCorePal.D3Shop.Web.Auth; +using NetCorePal.D3Shop.Web.Blazor; +using NetCorePal.D3Shop.Web.Helper; +using NetCorePal.Extensions.Dto; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.D3Shop.Web.Controllers.Identity; + +[Route("api/[controller]/[action]")] +[ApiController] +[KnownExceptionHandler] +[AdminPermission(PermissionCodes.DepartmentManagement)] +public class DepartmentController( + IMediator mediator, + DepartmentQuery departmentQuery) + : ControllerBase, IDepartmentService +{ + private CancellationToken CancellationToken => HttpContext?.RequestAborted ?? default; + + [HttpPost] + [AdminPermission(PermissionCodes.DepartmentCreate)] + public async Task> CreateDepartment([FromBody] CreateDepartmentRequest request) + { + + var departmentId = await mediator.Send( + new CreateDepartmentCommand(request.Name, request.Description,request.Users, request.ParentId), + CancellationToken); + + return departmentId.AsResponseData(); + } + + [HttpGet] + public async Task>> GetAllDepartments( + [FromQuery] DepartmentQueryRequest request) + { + var department = await departmentQuery.GetAllDepartmentsAsync(request, CancellationToken); + return department.AsResponseData(); + } + + + + [HttpPut("{id}")] + [AdminPermission(PermissionCodes.DepartmentEdit)] + public async Task UpdateDepartmentInfo([FromRoute] DeptId id, + [FromBody] UpdateDepartmentInfoRequest request) + { + + await mediator.Send(new UpdateDepartmrntInfoCommand(id, request.Name, request.Description,request.Users), CancellationToken); + return new ResponseData(); + } + + + [HttpDelete("{id}")] + [AdminPermission(PermissionCodes.DepartmentDelete)] + public async Task DeleteDepartment([FromRoute] DeptId id) + { + await mediator.Send(new DeleteDepartmentCommand(id), CancellationToken); + return new ResponseData(); + } + +} \ No newline at end of file diff --git a/src/NetCorePal.D3Shop.Web/Migrations/20241223113656_AddTableDepartmentUser.Designer.cs b/src/NetCorePal.D3Shop.Web/Migrations/20241223113656_AddTableDepartmentUser.Designer.cs new file mode 100644 index 0000000..aeea4ab --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Migrations/20241223113656_AddTableDepartmentUser.Designer.cs @@ -0,0 +1,308 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetCorePal.D3Shop.Infrastructure; + +#nullable disable + +namespace NetCorePal.D3Shop.Web.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20241223113656_AddTableDepartmentUser")] + partial class AddTableDepartmentUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.DeliverAggregate.DeliverRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("deliverrecord", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DeletedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Phone") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("adminUsers", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUserPermission", b => + { + b.Property("AdminUserId") + .HasColumnType("bigint"); + + b.Property("PermissionCode") + .HasColumnType("varchar(255)"); + + b.Property("SourceRoleIds") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("AdminUserId", "PermissionCode"); + + b.ToTable("adminUserPermissions", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUserRole", b => + { + b.Property("AdminUserId") + .HasColumnType("bigint"); + + b.Property("RoleId") + .HasColumnType("bigint"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("AdminUserId", "RoleId"); + + b.ToTable("adminUserRoles", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.UserDept", b => + { + b.Property("AdminUserId") + .HasColumnType("bigint"); + + b.Property("DeptId") + .HasColumnType("bigint"); + + b.Property("DeptName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("AdminUserId", "DeptId"); + + b.ToTable("userDepts", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DeletedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("departments", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.DepartmentUser", b => + { + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("DeptId") + .HasColumnType("bigint"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("UserId", "DeptId"); + + b.HasIndex("DeptId"); + + b.ToTable("departmentUser", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("bigint"); + + b.Property("PermissionCode") + .HasColumnType("varchar(255)"); + + b.HasKey("RoleId", "PermissionCode"); + + b.ToTable("rolePermissions", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.OrderAggregate.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Paid") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("order", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUserPermission", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", null) + .WithMany("Permissions") + .HasForeignKey("AdminUserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUserRole", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", null) + .WithMany("Roles") + .HasForeignKey("AdminUserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.UserDept", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", null) + .WithMany("UserDepts") + .HasForeignKey("AdminUserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.DepartmentUser", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", null) + .WithMany("Users") + .HasForeignKey("DeptId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.RolePermission", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.Role", null) + .WithMany("Permissions") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", b => + { + b.Navigation("Permissions"); + + b.Navigation("Roles"); + + b.Navigation("UserDepts"); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.Role", b => + { + b.Navigation("Permissions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/NetCorePal.D3Shop.Web/Migrations/20241223113656_AddTableDepartmentUser.cs b/src/NetCorePal.D3Shop.Web/Migrations/20241223113656_AddTableDepartmentUser.cs new file mode 100644 index 0000000..08ec41f --- /dev/null +++ b/src/NetCorePal.D3Shop.Web/Migrations/20241223113656_AddTableDepartmentUser.cs @@ -0,0 +1,184 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace NetCorePal.D3Shop.Web.Migrations +{ + /// + public partial class AddTableDepartmentUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_adminUserPermissions_adminUsers_AdminUserId", + table: "adminUserPermissions"); + + migrationBuilder.DropForeignKey( + name: "FK_adminUserRoles_adminUsers_AdminUserId", + table: "adminUserRoles"); + + migrationBuilder.DropForeignKey( + name: "FK_adminUserRoles_roles_RoleId", + table: "adminUserRoles"); + + migrationBuilder.DropIndex( + name: "IX_adminUserRoles_RoleId", + table: "adminUserRoles"); + + migrationBuilder.DropColumn( + name: "PermissionRemark", + table: "rolePermissions"); + + migrationBuilder.DropColumn( + name: "PermissionRemark", + table: "adminUserPermissions"); + + migrationBuilder.CreateTable( + name: "departments", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Description = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + ParentId = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + IsDeleted = table.Column(type: "tinyint(1)", nullable: false), + DeletedAt = table.Column(type: "datetime(6)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_departments", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "userDepts", + columns: table => new + { + AdminUserId = table.Column(type: "bigint", nullable: false), + DeptId = table.Column(type: "bigint", nullable: false), + DeptName = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_userDepts", x => new { x.AdminUserId, x.DeptId }); + table.ForeignKey( + name: "FK_userDepts_adminUsers_AdminUserId", + column: x => x.AdminUserId, + principalTable: "adminUsers", + principalColumn: "Id"); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "departmentUser", + columns: table => new + { + DeptId = table.Column(type: "bigint", nullable: false), + UserId = table.Column(type: "bigint", nullable: false), + UserName = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_departmentUser", x => new { x.UserId, x.DeptId }); + table.ForeignKey( + name: "FK_departmentUser_departments_DeptId", + column: x => x.DeptId, + principalTable: "departments", + principalColumn: "Id"); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_departmentUser_DeptId", + table: "departmentUser", + column: "DeptId"); + + migrationBuilder.AddForeignKey( + name: "FK_adminUserPermissions_adminUsers_AdminUserId", + table: "adminUserPermissions", + column: "AdminUserId", + principalTable: "adminUsers", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_adminUserRoles_adminUsers_AdminUserId", + table: "adminUserRoles", + column: "AdminUserId", + principalTable: "adminUsers", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_adminUserPermissions_adminUsers_AdminUserId", + table: "adminUserPermissions"); + + migrationBuilder.DropForeignKey( + name: "FK_adminUserRoles_adminUsers_AdminUserId", + table: "adminUserRoles"); + + migrationBuilder.DropTable( + name: "departmentUser"); + + migrationBuilder.DropTable( + name: "userDepts"); + + migrationBuilder.DropTable( + name: "departments"); + + migrationBuilder.AddColumn( + name: "PermissionRemark", + table: "rolePermissions", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "PermissionRemark", + table: "adminUserPermissions", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_adminUserRoles_RoleId", + table: "adminUserRoles", + column: "RoleId"); + + migrationBuilder.AddForeignKey( + name: "FK_adminUserPermissions_adminUsers_AdminUserId", + table: "adminUserPermissions", + column: "AdminUserId", + principalTable: "adminUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_adminUserRoles_adminUsers_AdminUserId", + table: "adminUserRoles", + column: "AdminUserId", + principalTable: "adminUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_adminUserRoles_roles_RoleId", + table: "adminUserRoles", + column: "RoleId", + principalTable: "roles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/src/NetCorePal.D3Shop.Web/Migrations/ApplicationDbContextModelSnapshot.cs b/src/NetCorePal.D3Shop.Web/Migrations/ApplicationDbContextModelSnapshot.cs index 9d73f8a..fc0f9b2 100644 --- a/src/NetCorePal.D3Shop.Web/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/NetCorePal.D3Shop.Web/Migrations/ApplicationDbContextModelSnapshot.cs @@ -80,10 +80,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("PermissionCode") .HasColumnType("varchar(255)"); - b.Property("PermissionRemark") - .IsRequired() - .HasColumnType("longtext"); - b.Property("SourceRoleIds") .IsRequired() .HasColumnType("longtext"); @@ -107,11 +103,78 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("AdminUserId", "RoleId"); - b.HasIndex("RoleId"); - b.ToTable("adminUserRoles", (string)null); }); + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.UserDept", b => + { + b.Property("AdminUserId") + .HasColumnType("bigint"); + + b.Property("DeptId") + .HasColumnType("bigint"); + + b.Property("DeptName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("AdminUserId", "DeptId"); + + b.ToTable("userDepts", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DeletedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("departments", (string)null); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.DepartmentUser", b => + { + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("DeptId") + .HasColumnType("bigint"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("UserId", "DeptId"); + + b.HasIndex("DeptId"); + + b.ToTable("departmentUser", (string)null); + }); + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.Role", b => { b.Property("Id") @@ -144,10 +207,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("PermissionCode") .HasColumnType("varchar(255)"); - b.Property("PermissionRemark") - .IsRequired() - .HasColumnType("longtext"); - b.HasKey("RoleId", "PermissionCode"); b.ToTable("rolePermissions", (string)null); @@ -182,7 +241,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", null) .WithMany("Permissions") .HasForeignKey("AdminUserId") - .OnDelete(DeleteBehavior.Cascade) + .OnDelete(DeleteBehavior.ClientCascade) .IsRequired(); }); @@ -191,13 +250,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", null) .WithMany("Roles") .HasForeignKey("AdminUserId") - .OnDelete(DeleteBehavior.Cascade) + .OnDelete(DeleteBehavior.ClientCascade) .IsRequired(); + }); - b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.Role", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.UserDept", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate.AdminUser", null) + .WithMany("UserDepts") + .HasForeignKey("AdminUserId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.DepartmentUser", b => + { + b.HasOne("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", null) + .WithMany("Users") + .HasForeignKey("DeptId") + .OnDelete(DeleteBehavior.ClientCascade) .IsRequired(); }); @@ -215,6 +286,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Permissions"); b.Navigation("Roles"); + + b.Navigation("UserDepts"); + }); + + modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate.Department", b => + { + b.Navigation("Users"); }); modelBuilder.Entity("NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate.Role", b => diff --git a/src/NetCorePal.D3Shop.Web/NetCorePal.D3Shop.Web.csproj b/src/NetCorePal.D3Shop.Web/NetCorePal.D3Shop.Web.csproj index 60b30c3..328a393 100644 --- a/src/NetCorePal.D3Shop.Web/NetCorePal.D3Shop.Web.csproj +++ b/src/NetCorePal.D3Shop.Web/NetCorePal.D3Shop.Web.csproj @@ -25,6 +25,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/NetCorePal.D3Shop.Domain.Tests/Identity/DepartmentTests.cs b/test/NetCorePal.D3Shop.Domain.Tests/Identity/DepartmentTests.cs new file mode 100644 index 0000000..9e93ab4 --- /dev/null +++ b/test/NetCorePal.D3Shop.Domain.Tests/Identity/DepartmentTests.cs @@ -0,0 +1,109 @@ +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.RoleAggregate; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NetCorePal.D3Shop.Domain.Tests.Identity +{ + + + public class DepartmentTests + { + private readonly Department _department; + + // 构造函数:初始化 Department 实例,使用构造函数传入初始数据 + public DepartmentTests() + { + // 创建初始的用户列表 + var initialUsers = new List + { + new DepartmentUser("User1", new AdminUserId(1)), // 创建 User1 + new DepartmentUser("User2", new AdminUserId(2)) // 创建 User2 + }; + + // 使用构造函数创建 Department 实例,传入部门名称、描述、父部门 ID 和用户列表 + _department = new Department("InitialName", "InitialDescription", new DeptId(1), initialUsers); + } + + // 测试:更新部门名称和描述 + [Fact] + public void UpdateDepartInfo_ShouldUpdateNameAndDescription() + { + // Arrange:准备更新后的名称和描述 + var newName = "UpdatedName"; + var newDescription = "UpdatedDescription"; + + // Act:调用 UpdateDepartInfo 方法更新部门名称和描述 + _department.UpdateDepartInfo(newName, newDescription, _department.Users); + + // Assert:验证名称和描述是否正确更新 + Assert.Equal(newName, _department.Name); + Assert.Equal(newDescription, _department.Description); + } + + // 测试:添加新用户 + [Fact] + public void UpdateDepartInfo_ShouldAddNewUsers() + { + // Arrange:准备新的用户列表,其中包括一个已有的用户和一个新用户 + var newUsers = new List + { + new DepartmentUser("User1", new AdminUserId(1)), // 已有用户 User1 + new DepartmentUser("User3", new AdminUserId(3)) // 新用户 User3 + }; + + // Act:调用 UpdateDepartInfo 方法更新用户列表 + _department.UpdateDepartInfo(_department.Name, _department.Description, newUsers); + + // Assert:验证新用户是否已成功添加 + Assert.Contains(_department.Users, u => u.UserId.Equals(new AdminUserId(3)) && u.UserName == "User3"); + } + + // 测试:移除已不存在的用户 + [Fact] + public void UpdateDepartInfo_ShouldRemoveAbsentUsers() + { + // Arrange:准备新的用户列表,其中仅包含 User2,User1 将被移除 + var newUsers = new List + { + new DepartmentUser("User2", new AdminUserId(2)) // 仅包含 User2 + // User1 不在新列表中,应该被移除 + }; + + // Act:调用 UpdateDepartInfo 方法更新用户列表 + _department.UpdateDepartInfo(_department.Name, _department.Description, newUsers); + + // Assert:验证 User1 是否被移除 + Assert.DoesNotContain(_department.Users, u => u.UserId.Equals(new AdminUserId(1)) && u.UserName == "User1"); + } + + // 测试:保留没有改变的用户 + [Fact] + public void UpdateDepartInfo_ShouldRetainUnchangedUsers() + { + // Arrange:准备新的用户列表,User1 和 User2 都没有变化 + var newUsers = new List + { + new DepartmentUser("User1", new AdminUserId(1)), // 保持不变的用户 User1 + new DepartmentUser("User2", new AdminUserId(2)) // 保持不变的用户 User2 + }; + + // Act:调用 UpdateDepartInfo 方法更新用户列表 + _department.UpdateDepartInfo(_department.Name, _department.Description, newUsers); + + // Assert:验证用户列表中依然只有 2 个用户,没有丢失或重复 + Assert.Equal(2, _department.Users.Count); // 确保用户数量没有变化 + } + } + + + + +} + + + diff --git a/test/NetCorePal.D3Shop.Web.Tests/Identity/DepartmentTests.cs b/test/NetCorePal.D3Shop.Web.Tests/Identity/DepartmentTests.cs new file mode 100644 index 0000000..4cac8f8 --- /dev/null +++ b/test/NetCorePal.D3Shop.Web.Tests/Identity/DepartmentTests.cs @@ -0,0 +1,153 @@ +using AntDesign; +using Microsoft.AspNetCore.Mvc.Formatters; +using NetCorePal.D3Shop.Admin.Shared.Requests; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.AdminUserAggregate; +using Newtonsoft.Json; +using System.Net.Http.Headers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NetCorePal.D3Shop.Domain.AggregatesModel.Identity.DepartmentAggregate; +using NetCorePal.D3Shop.Admin.Shared.Dtos.Identity; +using NetCorePal.Extensions.Dto; +using NetCorePal.D3Shop.Admin.Shared.Responses; +using System.Xml.Linq; + +namespace NetCorePal.D3Shop.Web.Tests.Identity +{ + [Collection("web")] + public class DepartmentTests + { + private readonly HttpClient _client; + + public DepartmentTests(MyWebApplicationFactory factory) + { + _client = factory.WithWebHostBuilder(builder => { builder.ConfigureServices(_ => { }); }) + .CreateClient(); + const string json = $$""" + { + "name": "{{AppDefaultCredentials.Name}}", + "password": "{{AppDefaultCredentials.Password}}" + } + """; + var content = new StringContent(json); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + _client.PostAsync("api/AdminUserAccount/login", content).GetAwaiter().GetResult(); + } + + #region CreateDepartment Tests + + [Fact] + public async Task CreateDepartment_ShouldReturnDeptId() + { + // Arrange + var request = new CreateDepartmentRequest + { + Name = "TestDepartment", + Description = "test decription", + ParentId = new DeptId(0), + Users = new List + { + new CreateDepartmentUserInfoDto(new AdminUserId(1),"User1"), // 创建 User1 + new CreateDepartmentUserInfoDto(new AdminUserId(2), "User2" ) // 创建 User2 + }, + + }; + + // Act + var response = await _client.PostAsNewtonsoftJsonAsync("/api/Department/CreateDepartment", request); + + // Assert - 创建成功 + Assert.True(response.IsSuccessStatusCode); + var responseData = await response.Content.ReadFromNewtonsoftJsonAsync>(); + Assert.NotNull(responseData); + Assert.NotEqual(0, responseData.Data.Id); // 验证返回的用户ID不是默认值 + } + + #endregion + + #region GetAllDepartments Tests + + [Fact] + public async Task GetAllDepartments_ShouldReturnPagedData() + { + const string testDeptName = "TestDepartment"; + // Arrange + var request = new DepartmentQueryRequest + { + PageIndex = 1, + PageSize = 10, + Name = testDeptName, + }; + + var queryString = $"?PageIndex={request.PageIndex}&PageSize={request.PageSize}"; + var url = "/api/Department/GetAllDepartments" + queryString; + + // Act + var response = await _client.GetAsync(url); + + // Assert + response.EnsureSuccessStatusCode(); // 确保返回 200 OK + var conditionData = await response.Content + .ReadFromNewtonsoftJsonAsync>>(); + Assert.NotNull(conditionData); + Assert.All(conditionData.Data.Items, dept => Assert.Contains(testDeptName, dept.Name)); // 验证返回的用户符合条件 + } + + #endregion + + #region UpdateDepartmentInfo Tests + + [Fact] + public async Task UpdateDepartmentInfo_ShouldReturnSuccess() + { + // Arrange + var deptId = 1; // 假设部门 ID 为 1 + var request = new UpdateDepartmentInfoRequest + { + Name = "Updated Department", + Description = "Updated Description", + Users = new List + { + new CreateDepartmentUserInfoDto( new AdminUserId(1),"User3"), // 创建 User1 + new CreateDepartmentUserInfoDto(new AdminUserId(2), "User4" ) // 创建 User2 + } + }; + + // Act + var response = await _client.PutAsNewtonsoftJsonAsync($"/api/Department/UpdateDepartmentInfo/{deptId}", request); + + // Assert + response.EnsureSuccessStatusCode(); + var responseData = await response.Content.ReadAsStringAsync(); + Assert.Contains("success", responseData, StringComparison.OrdinalIgnoreCase); + } + + #endregion + + #region DeleteDepartment Tests + + [Fact] + public async Task DeleteDepartment_ShouldReturnSuccess() + { + // Arrange + var deptId = 1; // 假设部门 ID 为 1 + + // Act + var response = await _client.DeleteAsync($"/api/Department/DeleteDepartment/{deptId}"); + + // Assert + response.EnsureSuccessStatusCode(); + var responseData = await response.Content.ReadAsStringAsync(); + Assert.Contains("success", responseData, StringComparison.OrdinalIgnoreCase); + } + + #endregion + + } + + + +}