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(); 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.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> 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(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.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 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); } }