using Dapper; using FastEndpoints; using Npgsql; using Shared.Operations; namespace Modules.Common.Operations.Resolve; public sealed record ResolveWorkItemsRequest(IReadOnlyList Ids, string Reason); public sealed record ResolveWorkItemsResponse(int ResolvedCount, int RejectedCount); public sealed class Endpoint(NpgsqlDataSource dataSource) : Endpoint { public override void Configure() { Post("/api/operations/work-items/resolve"); Permissions("common.operations.resolve"); } public override async Task HandleAsync(ResolveWorkItemsRequest req, CancellationToken ct) { var ids = req.Ids.Distinct().Take(500).ToArray(); var tenant = OperationsIdentity.TenantId(User); var actorId = OperationsIdentity.UserId(User); var actorName = OperationsIdentity.UserName(User); await using var connection = await dataSource.OpenConnectionAsync(ct); await using var tx = await connection.BeginTransactionAsync(ct); // Manual resolution is opt-in. Domain-originated exceptions should normally be resolved // by source-module events through OperationsProjectionWriter.ResolveBySourceAsync(). var changed = (await connection.QueryAsync(new CommandDefinition(""" update kbx.work_items set status='resolved', resolution_reason=@Reason, resolved_at=now(), version=version+1, updated_at=now() where tenant_id=@Tenant and id=any(@Ids) and allow_manual_resolution=true and status in ('open','claimed') returning id; """, new { Tenant = tenant, Ids = ids, Reason = req.Reason }, tx, cancellationToken: ct))).AsList(); foreach (var id in changed) await connection.ExecuteAsync(new CommandDefinition(""" insert into kbx.work_item_audit(id, work_item_id, tenant_id, action, actor_id, actor_name, reason, after_status) values(@Id, @WorkItemId, @Tenant, 'manual-resolve', @ActorId, @ActorName, @Reason, 'resolved'); """, new { Id = Guid.NewGuid(), WorkItemId = id, Tenant = tenant, ActorId = actorId, ActorName = actorName, Reason = req.Reason }, tx, cancellationToken: ct)); await tx.CommitAsync(ct); await Send.OkAsync(new(changed.Count, ids.Length - changed.Count), ct); } }