feat(iios): idempotent support ticket + callback creates via ledger (P9)

SupportService.createTicket + requestCallback now accept an optional
idempotencyKey (from the Idempotency-Key header). With a key, a same-body
retry replays the same row and a different-body reuse is a 409; without one,
they run unwrapped as before. Tenant-scoped via the command ledger.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 02:22:19 +05:30
parent b8b7e0d3e8
commit 093f6484b4
4 changed files with 128 additions and 71 deletions
@@ -22,8 +22,12 @@ export class SupportController {
) {}
@Post('tickets')
async createTicket(@Body() body: CreateTicketDto, @Headers('authorization') auth?: string) {
return this.support.createTicket(this.principal(auth), body);
async createTicket(
@Body() body: CreateTicketDto,
@Headers('idempotency-key') idempotencyKey?: string,
@Headers('authorization') auth?: string,
) {
return this.support.createTicket(this.principal(auth), body, idempotencyKey);
}
@Post('escalate')
@@ -42,13 +46,21 @@ export class SupportController {
}
@Post('callbacks')
async callback(@Body() body: CallbackDto, @Headers('authorization') auth?: string) {
return this.support.requestCallback(this.principal(auth), {
preferChannel: body.preferChannel,
preferredTime: body.preferredTime ? new Date(body.preferredTime) : undefined,
notes: body.notes,
ticketId: body.ticketId,
});
async callback(
@Body() body: CallbackDto,
@Headers('idempotency-key') idempotencyKey?: string,
@Headers('authorization') auth?: string,
) {
return this.support.requestCallback(
this.principal(auth),
{
preferChannel: body.preferChannel,
preferredTime: body.preferredTime ? new Date(body.preferredTime) : undefined,
notes: body.notes,
ticketId: body.ticketId,
},
idempotencyKey,
);
}
// ─── admin / agent ────────────────────────────────────────────────