5.5 KiB
Plan: Cancellation Statistics Feature
Objective
Enable stakeholders to see how often dispositions and applications have been canceled by either the teamer or the office, per hotel and for a given date range.
Current State Analysis
Dispositions - Already Tracked
The Disposition entity has comprehensive cancellation tracking:
| Field | Type | Purpose |
|---|---|---|
status |
string | 'called_off' when cancelled |
calledOffBy |
string (nullable) | 'teamer' or 'office' |
calledOffReason |
text (nullable) | Reason for cancellation |
updatedAt |
datetime | Timestamp of last change |
Constants:
public const STATUS_CALLED_OFF = 'called_off';
public const CALLED_OFF_BY_TEAMER = 'teamer';
public const CALLED_OFF_BY_OFFICE = 'office';
Cancellation entry points:
-
Individual disposition cancellation via
Administrative/Disposition/CallOffController- Form requires
calledOffByandcalledOffReason - Dispatches
DispositionCalledOffEvent
- Form requires
-
Full assignment cancellation via
Administrative/Assignment/CallOffController- Sets all dispositions to
calledOffBy = 'office' - Dispatches
AssignmentCalledOffEvent
- Sets all dispositions to
Applications - Missing Tracking
The Application entity lacks cancellation tracking:
| Current Field | Issue |
|---|---|
status |
Only 'rejected' - no distinction between teamer withdrawal and office rejection |
| - | No cancelledBy field |
| - | No cancellationReason field |
Current rejection mechanisms:
- Manual rejection by office (no tracking of who)
- Automatic rejection by
RejectApplicationListenerwhen disposition slots fill - Automatic removal by
InvalidateApplicationsListenerwhen teamer has overlapping confirmed dispositions
Pending Decision
Question for stakeholders: Should automatic rejections be included in statistics?
| Option | Description | Impact |
|---|---|---|
| Only user-initiated | Count only manual cancellations by teamer or office | Simpler model, clearer accountability |
| Include automatic | Also track system-initiated rejections | Full picture of lost applications, requires 'system' category |
Status: Awaiting stakeholder response
Proposed Changes
Application Entity
Option A: If only user-initiated cancellations count
Add new status and fields:
public const STATUS_WITHDRAWN = 'withdrawn'; // teamer-initiated
public const STATUS_REJECTED = 'rejected'; // office-initiated (existing)
private ?string $cancellationReason = null;
Option B: If automatic rejections should be tracked
Add new status, fields, and constant:
public const STATUS_WITHDRAWN = 'withdrawn'; // teamer-initiated
public const STATUS_REJECTED = 'rejected'; // office or system initiated
public const CANCELLED_BY_TEAMER = 'teamer';
public const CANCELLED_BY_OFFICE = 'office';
public const CANCELLED_BY_SYSTEM = 'system';
private ?string $cancelledBy = null;
private ?string $cancellationReason = null;
Database Migration
Add columns to application table:
cancellation_reason(LONGTEXT, nullable)- Possibly
cancelled_by(VARCHAR(64), nullable) if Option B
Repository Methods
Add to ApplicationRepository:
/**
* @return array<string, array{teamer: int, office: int}>
*/
public function getCancellationStatsByHotel(
\DateTimeImmutable $startDate,
\DateTimeImmutable $endDate
): array;
Add to DispositionRepository:
/**
* @return array<string, array{teamer: int, office: int}>
*/
public function getCancellationStatsByHotel(
\DateTimeImmutable $startDate,
\DateTimeImmutable $endDate
): array;
Date filtering: Based on assignment travel dates (confirmed in discussion).
Update Listeners (if Option B)
Modify RejectApplicationListener and InvalidateApplicationsListener to set cancelledBy = 'system'.
UI Changes
Add withdrawal functionality for teamers (if not already present) that sets STATUS_WITHDRAWN.
Implementation Steps
- Finalize decision on automatic rejection tracking
- Add new constants and fields to
Applicationentity - Create database migration
- Update
ApplicationRepositorywith statistics methods - Update
DispositionRepositorywith statistics methods - Update listeners if tracking automatic rejections
- Add/update controllers for teamer withdrawal flow
- Write tests for new repository methods
- Run php-cs-fixer
Related Files
Entities
src/Entity/Application.phpsrc/Entity/Disposition.phpsrc/Entity/Assignment.php
Repositories
src/Repository/ApplicationRepository.phpsrc/Repository/DispositionRepository.php
Controllers
src/Controller/Administrative/Disposition/CallOffController.phpsrc/Controller/Administrative/Assignment/CallOffController.phpsrc/Controller/Teamer/Disposition/DetailController.php
Listeners
src/EventListener/InvalidateApplicationsListener.phpsrc/EventListener/RejectApplicationListener.php
Events
src/Event/DispositionCalledOffEvent.phpsrc/Event/AssignmentCalledOffEvent.php
Forms
src/Form/DispositionCallOffType.php
Notes
- Filter statistics by assignment travel dates, not cancellation timestamp
- Disposition tracking already works - only need repository query methods
- Application tracking requires entity changes and migration
- Consider whether teamer self-service withdrawal exists or needs to be added