freescout2ant/Console/Commands/RetryFailedArchives.php
Claude 3c3a092f7a
Track failed Ameise archive attempts and add retry tooling
Previously a thread could be skipped silently in ConversationArchiver
(no customer match, ambiguous match, API/network error, attachment
failure) without any record. Default-off logging hid the cause, and the
queue job swallowed exceptions, so messages "archived" in FreeScout
never surfaced as missing in Ameise.

Now every archive attempt records a row in the new crm_archive_attempts
table with status, sanitized HTTP response, attempt counter and
resolution timestamp. ArchiveThreadsJob retries transient failures up to
5 times with exponential backoff; ameise:list-failed-archives and
ameise:retry-failed-archives expose the queue from the CLI; the Ameise
settings page lists open failures with retry/dismiss buttons.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VU73s4KM5JZqVWQYPpK3cP
2026-06-16 23:06:49 +00:00

77 lines
2.6 KiB
PHP

<?php
namespace Modules\AmeiseModule\Console\Commands;
use App\Conversation;
use App\Thread;
use App\User;
use Illuminate\Console\Command;
use Modules\AmeiseModule\Entities\CrmArchiveAttempt;
use Modules\AmeiseModule\Jobs\ArchiveThreadsJob;
class RetryFailedArchives extends Command
{
protected $signature = 'ameise:retry-failed-archives
{--id=* : crm_archive_attempts.id, mehrfach erlaubt}
{--conversation=* : conversation_id, mehrfach erlaubt}
{--all : Alle offenen Fehlversuche erneut anstossen}';
protected $description = 'Setzt fehlgeschlagene Archivierungen erneut in die Queue (ArchiveThreadsJob)';
public function handle()
{
$ids = $this->option('id');
$conversationIds = $this->option('conversation');
$all = (bool) $this->option('all');
if (!$ids && !$conversationIds && !$all) {
$this->error('Bitte --id, --conversation oder --all angeben.');
return 1;
}
$query = CrmArchiveAttempt::whereNull('resolved_at')
->whereIn('status', CrmArchiveAttempt::FAILURE_STATUSES);
if ($ids) {
$query->whereIn('id', $ids);
} elseif ($conversationIds) {
$query->whereIn('conversation_id', $conversationIds);
}
$attempts = $query->get();
if ($attempts->isEmpty()) {
$this->info('Keine passenden Fehlversuche gefunden.');
return 0;
}
$dispatched = 0;
$seen = [];
foreach ($attempts as $attempt) {
$key = $attempt->thread_id . '-' . $attempt->user_id;
if (isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$thread = Thread::find($attempt->thread_id);
$conversation = Conversation::find($attempt->conversation_id);
$user = User::find($attempt->user_id);
if (!$thread || !$conversation || !$user) {
$this->warn(sprintf('Skip attempt %d: thread/conversation/user fehlt', $attempt->id));
continue;
}
ArchiveThreadsJob::dispatch($conversation, $thread, $user);
CrmArchiveAttempt::record([
'conversation_id' => $conversation->id,
'thread_id' => $thread->id,
'user_id' => $user->id,
'status' => CrmArchiveAttempt::STATUS_PENDING,
'reason' => 'Manuell neu eingereiht via CLI',
]);
$dispatched++;
}
$this->info(sprintf('%d Job(s) erneut eingereiht.', $dispatched));
return 0;
}
}