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(); 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() .AsNoTracking() .FirstOrDefault(x => x.TenantKey == key); if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found"); return cfg.TenantId; } public async Task 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> 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(req.Page, req.PageSize, total, items); } public async Task 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 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); } }