using Microsoft.EntityFrameworkCore; using Tau.Acuvim.Portal.Data; using Tau.Acuvim.Portal.Domain.Rates; using Tau.Acuvim.Portal.DTOs; namespace Tau.Acuvim.Portal.Services; public sealed class RateService(AppDbContext db) { public async Task> ListMunicipalitiesAsync(CancellationToken ct = default) => await db.Municipalities .OrderBy(m => m.Name) .Select(m => new MunicipalityDto(m.Id, m.Name, m.TimeZoneId, m.IsActive, m.Tariffs.Count)) .ToListAsync(ct); public async Task CreateMunicipalityAsync(CreateMunicipalityRequest req, CancellationToken ct = default) { var m = new Municipality { Name = req.Name.Trim(), TimeZoneId = NormalizeTz(req.TimeZoneId), IsActive = req.IsActive }; db.Municipalities.Add(m); await db.SaveChangesAsync(ct); return m; } public async Task UpdateMunicipalityAsync(int id, UpdateMunicipalityRequest req, CancellationToken ct = default) { var m = await db.Municipalities.FindAsync(new object?[] { id }, ct); if (m is null) return false; m.Name = req.Name.Trim(); m.TimeZoneId = NormalizeTz(req.TimeZoneId); m.IsActive = req.IsActive; await db.SaveChangesAsync(ct); return true; } public async Task DeleteMunicipalityAsync(int id, CancellationToken ct = default) { var m = await db.Municipalities.FindAsync(new object?[] { id }, ct); if (m is null) return false; db.Municipalities.Remove(m); await db.SaveChangesAsync(ct); return true; } public async Task> ListTariffsAsync(int municipalityId, CancellationToken ct = default) => await db.Tariffs .Where(t => t.MunicipalityId == municipalityId) .OrderByDescending(t => t.EffectiveFrom) .Select(t => new TariffSummaryDto( t.Id, t.Name, t.EffectiveFrom, t.EffectiveTo, t.DefaultRatePerKwh, t.FixedMonthlyCharge, t.VatPercentage, t.IsActive, t.Periods.Count)) .ToListAsync(ct); public async Task GetTariffAsync(int tariffId, CancellationToken ct = default) { var t = await db.Tariffs.Include(x => x.Periods) .FirstOrDefaultAsync(x => x.Id == tariffId, ct); return t is null ? null : ToDetail(t); } public async Task GetActiveTariffAsync(int municipalityId, DateTime atUtc, CancellationToken ct = default) { var day = DateOnly.FromDateTime(atUtc); var t = await db.Tariffs.Include(x => x.Periods) .Where(x => x.MunicipalityId == municipalityId && x.IsActive && x.EffectiveFrom <= day && (x.EffectiveTo == null || x.EffectiveTo >= day)) .OrderByDescending(x => x.EffectiveFrom) .FirstOrDefaultAsync(ct); return t is null ? null : ToDetail(t); } public async Task CreateTariffAsync(int municipalityId, UpsertTariffRequest req, CancellationToken ct = default) { var t = new Tariff { MunicipalityId = municipalityId, Name = req.Name.Trim(), EffectiveFrom = req.EffectiveFrom, EffectiveTo = req.EffectiveTo, DefaultRatePerKwh = req.DefaultRatePerKwh, FixedMonthlyCharge = req.FixedMonthlyCharge, VatPercentage = req.VatPercentage, IsActive = req.IsActive, Periods = MapPeriods(req.Periods).ToList() }; db.Tariffs.Add(t); await db.SaveChangesAsync(ct); return ToDetail(t); } public async Task UpdateTariffAsync(int tariffId, UpsertTariffRequest req, CancellationToken ct = default) { var t = await db.Tariffs.Include(x => x.Periods).FirstOrDefaultAsync(x => x.Id == tariffId, ct); if (t is null) return null; t.Name = req.Name.Trim(); t.EffectiveFrom = req.EffectiveFrom; t.EffectiveTo = req.EffectiveTo; t.DefaultRatePerKwh = req.DefaultRatePerKwh; t.FixedMonthlyCharge = req.FixedMonthlyCharge; t.VatPercentage = req.VatPercentage; t.IsActive = req.IsActive; db.TariffPeriods.RemoveRange(t.Periods); t.Periods = MapPeriods(req.Periods).ToList(); await db.SaveChangesAsync(ct); return ToDetail(t); } public async Task DeleteTariffAsync(int tariffId, CancellationToken ct = default) { var t = await db.Tariffs.FindAsync(new object?[] { tariffId }, ct); if (t is null) return false; db.Tariffs.Remove(t); await db.SaveChangesAsync(ct); return true; } private static IEnumerable MapPeriods(IReadOnlyList periods) => periods.Select(p => new TariffPeriod { Name = p.Name.Trim(), DaysOfWeek = (DayOfWeekFlag)p.DaysOfWeek, StartTime = TimeOnly.Parse(p.StartTime), EndTime = TimeOnly.Parse(p.EndTime), RatePerKwh = p.RatePerKwh }); private static TariffDetailDto ToDetail(Tariff t) => new( t.Id, t.MunicipalityId, t.Name, t.EffectiveFrom, t.EffectiveTo, t.DefaultRatePerKwh, t.FixedMonthlyCharge, t.VatPercentage, t.IsActive, t.Periods.OrderBy(p => p.StartTime).Select(p => new TariffPeriodDto( p.Name, (int)p.DaysOfWeek, p.StartTime.ToString("HH:mm"), p.EndTime.ToString("HH:mm"), p.RatePerKwh)).ToArray()); private static string? NormalizeTz(string? tz) => string.IsNullOrWhiteSpace(tz) ? null : tz.Trim(); }