From ea2e5229c87b59c9a353762b2c20a7b7e407afa0 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Tue, 18 Aug 2026 15:43:33 +0530 Subject: [PATCH] Add user management domain and seed model --- Distribution/Data/AppDBContext.cs | 27 ++++ .../Data/Seed/RoleAndPermissionSeed.cs | 129 ++++++++++++++++++ Distribution/Models/Permission.cs | 8 ++ Distribution/Models/Role.cs | 8 ++ Distribution/Models/RolePermission.cs | 15 ++ Distribution/Models/Route.cs | 8 +- Distribution/Models/Store.cs | 8 +- Distribution/Models/User.cs | 21 +++ Distribution/Models/UserRegionScope.cs | 15 ++ Distribution/Models/UserRouteScope.cs | 15 ++ Distribution/Models/UserStoreScope.cs | 15 ++ 11 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 Distribution/Data/Seed/RoleAndPermissionSeed.cs create mode 100644 Distribution/Models/Permission.cs create mode 100644 Distribution/Models/Role.cs create mode 100644 Distribution/Models/RolePermission.cs create mode 100644 Distribution/Models/User.cs create mode 100644 Distribution/Models/UserRegionScope.cs create mode 100644 Distribution/Models/UserRouteScope.cs create mode 100644 Distribution/Models/UserStoreScope.cs diff --git a/Distribution/Data/AppDBContext.cs b/Distribution/Data/AppDBContext.cs index 7fa7204..27c2698 100644 --- a/Distribution/Data/AppDBContext.cs +++ b/Distribution/Data/AppDBContext.cs @@ -15,10 +15,37 @@ public class AppDBContext(DbContextOptions options) : DbContext(op public DbSet OrderItems { get; set; } public DbSet DeliveryRuns { get; set; } public DbSet Items { get; set; } + public DbSet Users { get; set; } + public DbSet Roles { get; set; } + public DbSet Permissions { get; set; } + public DbSet RolePermissions { get; set; } + public DbSet UserRegionScopes { get; set; } + public DbSet UserRouteScopes { get; set; } + public DbSet UserStoreScopes { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .HasKey(oi => new { oi.FK_OrderId, oi.FK_ItemId }); + + modelBuilder.Entity() + .HasKey(rp => new { rp.FK_RoleId, rp.FK_PermissionId }); + + modelBuilder.Entity() + .HasKey(s => new { s.FK_UserId, s.FK_RegionId }); + + modelBuilder.Entity() + .HasKey(s => new { s.FK_UserId, s.FK_RouteId }); + + modelBuilder.Entity() + .HasKey(s => new { s.FK_UserId, s.FK_StoreId }); + + modelBuilder.Entity() + .HasOne(u => u.ReportsTo) + .WithMany() + .HasForeignKey(u => u.FK_ReportsToUserId) + .OnDelete(DeleteBehavior.Restrict); + + Seed.RoleAndPermissionSeed.Apply(modelBuilder); } } diff --git a/Distribution/Data/Seed/RoleAndPermissionSeed.cs b/Distribution/Data/Seed/RoleAndPermissionSeed.cs new file mode 100644 index 0000000..bd9a654 --- /dev/null +++ b/Distribution/Data/Seed/RoleAndPermissionSeed.cs @@ -0,0 +1,129 @@ +using Distribution.Models; +using Microsoft.EntityFrameworkCore; + +namespace Distribution.Data.Seed; + +public static class RoleAndPermissionSeed +{ + private static readonly DateTime SeedDate = new(2026, 8, 18); + + private static readonly Guid SalesRepId = new("10000000-0000-0000-0000-000000000001"); + private static readonly Guid SupervisorId = new("10000000-0000-0000-0000-000000000002"); + private static readonly Guid AreaManagerId = new("10000000-0000-0000-0000-000000000003"); + private static readonly Guid AreaSupervisorId = new("10000000-0000-0000-0000-000000000004"); + private static readonly Guid RegionalManagerId = new("10000000-0000-0000-0000-000000000005"); + private static readonly Guid RegionalAgencyId = new("10000000-0000-0000-0000-000000000006"); + + private static readonly (string Code, string Description) [] PermissionDefs = + [ + ("regions.view", "View regions"), + ("regions.create", "Create regions"), + ("regions.update", "Update regions"), + ("regions.delete", "Delete regions"), + + ("routes.view", "View routes"), + ("routes.create", "Create routes"), + ("routes.update", "Update routes"), + ("routes.delete", "Delete routes"), + + ("vehicles.view", "View vehicles"), + ("vehicles.create", "Create vehicles"), + ("vehicles.update", "Update vehicles"), + ("vehicles.delete", "Delete vehicles"), + + ("stores.view", "View stores"), + ("stores.create", "Create stores"), + ("stores.update", "Update stores"), + ("stores.delete", "Delete stores"), + + ("warehouses.view", "View warehouses"), + ("warehouses.create", "Create warehouses"), + ("warehouses.update", "Update warehouses"), + ("warehouses.delete", "Delete warehouses"), + + ("orders.view", "View orders"), + ("orders.create", "Create orders"), + ("orders.update", "Update orders"), + ("orders.delete", "Delete orders"), + + ("users.view", "View users"), + ("users.create", "Create users"), + ("users.update", "Update users"), + ("users.deactivate", "Deactivate users"), + ("users.assign_role", "Assign a role to a user"), + ("users.assign_scope", "Assign region/route/store scope to a user"), + ]; + + private static readonly Dictionary PermissionIds = PermissionDefs + .Select((p, i) => (p.Code, Id: DeterministicPermissionId(i))) + .ToDictionary(x => x.Code, x => x.Id); + + private static Guid DeterministicPermissionId(int index) => + new($"20000000-0000-0000-0000-{index + 1:D12}"); + + public static void Apply(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasData( + new Role { Id = SalesRepId, Name = "SalesRep", Level = 10, CreatedAt = SeedDate, IsActive = true }, + new Role { Id = SupervisorId, Name = "Supervisor", Level = 20, CreatedAt = SeedDate, IsActive = true }, + new Role { Id = AreaManagerId, Name = "AreaManager", Level = 30, CreatedAt = SeedDate, IsActive = true }, + new Role { Id = AreaSupervisorId, Name = "AreaSupervisor", Level = 40, CreatedAt = SeedDate, IsActive = true }, + new Role { Id = RegionalManagerId, Name = "RegionalManager", Level = 50, CreatedAt = SeedDate, IsActive = true }, + new Role { Id = RegionalAgencyId, Name = "RegionalAgency", Level = 60, CreatedAt = SeedDate, IsActive = true } + ); + + modelBuilder.Entity().HasData( + PermissionDefs.Select(p => new Permission + { + Id = PermissionIds[p.Code], + Code = p.Code, + Description = p.Description, + CreatedAt = SeedDate, + IsActive = true + }) + ); + + var salesRepPerms = new[] { "orders.view", "orders.create", "orders.update", "stores.view", "routes.view" }; + var supervisorPerms = salesRepPerms.Concat(["orders.delete", "stores.create", "stores.update"]).ToArray(); + var areaManagerPerms = supervisorPerms.Concat(["vehicles.view", "warehouses.view"]).ToArray(); + var areaSupervisorPerms = areaManagerPerms.Concat(["routes.create", "routes.update"]).ToArray(); + var regionalManagerPerms = new[] + { + "orders.view", "orders.create", "orders.update", "orders.delete", + "stores.view", "stores.create", "stores.update", "stores.delete", + "routes.view", "routes.create", "routes.update", "routes.delete", + "vehicles.view", "vehicles.create", "vehicles.update", "vehicles.delete", + "warehouses.view", "warehouses.create", "warehouses.update", "warehouses.delete", + "regions.view", "users.view" + }; + var regionalAgencyPerms = PermissionDefs.Select(p => p.Code).ToArray(); + + var rolePermissionMap = new (Guid RoleId, string[] Codes)[] + { + (SalesRepId, salesRepPerms), + (SupervisorId, supervisorPerms), + (AreaManagerId, areaManagerPerms), + (AreaSupervisorId, areaSupervisorPerms), + (RegionalManagerId, regionalManagerPerms), + (RegionalAgencyId, regionalAgencyPerms), + }; + + modelBuilder.Entity().HasData( + rolePermissionMap.SelectMany(rp => rp.Codes.Select(code => new RolePermission + { + Id = DeterministicRolePermissionId(rp.RoleId, PermissionIds[code]), + FK_RoleId = rp.RoleId, + FK_PermissionId = PermissionIds[code], + CreatedAt = SeedDate, + IsActive = true + })) + ); + } + + private static Guid DeterministicRolePermissionId(Guid roleId, Guid permissionId) + { + using var md5 = System.Security.Cryptography.MD5.Create(); + var bytes = md5.ComputeHash(roleId.ToByteArray().Concat(permissionId.ToByteArray()).ToArray()); + return new Guid(bytes); + } +} diff --git a/Distribution/Models/Permission.cs b/Distribution/Models/Permission.cs new file mode 100644 index 0000000..3a0ebb6 --- /dev/null +++ b/Distribution/Models/Permission.cs @@ -0,0 +1,8 @@ +namespace Distribution.Models +{ + public class Permission : Template + { + public string Code { get; set; } + public string Description { get; set; } + } +} diff --git a/Distribution/Models/Role.cs b/Distribution/Models/Role.cs new file mode 100644 index 0000000..9c2c4aa --- /dev/null +++ b/Distribution/Models/Role.cs @@ -0,0 +1,8 @@ +namespace Distribution.Models +{ + public class Role : Template + { + public string Name { get; set; } + public int Level { get; set; } + } +} diff --git a/Distribution/Models/RolePermission.cs b/Distribution/Models/RolePermission.cs new file mode 100644 index 0000000..8242529 --- /dev/null +++ b/Distribution/Models/RolePermission.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models +{ + public class RolePermission : Template + { + public Guid FK_RoleId { get; set; } + [ForeignKey("FK_RoleId")] + public Role Role { get; set; } + + public Guid FK_PermissionId { get; set; } + [ForeignKey("FK_PermissionId")] + public Permission Permission { get; set; } + } +} diff --git a/Distribution/Models/Route.cs b/Distribution/Models/Route.cs index c3edbef..2e7dfe5 100644 --- a/Distribution/Models/Route.cs +++ b/Distribution/Models/Route.cs @@ -1,4 +1,6 @@ -namespace Distribution.Models +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models { public class Route : Template { @@ -6,5 +8,9 @@ public String StartPoint { get; set; } public String EndPoint { get; set; } public decimal Length { get; set; } = 0; + + public Guid FK_RegionId { get; set; } + [ForeignKey("FK_RegionId")] + public Region Region { get; set; } } } diff --git a/Distribution/Models/Store.cs b/Distribution/Models/Store.cs index 9c6cf6e..849564f 100644 --- a/Distribution/Models/Store.cs +++ b/Distribution/Models/Store.cs @@ -1,4 +1,6 @@ -namespace Distribution.Models +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models { public class Store : Template { @@ -9,5 +11,9 @@ public decimal Longitude { get; set; } = 0; public decimal OneTimeAmount { get; set; } = 0; public decimal CreditAmount { get; set; } = 0; + + public Guid FK_RouteId { get; set; } + [ForeignKey("FK_RouteId")] + public Route Route { get; set; } } } diff --git a/Distribution/Models/User.cs b/Distribution/Models/User.cs new file mode 100644 index 0000000..e4a30e6 --- /dev/null +++ b/Distribution/Models/User.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models +{ + public class User : Template + { + public string Username { get; set; } + public string PasswordHash { get; set; } + public string FullName { get; set; } + + public Guid FK_RoleId { get; set; } + [ForeignKey("FK_RoleId")] + public Role Role { get; set; } + + public Guid? FK_ReportsToUserId { get; set; } + [ForeignKey("FK_ReportsToUserId")] + public User ReportsTo { get; set; } + + public DateTime? UpdatedAt { get; set; } + } +} diff --git a/Distribution/Models/UserRegionScope.cs b/Distribution/Models/UserRegionScope.cs new file mode 100644 index 0000000..96705b2 --- /dev/null +++ b/Distribution/Models/UserRegionScope.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models +{ + public class UserRegionScope : Template + { + public Guid FK_UserId { get; set; } + [ForeignKey("FK_UserId")] + public User User { get; set; } + + public Guid FK_RegionId { get; set; } + [ForeignKey("FK_RegionId")] + public Region Region { get; set; } + } +} diff --git a/Distribution/Models/UserRouteScope.cs b/Distribution/Models/UserRouteScope.cs new file mode 100644 index 0000000..4a605a8 --- /dev/null +++ b/Distribution/Models/UserRouteScope.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models +{ + public class UserRouteScope : Template + { + public Guid FK_UserId { get; set; } + [ForeignKey("FK_UserId")] + public User User { get; set; } + + public Guid FK_RouteId { get; set; } + [ForeignKey("FK_RouteId")] + public Route Route { get; set; } + } +} diff --git a/Distribution/Models/UserStoreScope.cs b/Distribution/Models/UserStoreScope.cs new file mode 100644 index 0000000..1415e34 --- /dev/null +++ b/Distribution/Models/UserStoreScope.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Distribution.Models +{ + public class UserStoreScope : Template + { + public Guid FK_UserId { get; set; } + [ForeignKey("FK_UserId")] + public User User { get; set; } + + public Guid FK_StoreId { get; set; } + [ForeignKey("FK_StoreId")] + public Store Store { get; set; } + } +}