5 lines
3.2 KiB
C#
5 lines
3.2 KiB
C#
using System.Text.Json;using Dapper;using FastEndpoints;using Npgsql;using Shared.Problems;
|
|
namespace Modules.OMS.Orders.Confirm;
|
|
public sealed record Request(long Version);public sealed record Response(Guid OrderId,long Version,string Status);
|
|
public sealed class Endpoint(NpgsqlDataSource dataSource):Endpoint<Request,Response>{public override void Configure(){Post("/api/oms/orders/{id}/confirm");Permissions("oms.order.confirm");}public override async Task HandleAsync(Request req,CancellationToken ct){var id=Route<Guid>("id");var key=HttpContext.Request.Headers["Idempotency-Key"].FirstOrDefault();if(string.IsNullOrWhiteSpace(key)){await Send.ResponseAsync(KbxValidationProblem.Create(new(null,null,"IDEMPOTENCY_KEY_REQUIRED","안전한 재처리를 위해 Idempotency-Key가 필요합니다.")),400,cancellation:ct);return;}await using var c=await dataSource.OpenConnectionAsync(ct);await using var tx=await c.BeginTransactionAsync(ct);var replay=await c.QuerySingleOrDefaultAsync<string?>(new CommandDefinition("select response_json::text from kbx.command_receipts where operation_id=@OperationId and idempotency_key=@Key",new{OperationId="oms.orders.confirm",Key=key},tx,cancellationToken:ct));if(replay is not null){await tx.RollbackAsync(ct);await Send.OkAsync(JsonSerializer.Deserialize<Response>(replay)!,ct);return;}var current=await c.QuerySingleOrDefaultAsync<(long Version,string Status)>(new CommandDefinition("select version as Version,status as Status from oms.orders where id=@Id for update",new{Id=id},tx,cancellationToken:ct));if(current.Status is null){await Send.ResponseAsync(KbxNotFoundProblem.Create("ORDER_NOT_FOUND","주문을 찾을 수 없습니다."),404,cancellation:ct);return;}if(current.Version!=req.Version){await Send.ResponseAsync(KbxConflictProblem.Version(current.Version),409,cancellation:ct);return;}if(current.Status!="DRAFT"){await Send.ResponseAsync(KbxBusinessProblem.Create("ORDER_NOT_CONFIRMABLE","주문을 확정할 수 없습니다.","작성 상태의 주문만 확정할 수 있습니다."),409,cancellation:ct);return;}var next=current.Version+1;var actor=User.Identity?.Name??"unknown";await c.ExecuteAsync(new CommandDefinition(@"update oms.orders set status='CONFIRMED',version=@Version,updated_at=now(),updated_by=@Actor where id=@Id;insert into audit.entries(id,aggregate_type,aggregate_id,action,actor,occurred_at,data) values(@AuditId,'Order',@Id,'ORDER_CONFIRMED',@Actor,now(),jsonb_build_object('version',@Version,'changes',jsonb_build_array(jsonb_build_object('field','status','label','상태','before','작성','after','확정'))));insert into integration.outbox(id,event_type,aggregate_id,payload,occurred_at,status) values(@EventId,'OrderConfirmed',@Id,jsonb_build_object('orderId',@Id,'version',@Version),now(),'PENDING');",new{Id=id,Version=next,Actor=actor,AuditId=Guid.NewGuid(),EventId=Guid.NewGuid()},tx,cancellationToken:ct));var response=new Response(id,next,"확정");await c.ExecuteAsync(new CommandDefinition("insert into kbx.command_receipts(operation_id,idempotency_key,response_json) values(@OperationId,@Key,cast(@ResponseJson as jsonb))",new{OperationId="oms.orders.confirm",Key=key,ResponseJson=JsonSerializer.Serialize(response)},tx,cancellationToken:ct));await tx.CommitAsync(ct);await Send.OkAsync(response,ct);}}
|