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

@ -29,6 +29,7 @@ import type {
Sprint,
TerminalSession,
Ticket,
TicketBulkResult,
TicketCarnet,
TicketChat,
TicketLinkKind,
@ -297,6 +298,21 @@ export class HttpTicketGateway implements TicketGateway {
async delete(projectId: string, ref: string): Promise<void> {
await this.http.invoke<void>("ticket_delete", { request: { projectId, ref } });
}
bulkUpdateStatus(projectId: string, refs: string[], status: Ticket["status"]): Promise<TicketBulkResult> {
return this.http.invoke<TicketBulkResult>("ticket_bulk_update_status", {
request: { projectId, refs, status },
});
}
bulkUpdatePriority(projectId: string, refs: string[], priority: Ticket["priority"]): Promise<TicketBulkResult> {
return this.http.invoke<TicketBulkResult>("ticket_bulk_update_priority", {
request: { projectId, refs, priority },
});
}
bulkDelete(projectId: string, refs: string[]): Promise<TicketBulkResult> {
return this.http.invoke<TicketBulkResult>("ticket_bulk_delete", {
request: { projectId, refs },
});
}
readCarnet(projectId: string, ref: string): Promise<TicketCarnet> {
return this.http.invoke<TicketCarnet>("ticket_read_carnet", { request: { projectId, ref } });
}

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 {

View File

@ -17,6 +17,7 @@ import type {
ReplyChunk,
Sprint,
Ticket,
TicketBulkResult,
TicketCarnet,
TicketChat,
TicketLinkKind,
@ -90,6 +91,32 @@ export class TauriTicketGateway implements TicketGateway {
});
}
async bulkUpdateStatus(
projectId: string,
refs: string[],
status: Ticket["status"],
): Promise<TicketBulkResult> {
return invoke<TicketBulkResult>("ticket_bulk_update_status", {
request: { projectId, refs, status },
});
}
async bulkUpdatePriority(
projectId: string,
refs: string[],
priority: Ticket["priority"],
): Promise<TicketBulkResult> {
return invoke<TicketBulkResult>("ticket_bulk_update_priority", {
request: { projectId, refs, priority },
});
}
async bulkDelete(projectId: string, refs: string[]): Promise<TicketBulkResult> {
return invoke<TicketBulkResult>("ticket_bulk_delete", {
request: { projectId, refs },
});
}
async readCarnet(projectId: string, ref: string): Promise<TicketCarnet> {
return invoke<TicketCarnet>("ticket_read_carnet", {
request: { projectId, ref },