fix(worker): fix Bandel ticket sync and reactivate the integration

The status PATCH needs statusName alongside statusId or the API 400s,
so every write-back had been silently failing. The board's flat
assigneeName is null on every live ticket; the real assignee list is
ticketAssignees[]. Import now also picks up In Bearbeitung tickets,
not just Offen ones.

Both issues are live-verified against the real API, so the feature
flag flips back on.
This commit is contained in:
mika kuns
2026-09-02 09:59:06 +02:00
parent f640580463
commit 30d0611b8e
10 changed files with 185 additions and 64 deletions
@@ -16,7 +16,9 @@ public sealed record TicketImportResult(int Examined, int Created);
/// </summary>
public sealed class TicketImportService
{
private const int StatusOffen = 1;
// Offen UND In Bearbeitung: ein Ticket, an dem schon gearbeitet wird, ist genauso ein
// Arbeitsauftrag wie ein neues. Fertig/Archiviert bleiben draußen.
private static readonly int[] ImportableStatuses = [1, TicketStatusMap.InBearbeitung];
private readonly TicketClientFactory _clients;
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
@@ -48,8 +50,8 @@ public sealed class TicketImportService
var board = await client.GetProjectBoardAsync(projectId, ct);
var mine = board
.Where(t => t.StatusId == StatusOffen)
.Where(t => string.Equals(t.AssigneeName, identity.UserName, StringComparison.OrdinalIgnoreCase))
.Where(t => ImportableStatuses.Contains(t.StatusId))
.Where(t => IsAssignedTo(t, identity.UserName))
.ToList();
var known = await ctx.Tasks.AsNoTracking()
@@ -87,6 +89,16 @@ public sealed class TicketImportService
return new TicketImportResult(board.Count, created);
}
/// <summary>
/// Zuweisung ausschließlich über <c>ticketAssignees</c> — das flache <c>assigneeName</c> ist
/// live auf jedem Ticket null (siehe <see cref="TicketBoardItem"/>). Ein Ticket kann mehrere
/// Zuweisungen haben; eine davon muss der Token-Inhaber sein.
/// </summary>
private static bool IsAssignedTo(TicketBoardItem ticket, string userName)
=> ticket.TicketAssignees?.Any(
a => string.Equals(a.AssigneeName, userName, StringComparison.OrdinalIgnoreCase))
?? false;
private static string BuildDescription(TicketBoardItem ticket)
{
var body = string.IsNullOrWhiteSpace(ticket.Description) ? "" : ticket.Description.Trim() + "\n\n";