using Dapper; using FastEndpoints; using Npgsql; using Shared.Operations; using System.Text.Json; namespace Modules.Common.Reconcile.CreateExceptions; public sealed record CreateReconcileExceptionsRequest(IReadOnlyList Ids); public sealed record CreateReconcileExceptionsResponse(int CreatedOrUpdatedCount, int SkippedCount); public sealed class Endpoint(NpgsqlDataSource dataSource, OperationsProjectionWriter projection) : Endpoint { public override void Configure() { Post("/api/reconcile/items/create-exceptions"); Permissions("common.operations.create"); } public override async Task HandleAsync(CreateReconcileExceptionsRequest req, CancellationToken ct) { var tenant = OperationsIdentity.TenantId(User); var ids = req.Ids.Distinct().Take(500).ToArray(); if (ids.Length == 0) { await Send.OkAsync(new(0, 0), ct); return; } await using var connection = await dataSource.OpenConnectionAsync(ct); var rows = (await connection.QueryAsync(new CommandDefinition(""" select id, reconcile_type as ReconcileType, reference_no as ReferenceNo, expected_value as ExpectedValue, actual_value as ActualValue, difference_value as DifferenceValue, reason_code as ReasonCode, reason_text as ReasonText, occurred_at as OccurredAt, version from kbx.reconcile_items where tenant_id=@Tenant and id=any(@Ids) and status in ('mismatch','pending'); """, new { Tenant = tenant, Ids = ids }, cancellationToken: ct))).AsList(); foreach (var row in rows) { await projection.UpsertAsync(new UpsertWorkItem( tenant, "COMMON", "reconcile", row.Id.ToString(), row.ReferenceNo, "COMMON-REC-001", row.Version, "RECONCILE_MISMATCH", $"대사 불일치 · {row.ReferenceNo}", row.ReasonText, "warning", row.OccurredAt, Context: new { row.ReconcileType, row.ReferenceNo, row.ExpectedValue, row.ActualValue, row.DifferenceValue, row.ReasonCode }), ct); } await Send.OkAsync(new(rows.Count, ids.Length - rows.Count), ct); } private sealed record Row(Guid Id, string ReconcileType, string ReferenceNo, string ExpectedValue, string ActualValue, string? DifferenceValue, string? ReasonCode, string? ReasonText, DateTimeOffset OccurredAt, long Version); }