[Add] MasterData Services.

This commit is contained in:
Thanakarn Klangkasame
2025-11-26 10:29:56 +07:00
parent d4ab1cb592
commit 1e636aa3d5
205 changed files with 7814 additions and 69 deletions

View File

@@ -126,6 +126,15 @@ public class AppDbContext : DbContext
public DbSet<Allergen> Allergens => Set<Allergen>();
public DbSet<AllergenBlock> AllergenBlocks => Set<AllergenBlock>();
public DbSet<Province> Provinces => Set<Province>();
public DbSet<ProvinceBlock> ProvinceBlocks => Set<ProvinceBlock>();
public DbSet<District> Districts => Set<District>();
public DbSet<DistrictBlock> DistrictBlocks => Set<DistrictBlock>();
public DbSet<Subdistrict> Subdistricts => Set<Subdistrict>();
public DbSet<SubdistrictBlock> SubdistrictBlocks => Set<SubdistrictBlock>();
protected override void OnModelCreating(ModelBuilder model)
{
// JSON converters/comparers
@@ -182,7 +191,6 @@ public class AppDbContext : DbContext
metaProp.HasColumnType("jsonb");
b.HasIndex(x => new { x.Scope, x.TenantId, x.Code }).IsUnique();
// b.HasIndex(x => new { x.TenantId, x.OverridesGlobalId }).IsUnique().HasFilter("\"OverridesGlobalId\" IS NOT NULL");
}
void ConfigureBlockBase<TBlock, TMaster>(string table, string schema = "master")
@@ -780,6 +788,62 @@ public class AppDbContext : DbContext
ConfigureMasterBase<Allergen>("allergens");
ConfigureBlockBase<AllergenBlock, Allergen>("allergen_blocks");
ConfigureMasterBase<Province>("provinces");
ConfigureBlockBase<ProvinceBlock, Province>("province_blocks");
model.Entity<Province>(b =>
{
b.Property(x => x.Code)
.IsRequired()
.HasMaxLength(2);
b.HasIndex(x => x.Code);
});
ConfigureMasterBase<District>("districts");
ConfigureBlockBase<DistrictBlock, District>("district_blocks");
model.Entity<District>(b =>
{
b.Property(x => x.Code)
.IsRequired()
.HasMaxLength(4);
b.HasIndex(x => x.Code);
b.HasOne(x => x.Province)
.WithMany(p => p.Districts)
.HasForeignKey(x => new { x.TenantId, x.ProvinceId })
.HasPrincipalKey(p => new { p.TenantId, p.Id })
.OnDelete(DeleteBehavior.Restrict);
b.HasIndex(x => new { x.TenantId, x.ProvinceId });
});
ConfigureMasterBase<Subdistrict>("subdistricts");
ConfigureBlockBase<SubdistrictBlock, Subdistrict>("subdistrict_blocks");
model.Entity<Subdistrict>(b =>
{
b.Property(x => x.Code)
.IsRequired()
.HasMaxLength(6);
b.Property(x => x.Postcode)
.HasMaxLength(10);
b.HasIndex(x => x.Code);
b.HasIndex(x => x.Postcode);
b.HasOne(x => x.District)
.WithMany(d => d.Subdistricts)
.HasForeignKey(x => new { x.TenantId, x.DistrictId })
.HasPrincipalKey(d => new { d.TenantId, d.Id })
.OnDelete(DeleteBehavior.Restrict);
b.HasIndex(x => new { x.TenantId, x.DistrictId });
});
// ====== Enum conversions ======
model.Entity<UserIdentity>().Property(x => x.Type).HasConversion<int>();
model.Entity<UserMfaFactor>().Property(x => x.Type).HasConversion<int>();

View File

