diff --git a/.gitignore b/.gitignore index e10897e..1c378cd 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ CodeCoverage/ *.VisualState.xml TestResult.xml nunit-*.xml +notifications.md - +.vs appsettings.Development.json \ No newline at end of file diff --git a/Fin.Api/Fin.Api.csproj b/Fin.Api/Fin.Api.csproj index 4e5665c..4f9c60c 100644 --- a/Fin.Api/Fin.Api.csproj +++ b/Fin.Api/Fin.Api.csproj @@ -7,7 +7,7 @@ - + diff --git a/Fin.Api/Fin.Api.sln b/Fin.Api/Fin.Api.sln new file mode 100644 index 0000000..912a3b0 --- /dev/null +++ b/Fin.Api/Fin.Api.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fin.Api", "Fin.Api.csproj", "{330E38C3-8925-05A6-F8DE-2312E9F69666}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {330E38C3-8925-05A6-F8DE-2312E9F69666}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {330E38C3-8925-05A6-F8DE-2312E9F69666}.Debug|Any CPU.Build.0 = Debug|Any CPU + {330E38C3-8925-05A6-F8DE-2312E9F69666}.Release|Any CPU.ActiveCfg = Release|Any CPU + {330E38C3-8925-05A6-F8DE-2312E9F69666}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {85E7AB78-8C20-4A98-9130-CD73E0B54F27} + EndGlobalSection +EndGlobal diff --git a/Fin.Api/FinancialInstitutions/FinancialInstitutionController.cs b/Fin.Api/FinancialInstitutions/FinancialInstitutionController.cs new file mode 100644 index 0000000..1b4c205 --- /dev/null +++ b/Fin.Api/FinancialInstitutions/FinancialInstitutionController.cs @@ -0,0 +1,53 @@ +using Fin.Application.FinancialInstitutions; +using Fin.Domain.FinancialInstitutions.Dtos; +using Fin.Domain.Global.Classes; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Fin.Api.FinancialInstitutions; + +[Route("financial-institutions")] +[Authorize] +public class FinancialInstitutionController(IFinancialInstitutionService service): ControllerBase +{ + [HttpGet] + public async Task> GetList([FromQuery] FinancialInstitutionGetListInput input) + { + return await service.GetList(input); + } + + [HttpGet("{id:guid}")] + public async Task> Get([FromRoute] Guid id) + { + var institution = await service.Get(id); + return institution != null ? Ok(institution) : NotFound(); + } + + [HttpPost] + public async Task> Create([FromBody] FinancialInstitutionInput input) + { + var institution = await service.Create(input, autoSave: true); + return institution != null ? Created($"financial-institutions/{institution.Id}", institution) : UnprocessableEntity(); + } + + [HttpPut("{id:guid}")] + public async Task Update([FromRoute] Guid id, [FromBody] FinancialInstitutionInput input) + { + var updated = await service.Update(id, input, autoSave: true); + return updated ? Ok() : NotFound(); + } + + [HttpDelete("{id:guid}")] + public async Task Delete([FromRoute] Guid id) + { + var deleted = await service.Delete(id, autoSave: true); + return deleted ? Ok() : NotFound(); + } + + [HttpPatch("{id:guid}/toggle-inactive")] + public async Task ToggleInactive([FromRoute] Guid id) + { + var toggled = await service.ToggleInactive(id, autoSave: true); + return toggled ? Ok() : NotFound(); + } +} diff --git a/Fin.Api/appsettings.json b/Fin.Api/appsettings.json index 53bd729..10a640f 100644 --- a/Fin.Api/appsettings.json +++ b/Fin.Api/appsettings.json @@ -40,4 +40,4 @@ "Url": "http://localhost:4200" } } -} +} \ No newline at end of file diff --git a/Fin.Application/Fin.Application.sln b/Fin.Application/Fin.Application.sln new file mode 100644 index 0000000..d691ce0 --- /dev/null +++ b/Fin.Application/Fin.Application.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fin.Application", "Fin.Application.csproj", "{930F4674-00C7-367F-2ABF-37898BCDDD08}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {930F4674-00C7-367F-2ABF-37898BCDDD08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {930F4674-00C7-367F-2ABF-37898BCDDD08}.Debug|Any CPU.Build.0 = Debug|Any CPU + {930F4674-00C7-367F-2ABF-37898BCDDD08}.Release|Any CPU.ActiveCfg = Release|Any CPU + {930F4674-00C7-367F-2ABF-37898BCDDD08}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5C017504-FB39-43D3-9270-899D293A0EB6} + EndGlobalSection +EndGlobal diff --git a/Fin.Application/FinancialInstitutions/Dtos/FinancialInstitutionGetListInput.cs b/Fin.Application/FinancialInstitutions/Dtos/FinancialInstitutionGetListInput.cs new file mode 100644 index 0000000..7bfb950 --- /dev/null +++ b/Fin.Application/FinancialInstitutions/Dtos/FinancialInstitutionGetListInput.cs @@ -0,0 +1,9 @@ +using Fin.Domain.FinancialInstitutions.Enums; +using Fin.Domain.Global.Classes; + +public class FinancialInstitutionGetListInput : PagedFilteredAndSortedInput +{ + public bool? Inactive { get; set; } + public FinancialInstitutionType? Type { get; set; } + +} \ No newline at end of file diff --git a/Fin.Application/FinancialInstitutions/FinancialInstitutionService.cs b/Fin.Application/FinancialInstitutions/FinancialInstitutionService.cs new file mode 100644 index 0000000..d00dfb4 --- /dev/null +++ b/Fin.Application/FinancialInstitutions/FinancialInstitutionService.cs @@ -0,0 +1,120 @@ +using Fin.Domain.FinancialInstitutions.Dtos; +using Fin.Domain.FinancialInstitutions.Entities; +using Fin.Domain.Global.Classes; +using Fin.Infrastructure.AmbientDatas; +using Fin.Infrastructure.AutoServices.Interfaces; +using Fin.Infrastructure.Database.Extensions; +using Fin.Infrastructure.Database.Repositories; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; + +namespace Fin.Application.FinancialInstitutions; + +public interface IFinancialInstitutionService +{ + Task Get(Guid id); + Task> GetList(FinancialInstitutionGetListInput input); + Task Create(FinancialInstitutionInput input, bool autoSave = false); + Task Update(Guid id, FinancialInstitutionInput input, bool autoSave = false); + Task Delete(Guid id, bool autoSave = false); + Task ToggleInactive(Guid id, bool autoSave = false); +} + +public class FinancialInstitutionService( + IRepository repository + ) : IFinancialInstitutionService, IAutoTransient +{ + public async Task Get(Guid id) + { + var entity = await repository.Query(false) + .FirstOrDefaultAsync(f => f.Id == id); + return entity != null ? new FinancialInstitutionOutput(entity) : null; + } + + public async Task> GetList(FinancialInstitutionGetListInput input) + { + return await repository.Query(false) + .WhereIf(input.Inactive.HasValue, f => f.Inactive == input.Inactive.Value) + .WhereIf(input.Type.HasValue, f => f.Type == input.Type.Value) + .OrderBy(f => f.Inactive) + .ThenBy(f => f.Name) + .ApplyFilterAndSorter(input) + .Select(f => new FinancialInstitutionOutput(f)) + .ToPagedResult(input); + } + + public async Task Create(FinancialInstitutionInput input, bool autoSave = false) + { + await ValidateInput(input); + + var institution = new FinancialInstitution(input); + await repository.AddAsync(institution, autoSave); + return new FinancialInstitutionOutput(institution); + } + + public async Task Update(Guid id, FinancialInstitutionInput input, bool autoSave = false) + { + await ValidateInput(input, id); + var institution = await repository.Query() + .FirstOrDefaultAsync(f => f.Id == id); + if (institution == null) return false; + + + + institution.Update(input); + await repository.UpdateAsync(institution, autoSave); + + return true; + } + + public async Task Delete(Guid id, bool autoSave = false) + { + var institution = await repository.Query() + .FirstOrDefaultAsync(f => f.Id == id); + if (institution == null) return false; + + await repository.DeleteAsync(institution, autoSave); + return true; + } + + public async Task ToggleInactive(Guid id, bool autoSave = false) + { + var institution = await repository.Query() + .FirstOrDefaultAsync(f => f.Id == id); + if (institution == null) return false; + + institution.ToggleInactive(); + + await repository.UpdateAsync(institution, autoSave); + return true; + } + + private async Task ValidateInput(FinancialInstitutionInput input, Guid? editingId = null) + { + + if (string.IsNullOrWhiteSpace(input.Name)) + throw new BadHttpRequestException("Name is required"); + if (input.Name.Count() > 100) + throw new BadHttpRequestException("Name must be at most 100 characters long"); + if (string.IsNullOrWhiteSpace(input.Icon)) + throw new BadHttpRequestException("Icon is required"); + if (input.Icon.Count() > 20) + throw new BadHttpRequestException("Icon must be at most 20 characters long"); + + if (string.IsNullOrWhiteSpace(input.Color)) + throw new BadHttpRequestException("Color is required"); + if (input.Color.Count() > 20) + throw new BadHttpRequestException("Color must be at most 20 characters long"); + + if(!string.IsNullOrWhiteSpace(input.Code) && input.Code.Count() > 15) + throw new BadHttpRequestException("Code must be at most 15 characters long"); + + var existingName = await repository.Query() + .Where(f => f.Name == input.Name) + .WhereIf(editingId.HasValue, f => f.Id != editingId.Value) + .AnyAsync(); + + if (existingName) + throw new BadHttpRequestException("A financial institution with this name already exists"); + } +} diff --git a/Fin.Domain/FinancialInstitutions/Dtos/FinancialInstitutionInput.cs b/Fin.Domain/FinancialInstitutions/Dtos/FinancialInstitutionInput.cs new file mode 100644 index 0000000..896b028 --- /dev/null +++ b/Fin.Domain/FinancialInstitutions/Dtos/FinancialInstitutionInput.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using Fin.Domain.FinancialInstitutions.Enums; + +namespace Fin.Domain.FinancialInstitutions.Dtos; + +public class FinancialInstitutionInput +{ + [Required] + [MaxLength(100)] + public string Name { get; set; } + + [MaxLength(15)] + public string Code { get; set; } + + [Required] + public FinancialInstitutionType Type { get; set; } + + [MaxLength(20)] + [Required] + public string Icon { get; set; } + + [MaxLength(20)] + [Required] + public string Color { get; set; } + } diff --git a/Fin.Domain/FinancialInstitutions/Dtos/FinancialInstitutionOutput.cs b/Fin.Domain/FinancialInstitutions/Dtos/FinancialInstitutionOutput.cs new file mode 100644 index 0000000..dc38915 --- /dev/null +++ b/Fin.Domain/FinancialInstitutions/Dtos/FinancialInstitutionOutput.cs @@ -0,0 +1,30 @@ +using Fin.Domain.FinancialInstitutions.Entities; +using Fin.Domain.FinancialInstitutions.Enums; + +namespace Fin.Domain.FinancialInstitutions.Dtos; + +public class FinancialInstitutionOutput +{ + public Guid Id { get; set; } + public string Name { get; set; } + public string Code { get; set; } + public FinancialInstitutionType Type { get; set; } + public string Icon { get; set; } + public string Color { get; set; } + public bool Inactive { get; set; } + + public FinancialInstitutionOutput() + { + } + + public FinancialInstitutionOutput(FinancialInstitution input) + { + Id = input.Id; + Name = input.Name; + Code = input.Code; + Type = input.Type; + Icon = input.Icon; + Color = input.Color; + Inactive = input.Inactive; + } +} diff --git a/Fin.Domain/FinancialInstitutions/Entities/FinancialInstitution.cs b/Fin.Domain/FinancialInstitutions/Entities/FinancialInstitution.cs new file mode 100644 index 0000000..aca544f --- /dev/null +++ b/Fin.Domain/FinancialInstitutions/Entities/FinancialInstitution.cs @@ -0,0 +1,44 @@ +using Fin.Domain.FinancialInstitutions.Dtos; +using Fin.Domain.FinancialInstitutions.Enums; +using Fin.Domain.Global.Interfaces; + +namespace Fin.Domain.FinancialInstitutions.Entities; + +public class FinancialInstitution : IAuditedEntity +{ + public string Name { get; set; } + public string Code { get; set; } + public FinancialInstitutionType Type { get; set; } + public string Icon { get; set; } + public string Color { get; set; } + public bool Inactive { get; set; } + + public Guid Id { get; set; } + public Guid CreatedBy { get; set; } + public Guid UpdatedBy { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + public FinancialInstitution() + { + } + + public FinancialInstitution(FinancialInstitutionInput input) + { + Name = input.Name; + Code = input.Code; + Type = input.Type; + Icon = input.Icon; + Color = input.Color; + } + + public void Update(FinancialInstitutionInput input) + { + Name = input.Name; + Code = input.Code; + Type = input.Type; + Icon = input.Icon; + Color = input.Color; + } + + public void ToggleInactive() => Inactive = !Inactive; +} diff --git a/Fin.Domain/FinancialInstitutions/Enums/FinancialInstitutionType.cs b/Fin.Domain/FinancialInstitutions/Enums/FinancialInstitutionType.cs new file mode 100644 index 0000000..a45eb91 --- /dev/null +++ b/Fin.Domain/FinancialInstitutions/Enums/FinancialInstitutionType.cs @@ -0,0 +1,9 @@ +namespace Fin.Domain.FinancialInstitutions.Enums +{ + public enum FinancialInstitutionType + { + Bank = 0, + DigitalBank = 1, + FoodCard = 2, + } +} diff --git a/Fin.Infrastructure/Database/Configurations/FinancialInstitutions/FinancialInstitutionConfiguration.cs b/Fin.Infrastructure/Database/Configurations/FinancialInstitutions/FinancialInstitutionConfiguration.cs new file mode 100644 index 0000000..454e112 --- /dev/null +++ b/Fin.Infrastructure/Database/Configurations/FinancialInstitutions/FinancialInstitutionConfiguration.cs @@ -0,0 +1,32 @@ +using Fin.Domain.FinancialInstitutions.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Fin.Infrastructure.Database.Configurations.FinancialInstitutions; + +public class FinancialInstitutionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + + builder.Property(x => x.Name) + .HasMaxLength(100) + .IsRequired() + .IsUnicode(); + + builder.Property(x => x.Code) + .HasMaxLength(15); + + builder.Property(x => x.Icon) + .HasMaxLength(20) + .IsRequired(); + + builder.Property(x => x.Color) + .HasMaxLength(20) + .IsRequired(); + + builder.Property(x => x.Type) + .IsRequired(); + } +} diff --git a/Fin.Infrastructure/Migrations/20251007000836_financialInstitution.Designer.cs b/Fin.Infrastructure/Migrations/20251007000836_financialInstitution.Designer.cs new file mode 100644 index 0000000..ede9e98 --- /dev/null +++ b/Fin.Infrastructure/Migrations/20251007000836_financialInstitution.Designer.cs @@ -0,0 +1,609 @@ +// +using System; +using Fin.Infrastructure.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Fin.Infrastructure.Migrations +{ + [DbContext(typeof(FinDbContext))] + [Migration("20251007000836_financialInstitution")] + partial class financialInstitution + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("public") + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Fin.Domain.FinancialInstitutions.Entities.FinancialInstitution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .HasMaxLength(15) + .HasColumnType("character varying(15)"); + + b.Property("Color") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Icon") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Inactive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(true) + .HasColumnType("character varying(100)"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("FinancialInstitution", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Menus.Entities.Menu", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Color") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("FrontRoute") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Icon") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("KeyWords") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OnlyForAdmin") + .HasColumnType("boolean"); + + b.Property("Position") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Menus", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Notifications.Entities.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Continuous") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("HtmlBody") + .HasColumnType("text"); + + b.Property("Link") + .HasColumnType("text"); + + b.Property("NormalizedTextBody") + .HasColumnType("text"); + + b.Property("NormalizedTitle") + .HasColumnType("text"); + + b.Property("Severity") + .HasColumnType("integer"); + + b.Property("StartToDelivery") + .HasColumnType("timestamp with time zone"); + + b.Property("StopToDelivery") + .HasColumnType("timestamp with time zone"); + + b.Property("TextBody") + .HasColumnType("text"); + + b.Property("Title") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.Property("Ways") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Notifications", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Notifications.Entities.NotificationUserDelivery", b => + { + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("BackgroundJobId") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Delivery") + .HasColumnType("boolean"); + + b.Property("Visualized") + .HasColumnType("boolean"); + + b.HasKey("NotificationId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserDeliveries", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Notifications.Entities.UserNotificationSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllowedWays") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("FirebaseTokens") + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationSettings", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Notifications.Entities.UserRememberUseSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("NotifyOn") + .HasColumnType("interval"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Ways") + .HasColumnType("text"); + + b.Property("WeekDays") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserRememberUseSettings", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Tenants.Entities.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Locale") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("Timezone") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Tenants", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Tenants.Entities.TenantUser", b => + { + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("TenantId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("TenantUsers", "public"); + }); + + modelBuilder.Entity("Fin.Domain.TitleCategories.Entities.TitleCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Color") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Icon") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Inactivated") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("TitleCategories", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Users.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BirthDate") + .HasColumnType("date"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gender") + .HasColumnType("integer"); + + b.Property("ImagePublicUrl") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IsActivity") + .HasColumnType("boolean"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("LastName") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Users", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Users.Entities.UserCredential", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EncryptedEmail") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("EncryptedPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("FailLoginAttempts") + .HasColumnType("integer"); + + b.Property("GoogleId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ResetToken") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EncryptedEmail") + .IsUnique(); + + b.HasIndex("GoogleId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Credentials", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Users.Entities.UserDeleteRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Aborted") + .HasColumnType("boolean"); + + b.Property("AbortedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("DeleteEffectivatedAt") + .HasColumnType("date"); + + b.Property("DeleteRequestedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.Property("UserAbortedId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserAbortedId"); + + b.HasIndex("UserId"); + + b.ToTable("UserDeleteRequests", "public"); + }); + + modelBuilder.Entity("Fin.Domain.Notifications.Entities.NotificationUserDelivery", b => + { + b.HasOne("Fin.Domain.Notifications.Entities.Notification", "Notification") + .WithMany("UserDeliveries") + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Fin.Domain.Users.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Fin.Domain.Notifications.Entities.UserNotificationSettings", b => + { + b.HasOne("Fin.Domain.Users.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Fin.Domain.Notifications.Entities.UserRememberUseSetting", b => + { + b.HasOne("Fin.Domain.Users.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Fin.Domain.Tenants.Entities.TenantUser", b => + { + b.HasOne("Fin.Domain.Tenants.Entities.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Fin.Domain.Users.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Fin.Domain.Users.Entities.UserCredential", b => + { + b.HasOne("Fin.Domain.Users.Entities.User", "User") + .WithOne("Credential") + .HasForeignKey("Fin.Domain.Users.Entities.UserCredential", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Fin.Domain.Users.Entities.UserDeleteRequest", b => + { + b.HasOne("Fin.Domain.Users.Entities.User", "UserAborted") + .WithMany() + .HasForeignKey("UserAbortedId"); + + b.HasOne("Fin.Domain.Users.Entities.User", "User") + .WithMany("DeleteRequests") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("UserAborted"); + }); + + modelBuilder.Entity("Fin.Domain.Notifications.Entities.Notification", b => + { + b.Navigation("UserDeliveries"); + }); + + modelBuilder.Entity("Fin.Domain.Users.Entities.User", b => + { + b.Navigation("Credential"); + + b.Navigation("DeleteRequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Fin.Infrastructure/Migrations/20251007000836_financialInstitution.cs b/Fin.Infrastructure/Migrations/20251007000836_financialInstitution.cs new file mode 100644 index 0000000..a17e53e --- /dev/null +++ b/Fin.Infrastructure/Migrations/20251007000836_financialInstitution.cs @@ -0,0 +1,45 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Fin.Infrastructure.Migrations +{ + /// + public partial class financialInstitution : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "FinancialInstitution", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Code = table.Column(type: "character varying(15)", maxLength: 15, nullable: true), + Type = table.Column(type: "integer", nullable: false), + Icon = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Color = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Inactive = table.Column(type: "boolean", nullable: false), + CreatedBy = table.Column(type: "uuid", nullable: false), + UpdatedBy = table.Column(type: "uuid", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_FinancialInstitution", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "FinancialInstitution", + schema: "public"); + } + } +} diff --git a/Fin.Infrastructure/Migrations/FinDbContextModelSnapshot.cs b/Fin.Infrastructure/Migrations/FinDbContextModelSnapshot.cs index 0240f1f..9b6facd 100644 --- a/Fin.Infrastructure/Migrations/FinDbContextModelSnapshot.cs +++ b/Fin.Infrastructure/Migrations/FinDbContextModelSnapshot.cs @@ -23,6 +23,55 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Fin.Domain.FinancialInstitutions.Entities.FinancialInstitution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .HasMaxLength(15) + .HasColumnType("character varying(15)"); + + b.Property("Color") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasColumnType("uuid"); + + b.Property("Icon") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Inactive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .IsUnicode(true) + .HasColumnType("character varying(100)"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("FinancialInstitution", "public"); + }); + modelBuilder.Entity("Fin.Domain.Menus.Entities.Menu", b => { b.Property("Id") diff --git a/Fin.Infrastructure/Seeders/Seeders/DefaultMenusSeeder.cs b/Fin.Infrastructure/Seeders/Seeders/DefaultMenusSeeder.cs index 9478140..d86820f 100644 --- a/Fin.Infrastructure/Seeders/Seeders/DefaultMenusSeeder.cs +++ b/Fin.Infrastructure/Seeders/Seeders/DefaultMenusSeeder.cs @@ -41,6 +41,17 @@ public async Task SeedAsync() KeyWords = "Menu" }, new() + { + Id = Guid.Parse("01994133-6669-7fcd-b6db-19a9b0c06f21"), + FrontRoute = "/admin/financial-institutions", + Name = "finCore.features.financialInstitutions.title", + Color = "#4CAF50", + Icon = "building-columns", + OnlyForAdmin = true, + Position = MenuPosition.LeftTop, + KeyWords = "Financial Institution" + }, + new() { Id = Guid.Parse("01999256-1baa-76bb-be49-6e209249c827"), FrontRoute = "/admin/notifications",