feat: surface stuck booking edits to customer experts
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Dashboard\Widget;
|
||||
|
||||
use App\Dashboard\Contract\DashboardWidgetProviderInterface;
|
||||
use App\Entity\BookingEditDraft;
|
||||
use App\Model\DashboardWidget;
|
||||
use App\Model\DashboardWidgetEntry;
|
||||
use App\Repository\BookingEditDraftRepository;
|
||||
use App\Security\Role;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
/**
|
||||
* Customers whose changes cannot be saved.
|
||||
*
|
||||
* A draft is deleted as soon as an update succeeds, so one that has been around for days belongs to
|
||||
* somebody BusPro keeps refusing — and nothing else tells anyone. One booking accumulated
|
||||
* thirty-seven failed attempts across two months before it was noticed by accident; the customer
|
||||
* had entered seventy-nine participants, none of which ever reached BusPro. The point of this card
|
||||
* is that the next one gets noticed in the first week instead.
|
||||
*
|
||||
* Each entry links to the draft, since the age alone does not say what is going wrong.
|
||||
*/
|
||||
class StuckBookingDraftsWidgetProvider implements DashboardWidgetProviderInterface
|
||||
{
|
||||
/**
|
||||
* How long a draft must have survived to be worth reporting.
|
||||
*
|
||||
* Short enough to catch a customer inside their first week of trying, long enough that an edit
|
||||
* somebody merely abandoned over a weekend does not fill the card.
|
||||
*/
|
||||
private const MIN_AGE_DAYS = 7;
|
||||
|
||||
private const LIMIT = 10;
|
||||
|
||||
public function __construct(
|
||||
private readonly BookingEditDraftRepository $draftRepository,
|
||||
private readonly UrlGeneratorInterface $urlGenerator,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getRequiredRole(): string
|
||||
{
|
||||
return Role::CUSTOMER_EXPERT;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 95;
|
||||
}
|
||||
|
||||
public function build(): ?DashboardWidget
|
||||
{
|
||||
$drafts = $this->draftRepository->findStuck(self::MIN_AGE_DAYS, self::LIMIT);
|
||||
|
||||
return new DashboardWidget(
|
||||
'Festhängende Buchungsänderungen',
|
||||
array_map(fn (BookingEditDraft $draft): DashboardWidgetEntry => new DashboardWidgetEntry(
|
||||
$this->label($draft),
|
||||
$this->urlGenerator->generate('app_admin_bookingeditdraft_show', ['id' => $draft->getId()]),
|
||||
'edit',
|
||||
), $drafts),
|
||||
'Keine festhängenden Buchungsänderungen.',
|
||||
$this->urlGenerator->generate('app_admin_bookingeditdraft'),
|
||||
'Alle Buchungsentwürfe',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Names the booking, how long it has been stuck, and when the customer last tried.
|
||||
*
|
||||
* The gap between the two is what distinguishes an abandoned edit from somebody still trying
|
||||
* every few days and getting nowhere.
|
||||
*/
|
||||
private function label(BookingEditDraft $draft): string
|
||||
{
|
||||
$days = $draft->getCreatedAt()->diff(new \DateTimeImmutable())->days ?? 0;
|
||||
|
||||
return sprintf(
|
||||
'Vorgang %s (%s) — seit %d Tagen, zuletzt %s',
|
||||
$draft->getBookingNumber() ?? $draft->getBookingId(),
|
||||
(string) $draft->getUser()->getEmail(),
|
||||
$days,
|
||||
$draft->getUpdatedAt()->format('d.m.Y'),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user