@@ -1,6 +1,8 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
using AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
using AMREZ.EOP.Abstractions.Applications.UseCases.ImportData.Location;
using AMREZ.EOP.Abstractions.Applications.UseCases.MasterData.Brand;
using AMREZ.EOP.Abstractions.Applications.UseCases.Payments.SlipVerification;
using AMREZ.EOP.Abstractions.Applications.UseCases.Payments.SlipVerification.QrDecode;
using AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
@@ -11,10 +13,13 @@ using AMREZ.EOP.Abstractions.Security;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Application.UseCases.Authentications;
using AMREZ.EOP.Application.UseCases.HumanResources;
using AMREZ.EOP.Application.UseCases.ImportData.Location;
using AMREZ.EOP.Application.UseCases.MasterData.Brand;
using AMREZ.EOP.Application.UseCases.Payments.SlipVerification;
using AMREZ.EOP.Application.UseCases.Payments.SlipVerification.BankDetect;
using AMREZ.EOP.Application.UseCases.Payments.SlipVerification.QrDecode;
using AMREZ.EOP.Application.UseCases.Tenancy;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Domain.Shared.Payments;
using AMREZ.EOP.Domain.Shared.Tenancy;
using AMREZ.EOP.Infrastructures.Data;
@@ -139,6 +144,8 @@ public static class ServiceCollectionExtensions
services.AddScoped<IQrDecoder, ZxingQrDecoder>();
services.AddScoped<IVerifyFromImageUseCase, VerifyFromImageUseCase>();
services.AddMasterDataBrands();
services.AddMasterDataLocations();
// Redis (optional)
services.AddSingleton<IConnectionMultiplexer?>(sp =>
{
@@ -160,4 +167,33 @@ public static class ServiceCollectionExtensions
return services;
}
public static IServiceCollection AddMasterDataBrands(this IServiceCollection services)
{
// Repo
services.AddScoped<IBrandRepository, BrandRepository>();
// UseCases
services.AddScoped<IListBrandsUseCase, ListBrandsUseCase>();
services.AddScoped<IGetBrandUseCase, GetBrandUseCase>();
services.AddScoped<ICreateBrandUseCase, CreateBrandUseCase>();
services.AddScoped<IUpdateBrandUseCase, UpdateBrandUseCase>();
services.AddScoped<IDeleteBrandUseCase, DeleteBrandUseCase>();
return services;
}
public static IServiceCollection AddMasterDataLocations(this IServiceCollection services)
{
// Repo
services.AddScoped<IProvinceRepository, ProvinceRepository>();
services.AddScoped<IDistrictRepository, DistrictRepository>();
services.AddScoped<ISubdistrictRepository, SubdistrictRepository>();
// UseCases
services.AddScoped<ILocationImportUseCase, LocationImportUseCase>();
return services;
}
}

View File

