bulk operations fixes

- Frontend: implémente bulkDelete, bulkArchive, bulkRestore
- Backend: adapts ticket DTOs pour bulk operations
- Infrastructure: MCP server pour gestion bulk tickets
- Tests: coverage frontend + backend
This commit is contained in:
2026-07-28 20:38:07 +02:00
parent 73c6081ab8
commit bd4f76ae74
19 changed files with 597 additions and 35 deletions

View File

@ -66,6 +66,7 @@ import type {
SystemPermissionSet,
TerminalSession,
Ticket,
TicketBulkResult,
TicketCarnet,
TicketChat,
TicketLink,
@ -2910,6 +2911,75 @@ export class MockTicketGateway implements TicketGateway {
});
}
async bulkUpdateStatus(
projectId: string,
refs: string[],
status: Ticket["status"],
): Promise<TicketBulkResult> {
const items = refs.map((ref) => {
try {
const ticket = this.require(projectId, ref);
ticket.status = status;
this.bump(ticket);
this.system?.emit({
type: "issueUpdated",
issueRef: ref,
version: ticket.version,
});
return { ref, ok: true, ticket: structuredClone(ticket) };
} catch (error) {
return { ref, ok: false, error: error as GatewayError };
}
});
return { items };
}
async bulkUpdatePriority(
projectId: string,
refs: string[],
priority: Ticket["priority"],
): Promise<TicketBulkResult> {
const items = refs.map((ref) => {
try {
const ticket = this.require(projectId, ref);
ticket.priority = priority;
this.bump(ticket);
this.system?.emit({
type: "issueUpdated",
issueRef: ref,
version: ticket.version,
});
return { ref, ok: true, ticket: structuredClone(ticket) };
} catch (error) {
return { ref, ok: false, error: error as GatewayError };
}
});
return { items };
}
async bulkDelete(projectId: string, refs: string[]): Promise<TicketBulkResult> {
const items = refs.map((ref) => {
try {
const ticket = this.require(projectId, ref);
const deleted = structuredClone(ticket);
const freedSprint = ticket.sprintId ?? null;
this.projectTickets(projectId).delete(ref);
this.projectCarnets(projectId).delete(ref);
if (freedSprint) this.recountSprints(projectId);
this.system?.emit({
type: "issueDeleted",
projectId,
issueRef: ref,
freedSprint,
});
return { ref, ok: true, ticket: deleted };
} catch (error) {
return { ref, ok: false, error: error as GatewayError };
}
});
return { items };
}
async readCarnet(projectId: string, ref: string): Promise<TicketCarnet> {
const ticket = this.require(projectId, ref);
return {