using System.Text.Json; using Microsoft.EntityFrameworkCore; using Tiku.Application.Jobs; using Tiku.Domain.Commerce; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Jobs; using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.Commerce.Reconciliation; internal sealed class CommerceReconciliationJobHandler(TikuDbContext dbContext) : IBackgroundJobHandler { public string JobType => "commerce_reconciliation"; public async Task HandleAsync( BackgroundJobExecutionContext context, CancellationToken cancellationToken = default) { var provider = NormalizeProvider(BackgroundJobPayload.GetString(context.Payload, "provider")); var hasProviderConfig = await dbContext.TenantExternalProviders.AnyAsync( item => item.TenantId == context.TenantId && item.Capability == TenantExternalProviderCapability.Payment && item.Provider == provider && item.Status == TenantExternalProviderStatus.Active, cancellationToken); if (!hasProviderConfig) throw new InvalidOperationException( $"Active payment provider '{provider}' is required for commerce reconciliation job."); var billDate = BackgroundJobPayload.GetDateOnly(context.Payload, "billDate") ?? DateOnly.FromDateTime(DateTime.UtcNow.Date); var billType = BackgroundJobPayload.GetEnum(context.Payload, "billType", ReconciliationBillType.Combined); var sourceHash = $"background-job:{context.JobId:N}"; var batch = await dbContext.CommerceReconciliationBatches.SingleOrDefaultAsync( item => item.TenantId == context.TenantId && item.Provider == provider && item.Source == ReconciliationSource.ProviderDownload && item.SourceHash == sourceHash, cancellationToken); if (batch is null) { batch = new CommerceReconciliationBatch { TenantId = context.TenantId, Provider = provider, BillDate = billDate, BillType = billType, Source = ReconciliationSource.ProviderDownload, SourceName = $"provider-bill:{provider}:{billDate:yyyyMMdd}", SourceHash = sourceHash, Status = ReconciliationBatchStatus.Pending, Metadata = JsonSerializer.SerializeToElement(new { jobId = context.JobId, note = "Provider bill job created the reconciliation batch; provider download/parser is handled by a dedicated provider processor." }) }; dbContext.CommerceReconciliationBatches.Add(batch); await dbContext.SaveChangesAsync(cancellationToken); } return new BackgroundJobHandlerResult(JsonSerializer.SerializeToElement(new { batchId = batch.Id, provider, billDate, billType = billType.ToString(), status = batch.Status.ToString() })); } private static string NormalizeProvider(string? provider) { var normalized = (provider ?? string.Empty).Trim().ToLowerInvariant(); return string.IsNullOrWhiteSpace(normalized) ? throw new InvalidOperationException("Background job provider is required.") : normalized; } }