@@ -2600,6 +2600,150 @@ namespace AMREZ.EOP.Infrastructures.Migrations
});
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.District", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(4)
.HasColumnType("character varying(4)");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<string>("CreatedBy")
.HasColumnType("text")
.HasColumnName("created_by");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(true);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("is_deleted");
b.Property<bool>("IsSystem")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false);
b.Property<string>("Meta")
.HasColumnType("jsonb");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("NameI18n")
.HasColumnType("jsonb");
b.Property<Guid?>("OverridesGlobalId")
.HasColumnType("uuid");
b.Property<Guid>("ProvinceId")
.HasColumnType("uuid");
b.Property<string>("Scope")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("global");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<string>("UpdatedBy")
.HasColumnType("text")
.HasColumnName("updated_by");
b.HasKey("Id");
b.HasIndex("Code");
b.HasIndex("TenantId");
b.HasIndex("TenantId", "ProvinceId");
b.HasIndex("Scope", "TenantId", "Code")
.IsUnique();
b.ToTable("districts", "master", t =>
{
t.HasCheckConstraint("ck_districts_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_districts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.DistrictBlock", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<string>("CreatedBy")
.HasColumnType("text")
.HasColumnName("created_by");
b.Property<Guid>("GlobalId")
.HasColumnType("uuid");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("is_deleted");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<string>("UpdatedBy")
.HasColumnType("text")
.HasColumnName("updated_by");
b.HasKey("Id");
b.HasIndex("GlobalId");
b.HasIndex("TenantId");
b.HasIndex("TenantId", "GlobalId")
.IsUnique();
b.ToTable("district_blocks", "master", t =>
{
t.HasCheckConstraint("ck_district_blocks_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_district_blocks_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.DocControlStatus", b =>
{
b.Property<Guid>("Id")
@@ -3569,6 +3713,145 @@ namespace AMREZ.EOP.Infrastructures.Migrations
});
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.Province", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(2)
.HasColumnType("character varying(2)");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<string>("CreatedBy")
.HasColumnType("text")
.HasColumnName("created_by");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(true);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("is_deleted");
b.Property<bool>("IsSystem")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false);
b.Property<string>("Meta")
.HasColumnType("jsonb");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("NameI18n")
.HasColumnType("jsonb");
b.Property<Guid?>("OverridesGlobalId")
.HasColumnType("uuid");
b.Property<string>("Scope")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("global");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<string>("UpdatedBy")
.HasColumnType("text")
.HasColumnName("updated_by");
b.HasKey("Id");
b.HasIndex("Code");
b.HasIndex("TenantId");
b.HasIndex("Scope", "TenantId", "Code")
.IsUnique();
b.ToTable("provinces", "master", t =>
{
t.HasCheckConstraint("ck_provinces_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_provinces_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.ProvinceBlock", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<string>("CreatedBy")
.HasColumnType("text")
.HasColumnName("created_by");
b.Property<Guid>("GlobalId")
.HasColumnType("uuid");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("is_deleted");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<string>("UpdatedBy")
.HasColumnType("text")
.HasColumnName("updated_by");
b.HasKey("Id");
b.HasIndex("GlobalId");
b.HasIndex("TenantId");
b.HasIndex("TenantId", "GlobalId")
.IsUnique();
b.ToTable("province_blocks", "master", t =>
{
t.HasCheckConstraint("ck_province_blocks_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_province_blocks_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.QaStage", b =>
{
b.Property<Guid>("Id")
@@ -4811,6 +5094,158 @@ namespace AMREZ.EOP.Infrastructures.Migrations
});
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.Subdistrict", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(6)
.HasColumnType("character varying(6)");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<string>("CreatedBy")
.HasColumnType("text")
.HasColumnName("created_by");
b.Property<Guid>("DistrictId")
.HasColumnType("uuid");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(true);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("is_deleted");
b.Property<bool>("IsSystem")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false);
b.Property<string>("Meta")
.HasColumnType("jsonb");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("NameI18n")
.HasColumnType("jsonb");
b.Property<Guid?>("OverridesGlobalId")
.HasColumnType("uuid");
b.Property<string>("Postcode")
.HasMaxLength(10)
.HasColumnType("character varying(10)");
b.Property<string>("Scope")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("text")
.HasDefaultValue("global");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<string>("UpdatedBy")
.HasColumnType("text")
.HasColumnName("updated_by");
b.HasKey("Id");
b.HasAlternateKey("TenantId", "Id");
b.HasIndex("Code");
b.HasIndex("Postcode");
b.HasIndex("TenantId");
b.HasIndex("TenantId", "DistrictId");
b.HasIndex("Scope", "TenantId", "Code")
.IsUnique();
b.ToTable("subdistricts", "master", t =>
{
t.HasCheckConstraint("ck_subdistricts_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_subdistricts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.SubdistrictBlock", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now() at time zone 'utc'");
b.Property<string>("CreatedBy")
.HasColumnType("text")
.HasColumnName("created_by");
b.Property<Guid>("GlobalId")
.HasColumnType("uuid");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("is_deleted");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.Property<string>("UpdatedBy")
.HasColumnType("text")
.HasColumnName("updated_by");
b.HasKey("Id");
b.HasIndex("GlobalId");
b.HasIndex("TenantId");
b.HasIndex("TenantId", "GlobalId")
.IsUnique();
b.ToTable("subdistrict_blocks", "master", t =>
{
t.HasCheckConstraint("ck_subdistrict_blocks_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_subdistrict_blocks_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.Uom", b =>
{
b.Property<Guid>("Id")
@@ -5538,6 +5973,27 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.IsRequired();
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.District", b =>
{
b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.Province", "Province")
.WithMany("Districts")
.HasForeignKey("TenantId", "ProvinceId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Province");
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.DistrictBlock", b =>
{
b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.District", null)
.WithMany()
.HasForeignKey("GlobalId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.DocControlStatusBlock", b =>
{
b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.DocControlStatus", null)
@@ -5601,6 +6057,15 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.IsRequired();
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.ProvinceBlock", b =>
{
b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.Province", null)
.WithMany()
.HasForeignKey("GlobalId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.QaStageBlock", b =>
{
b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.QaStage", null)
@@ -5682,6 +6147,27 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.IsRequired();
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.Subdistrict", b =>
{
b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.District", "District")
.WithMany("Subdistricts")
.HasForeignKey("TenantId", "DistrictId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("District");
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.SubdistrictBlock", b =>
{
b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.Subdistrict", null)
.WithMany()
.HasForeignKey("GlobalId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.UomBlock", b =>
{
b.HasOne("AMREZ.EOP.Domain.Entities.MasterData.Uom", null)
@@ -5780,6 +6266,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.Navigation("Children");
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.District", b =>
{
b.Navigation("Subdistricts");
});
modelBuilder.Entity("AMREZ.EOP.Domain.Entities.MasterData.Province", b =>
{
b.Navigation("Districts");
});
#pragma warning restore 612, 618
}
}

View File

@@ -0,0 +1,148 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Allergen;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class AllergenRepository : IAllergenRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public AllergenRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Allergen?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Allergens.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Allergen>> SearchEffectiveAsync(Guid tenantId, AllergenListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Allergens.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Allergens
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.AllergenBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Allergens.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Allergen>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Allergens.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Allergen entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Allergens.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Allergen entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Allergens.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Allergens.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.AllergenBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new AllergenBlock
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.AllergenBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,148 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Brand;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class BrandRepository : IBrandRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public BrandRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Brand?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Brands.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Brand>> SearchEffectiveAsync(Guid tenantId, BrandListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Brands.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Brands
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.BrandBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Brands.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Brand>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Brands.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Brand entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Brands.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Brand entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Brands.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Brands.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.BrandBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new BrandBlock
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.BrandBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,148 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Category;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class CategoryRepository : ICategoryRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public CategoryRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Category?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Categories.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Category>> SearchEffectiveAsync(Guid tenantId, CategoryListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Categories.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Categories
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.CategoryBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Categories.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Category>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Categories.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Category entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Categories.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Category entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Categories.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Categories.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.CategoryBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new CategoryBlock
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.CategoryBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,148 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.ComplianceStatus;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class ComplianceStatusRepository : IComplianceStatusRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public ComplianceStatusRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<ComplianceStatus?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.ComplianceStatuses.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<ComplianceStatus>> SearchEffectiveAsync(Guid tenantId, ComplianceStatusListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.ComplianceStatuses.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.ComplianceStatuses
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.ComplianceStatusBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.ComplianceStatuses.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<ComplianceStatus>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.ComplianceStatuses.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(ComplianceStatus entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.ComplianceStatuses.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(ComplianceStatus entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.ComplianceStatuses.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.ComplianceStatuses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.ComplianceStatusBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new ComplianceStatusBlock
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.ComplianceStatusBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,148 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Country;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class CountryRepository : ICountryRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public CountryRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Country?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Countries.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Country>> SearchEffectiveAsync(Guid tenantId, CountryListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Countries.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Countries
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.CountryBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Countries.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Country>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Countries.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Country entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Countries.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Country entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Countries.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Countries.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.CountryBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new CountryBlock
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.CountryBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,173 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.District;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class DistrictRepository : IDistrictRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public DistrictRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<District?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Districts.AsNoTracking()
.Include(x => x.Province)
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<District>> SearchEffectiveAsync(
Guid tenantId,
DistrictListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Districts.AsNoTracking()
.Include(d => d.Province)
.Where(d => d.Scope == "tenant" && d.TenantId == tid && !d.IsDeleted);
var overriddenIds = db.Districts
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.DistrictBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Districts.AsNoTracking()
.Include(d => d.Province)
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive)
q = q.Where(x => x.IsActive);
// Filter by Province
if (req.ProvinceId.HasValue && req.ProvinceId.Value != Guid.Empty)
q = q.Where(x => x.ProvinceId == req.ProvinceId.Value);
if (!string.IsNullOrWhiteSpace(req.ProvinceCode))
{
var pd = req.ProvinceCode.Trim();
q = q.Where(x => x.Province.Code == pd);
}
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x =>
x.Code.Contains(s) ||
x.Name.Contains(s) ||
x.Code.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Province.Code)
.ThenBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<District>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Districts.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(District entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Districts.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(District entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Districts.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var d = await db.Districts.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (d is null) return 0;
if (d.Scope == "global")
{
var exists = await db.DistrictBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new DistrictBlock
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.DistrictBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (d.TenantId != tid) return 0;
d.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Brand;
using AMREZ.EOP.Contracts.DTOs.MasterData.DocControlStatus;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class DocControlStatusRepository : IDocControlStatusRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public DocControlStatusRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<DocControlStatus?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.DocControlStatuses.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<DocControlStatus>> SearchEffectiveAsync(Guid tenantId, DocControlStatusListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.DocControlStatuses.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.DocControlStatuses
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.DocControlStatusBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.DocControlStatuses.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<DocControlStatus>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.DocControlStatuses.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(DocControlStatus entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.DocControlStatuses.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(DocControlStatus entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.DocControlStatuses.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.DocControlStatuses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.DocControlStatusBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new DocControlStatusBlock
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.DocControlStatusBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,148 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.FuncTest;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class FuncTestRepository : IFuncTestRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public FuncTestRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<FuncTest?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.FuncTests.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<FuncTest>> SearchEffectiveAsync(Guid tenantId, FuncTestListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.FuncTests.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.FuncTests
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.FuncTestBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.FuncTests.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<FuncTest>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.FuncTests.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(FuncTest entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.FuncTests.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(FuncTest entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.FuncTests.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.FuncTests.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.FuncTestBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new FuncTestBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.FuncTestBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,148 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.HazardClass;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class HazardClassRepository : IHazardClassRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public HazardClassRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<HazardClass?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.HazardClasses.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<HazardClass>> SearchEffectiveAsync(Guid tenantId, HazardClassListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.HazardClasses.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.HazardClasses
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.HazardClassBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.HazardClasses.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<HazardClass>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.HazardClasses.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(HazardClass entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.HazardClasses.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(HazardClass entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.HazardClasses.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.HazardClasses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.HazardClassBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new HazardClassBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.HazardClassBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,148 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Language;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class LanguageRepository : ILanguageRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public LanguageRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Language?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Languages.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Language>> SearchEffectiveAsync(Guid tenantId, LanguageListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Languages.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Languages
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.LanguageBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Languages.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Language>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Languages.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Language entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Languages.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Language entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Languages.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Languages.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.LanguageBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new LanguageBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.LanguageBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Language;
using AMREZ.EOP.Contracts.DTOs.MasterData.Manufacturer;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class ManufacturerRepository : IManufacturerRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public ManufacturerRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Manufacturer?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Manufacturers.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Manufacturer>> SearchEffectiveAsync(Guid tenantId, ManufacturerListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Manufacturers.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Manufacturers
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.ManufacturerBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Manufacturers.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Manufacturer>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Manufacturers.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Manufacturer entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Manufacturers.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Manufacturer entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Manufacturers.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Manufacturers.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.ManufacturerBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new ManufacturerBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.ManufacturerBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Manufacturer;
using AMREZ.EOP.Contracts.DTOs.MasterData.Market;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class MarketRepository : IMarketRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public MarketRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Market?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Markets.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Market>> SearchEffectiveAsync(Guid tenantId, MarketListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Markets.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Markets
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.MarketBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Markets.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Market>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Markets.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Market entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Markets.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Market entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Markets.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Markets.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.MarketBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new MarketBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.MarketBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Market;
using AMREZ.EOP.Contracts.DTOs.MasterData.PackingGroup;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class PackingGroupRepository : IPackingGroupRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public PackingGroupRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<PackingGroup?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.PackingGroups.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<PackingGroup>> SearchEffectiveAsync(Guid tenantId, PackingGroupListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.PackingGroups.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.PackingGroups
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.PackingGroupBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.PackingGroups.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<PackingGroup>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.PackingGroups.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(PackingGroup entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.PackingGroups.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(PackingGroup entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.PackingGroups.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.PackingGroups.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.PackingGroupBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new PackingGroupBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.PackingGroupBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,164 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Province;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class ProvinceRepository : IProvinceRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public ProvinceRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Province?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Provinces.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Province>> SearchEffectiveAsync(
Guid tenantId,
ProvinceListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
// tenant scoped
var tenantQ = db.Provinces.AsNoTracking()
.Where(p => p.Scope == "tenant" && p.TenantId == tid && !p.IsDeleted);
// overridden globals
var overriddenIds = db.Provinces
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
// blocked globals
var blockedIds = db.ProvinceBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
// global scoped (effective)
var globalsQ = db.Provinces.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive)
q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x =>
x.Code.Contains(s) ||
x.Name.Contains(s) ||
x.Code.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Province>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Provinces.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Province entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Provinces.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Province entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Provinces.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var p = await db.Provinces.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (p is null) return 0;
if (p.Scope == "global")
{
var exists = await db.ProvinceBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new ProvinceBlock
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.ProvinceBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (p.TenantId != tid) return 0;
p.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.PackingGroup;
using AMREZ.EOP.Contracts.DTOs.MasterData.QaStage;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class QaStageRepository : IQaStageRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public QaStageRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<QaStage?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.QaStages.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<QaStage>> SearchEffectiveAsync(Guid tenantId, QaStageListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.QaStages.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.QaStages
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.QaStageBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.QaStages.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<QaStage>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.QaStages.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(QaStage entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.QaStages.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(QaStage entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.QaStages.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.QaStages.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.QaStageBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new QaStageBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.QaStageBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,148 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.QcStatus;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class QcStatusRepository : IQcStatusRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public QcStatusRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<QcStatus?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.QcStatuses.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<QcStatus>> SearchEffectiveAsync(Guid tenantId, QcStatusListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.QcStatuses.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.QcStatuses
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.QcStatusBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.QcStatuses.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<QcStatus>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.QcStatuses.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(QcStatus entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.QcStatuses.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(QcStatus entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.QcStatuses.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.QcStatuses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.QcStatusBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new QcStatusBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.QcStatusBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.QcStatus;
using AMREZ.EOP.Contracts.DTOs.MasterData.RecallClass;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class RecallClassRepository : IRecallClassRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public RecallClassRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<RecallClass?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.RecallClasses.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<RecallClass>> SearchEffectiveAsync(Guid tenantId, RecallClassListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.RecallClasses.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.RecallClasses
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.RecallClassBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.RecallClasses.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<RecallClass>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.RecallClasses.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(RecallClass entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.RecallClasses.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(RecallClass entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.RecallClasses.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.RecallClasses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.RecallClassBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new RecallClassBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.RecallClassBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.RecallClass;
using AMREZ.EOP.Contracts.DTOs.MasterData.RiskClass;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class RiskClassRepository : IRiskClassRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public RiskClassRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<RiskClass?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.RiskClasses.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<RiskClass>> SearchEffectiveAsync(Guid tenantId, RiskClassListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.RiskClasses.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.RiskClasses
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.RiskClassBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.RiskClasses.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<RiskClass>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.RiskClasses.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(RiskClass entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.RiskClasses.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(RiskClass entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.RiskClasses.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.RiskClasses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.RiskClassBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new RiskClassBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.RiskClassBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.RiskClass;
using AMREZ.EOP.Contracts.DTOs.MasterData.Route;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class RouteRepository : IRouteRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public RouteRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Route?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Routes.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Route>> SearchEffectiveAsync(Guid tenantId, RouteListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Routes.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Routes
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.RouteBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Routes.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Route>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Routes.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Route entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Routes.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Route entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Routes.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Routes.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.RouteBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new RouteBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.RouteBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,148 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Route;
using AMREZ.EOP.Contracts.DTOs.MasterData.RxSchedule;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class RxScheduleRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public RxScheduleRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<RxSchedule?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.RxSchedules.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<RxSchedule>> SearchEffectiveAsync(Guid tenantId, RxScheduleListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.RxSchedules.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.RxSchedules
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.RxScheduleBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.RxSchedules.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<RxSchedule>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.RxSchedules.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(RxSchedule entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.RxSchedules.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(RxSchedule entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.RxSchedules.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.RxSchedules.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.RxScheduleBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new RxScheduleBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.RxScheduleBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.RxSchedule;
using AMREZ.EOP.Contracts.DTOs.MasterData.Species;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class SpeciesRepository : ISpeciesRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public SpeciesRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Species?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Species.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Species>> SearchEffectiveAsync(Guid tenantId, SpeciesListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Species.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Species
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.SpeciesBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Species.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Species>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Species.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Species entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Species.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Species entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Species.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Species.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.SpeciesBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new SpeciesBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.SpeciesBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Species;
using AMREZ.EOP.Contracts.DTOs.MasterData.StabilityStatus;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class StabilityStatusRepository : IStabilityStatusRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public StabilityStatusRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<StabilityStatus?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.StabilityStatuses.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<StabilityStatus>> SearchEffectiveAsync(Guid tenantId, StabilityStatusListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.StabilityStatuses.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.StabilityStatuses
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.StabilityStatusBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.StabilityStatuses.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<StabilityStatus>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.StabilityStatuses.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(StabilityStatus entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.StabilityStatuses.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(StabilityStatus entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.StabilityStatuses.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.StabilityStatuses.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.StabilityStatusBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new StabilityStatusBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.StabilityStatusBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.StabilityStatus;
using AMREZ.EOP.Contracts.DTOs.MasterData.SterilizationMethod;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public class SterilizationMethodRepository : ISterilizationMethodRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public SterilizationMethodRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<SterilizationMethod?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.SterilizationMethods.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<SterilizationMethod>> SearchEffectiveAsync(Guid tenantId, SterilizationMethodListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.SterilizationMethods.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.SterilizationMethods
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.SterilizationMethodBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.SterilizationMethods.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<SterilizationMethod>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.SterilizationMethods.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(SterilizationMethod entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.SterilizationMethods.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(SterilizationMethod entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.SterilizationMethods.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.SterilizationMethods.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.SterilizationMethodBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new SterilizationMethodBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.SterilizationMethodBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,187 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Subdistrict;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class SubdistrictRepository : ISubdistrictRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public SubdistrictRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Subdistrict?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Subdistricts.AsNoTracking()
.Include(x => x.District)
.ThenInclude(d => d.Province)
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Subdistrict>> SearchEffectiveAsync(
Guid tenantId,
SubdistrictListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Subdistricts.AsNoTracking()
.Include(s => s.District)
.ThenInclude(d => d.Province)
.Where(s => s.Scope == "tenant" && s.TenantId == tid && !s.IsDeleted);
var overriddenIds = db.Subdistricts
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.SubdistrictBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Subdistricts.AsNoTracking()
.Include(s => s.District)
.ThenInclude(d => d.Province)
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive)
q = q.Where(x => x.IsActive);
// Filter by District
if (req.DistrictId.HasValue && req.DistrictId.Value != Guid.Empty)
q = q.Where(x => x.DistrictId == req.DistrictId.Value);
if (!string.IsNullOrWhiteSpace(req.DistrictCode))
{
var dd = req.DistrictCode.Trim();
q = q.Where(x => x.District.Code == dd);
}
// Filter by Province
if (req.ProvinceId.HasValue && req.ProvinceId.Value != Guid.Empty)
q = q.Where(x => x.District.ProvinceId == req.ProvinceId.Value);
if (!string.IsNullOrWhiteSpace(req.ProvinceCode))
{
var pd = req.ProvinceCode.Trim();
q = q.Where(x => x.District.Province.Code == pd);
}
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x =>
x.Code.Contains(s) ||
x.Name.Contains(s) ||
x.Code.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.District.Province.Code)
.ThenBy(x => x.District.Code)
.ThenBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Subdistrict>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Subdistricts.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Subdistrict entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Subdistricts.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Subdistrict entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Subdistricts.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var s = await db.Subdistricts.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (s is null) return 0;
if (s.Scope == "global")
{
var exists = await db.SubdistrictBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new SubdistrictBlock
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.SubdistrictBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (s.TenantId != tid) return 0;
s.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.SterilizationMethod;
using AMREZ.EOP.Contracts.DTOs.MasterData.Uom;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class UomRepository : IUomRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public UomRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Uom?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Uoms.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Uom>> SearchEffectiveAsync(Guid tenantId, UomListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Uoms.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Uoms
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.UomBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Uoms.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Uom>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Uoms.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Uom entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Uoms.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Uom entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Uoms.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Uoms.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.UomBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new UomBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.UomBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,149 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Contracts.DTOs.Common;
using AMREZ.EOP.Contracts.DTOs.MasterData.Uom;
using AMREZ.EOP.Contracts.DTOs.MasterData.Vvm;
using AMREZ.EOP.Domain.Entities.MasterData;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
namespace AMREZ.EOP.Infrastructures.Repositories;
public class VvmRepository : IVvmRepository
{
private readonly IDbScope _scope;
private readonly ITenantResolver _tenantResolver;
private readonly IHttpContextAccessor _http;
public VvmRepository(IDbScope scope, ITenantResolver tenantResolver, IHttpContextAccessor http)
{
_scope = scope;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid EnsureTenant(out AppDbContext db)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
db = _scope.Get<AppDbContext>();
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var cfg = db.Set<AMREZ.EOP.Domain.Entities.Tenancy.TenantConfig>()
.AsNoTracking()
.FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<Vvm?> GetAsync(Guid id, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
return await db.Vvms.AsNoTracking()
.Where(x => x.Id == id && !x.IsDeleted)
.Where(x => x.Scope == "tenant" ? x.TenantId == tid : true)
.FirstOrDefaultAsync(ct);
}
public async Task<PagedResponse<Vvm>> SearchEffectiveAsync(Guid tenantId, VvmListRequest req,
CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var tenantQ = db.Vvms.AsNoTracking()
.Where(b => b.Scope == "tenant" && b.TenantId == tid && !b.IsDeleted);
var overriddenIds = db.Vvms
.Where(t => t.Scope == "tenant" && t.TenantId == tid && !t.IsDeleted && t.OverridesGlobalId != null)
.Select(t => t.OverridesGlobalId!.Value);
var blockedIds = db.VvmBlocks
.Where(b => b.TenantId == tid)
.Select(b => b.GlobalId);
var globalsQ = db.Vvms.AsNoTracking()
.Where(g => g.Scope == "global" && !g.IsDeleted)
.Where(g => !overriddenIds.Contains(g.Id) && !blockedIds.Contains(g.Id));
var q = tenantQ.Union(globalsQ);
if (!req.IncludeInactive) q = q.Where(x => x.IsActive);
if (!string.IsNullOrWhiteSpace(req.Search))
{
var s = req.Search.Trim();
q = q.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
}
var total = await q.CountAsync(ct);
var items = await q
.OrderBy(x => x.Code)
.ThenBy(x => x.Name)
.Skip((req.Page - 1) * req.PageSize)
.Take(req.PageSize)
.ToListAsync(ct);
return new PagedResponse<Vvm>(req.Page, req.PageSize, total, items);
}
public async Task<bool> CodeExistsAsync(Guid tenantId, string code, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var norm = code.Trim();
return await db.Vvms.AnyAsync(b =>
!b.IsDeleted &&
b.Code == norm &&
(b.Scope == "tenant" && b.TenantId == tid), ct);
}
public async Task AddAsync(Vvm entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
await db.Vvms.AddAsync(entity, ct);
await db.SaveChangesAsync(ct);
}
public async Task UpdateAsync(Vvm entity, CancellationToken ct = default)
{
EnsureTenant(out var db);
db.Vvms.Update(entity);
await db.SaveChangesAsync(ct);
}
public async Task<int> SoftDeleteAsync(Guid id, Guid tenantId, CancellationToken ct = default)
{
var tid = EnsureTenant(out var db);
var b = await db.Vvms.FirstOrDefaultAsync(x => x.Id == id && !x.IsDeleted, ct);
if (b is null) return 0;
if (b.Scope == "global")
{
var exists = await db.VvmBlocks
.AnyAsync(x => x.TenantId == tid && x.GlobalId == id, ct);
if (exists) return 0;
var block = new VvmBlock()
{
Id = Guid.NewGuid(),
TenantId = tid,
GlobalId = id
};
await db.VvmBlocks.AddAsync(block, ct);
return await db.SaveChangesAsync(ct);
}
if (b.TenantId != tid) return 0;
b.IsDeleted = true;
return await db.SaveChangesAsync(ct);
}
}