feat: per-disposition feedback waiver

The overdue-feedback lists are a live query without stored state, so a
feedback nobody will provide any more could not be written off. The new
disposition.feedback_waived flag takes a placement out of the admin and
manager dashboards, the house-manager list and the reminder mail.

Statistics still count a waived placement as a missing feedback. The
flag has no UI yet and is set by SQL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
frommeandClaude Opus 5.5 committed 2026-10-13 13:53:25 +02:00
1 parent c04d9e0faf
commit 7e182e3d51
4 files changed
+74

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Adds the per-placement feedback waiver.
*
* The overdue-feedback lists are a live query without any stored state, so there was no way to
* write off a feedback nobody will provide any more. The flag takes a placement out of the
* dashboards' overdue lists, the house-manager list and the reminder mail.
*
* Written by hand rather than taken from doctrine:migrations:diff, which also carries the
* pre-existing drift of the dev database.
*/
final class Version20261013120000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add the feedback_waived flag to dispositions';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE disposition ADD feedback_waived TINYINT DEFAULT 0 NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE disposition DROP feedback_waived');
}
}
+20
View File
@@ -72,6 +72,14 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
#[ORM\Column(type: Types::BOOLEAN)]
private bool $syncedWithBusProNet = false;
/**
* The office no longer expects a feedback for this placement, so nothing chases one: not the
* dashboards' overdue lists, not the house-manager list, not the reminder mail. It still
* counts as missing in the statistics.
*/
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
private bool $feedbackWaived = false;
public function __construct(Application $application)
{
$this->uuid = Uuid::v4();
@@ -353,4 +361,16 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
return $this;
}
public function isFeedbackWaived(): bool
{
return $this->feedbackWaived;
}
public function setFeedbackWaived(bool $feedbackWaived): static
{
$this->feedbackWaived = $feedbackWaived;
return $this;
}
}
+3
View File
@@ -225,9 +225,12 @@ class DispositionRepository extends ServiceEntityRepository
$qb->expr()->notIn('assignment.status', ':status'),
$qb->expr()->lte('destination.dateTo', ':dateTo'),
$qb->expr()->isNull('feedback'),
// waived placements are written off: nobody is asked for their feedback any more
$qb->expr()->eq('disposition.feedbackWaived', ':feedbackWaived'),
$this->excludesSkipFormalities($qb)
))
->setParameter('status', [Assignment::STATUS_CALLED_OFF, Assignment::STATUS_DELETED])
->setParameter('feedbackWaived', false)
;
$dateTo = new \DateTimeImmutable();
@@ -212,6 +212,21 @@ class DispositionRepositoryTest extends KernelTestCase
$this->assertFalse($query->getParameter('skipFormalities')->getValue());
}
/**
* A waived placement is written off by the office. The same query backs every surface that
* chases a feedback, so this is the one place the waiver has to be honoured.
*/
public function testPendingFeedbackSkipsWaivedDispositions(): void
{
$query = $this->repository()->getDispositionsWithPendingFeedbackQuery();
$this->assertStringContainsString(
'disposition.feedbackWaived = :feedbackWaived',
$query->getDQL()
);
$this->assertFalse($query->getParameter('feedbackWaived')->getValue());
}
/**
* Otherwise placements nobody is expected to rate would be counted as missingFeedbacks and
* drag every hotel's percentage down.