187 lines
5.5 KiB
Markdown
187 lines
5.5 KiB
Markdown
# 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:**
|
|
```php
|
|
public const STATUS_CALLED_OFF = 'called_off';
|
|
public const CALLED_OFF_BY_TEAMER = 'teamer';
|
|
public const CALLED_OFF_BY_OFFICE = 'office';
|
|
```
|
|
|
|
**Cancellation entry points:**
|
|
1. Individual disposition cancellation via `Administrative/Disposition/CallOffController`
|
|
- Form requires `calledOffBy` and `calledOffReason`
|
|
- Dispatches `DispositionCalledOffEvent`
|
|
|
|
2. Full assignment cancellation via `Administrative/Assignment/CallOffController`
|
|
- Sets all dispositions to `calledOffBy = 'office'`
|
|
- Dispatches `AssignmentCalledOffEvent`
|
|
|
|
### 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:**
|
|
1. Manual rejection by office (no tracking of who)
|
|
2. Automatic rejection by `RejectApplicationListener` when disposition slots fill
|
|
3. Automatic removal by `InvalidateApplicationsListener` when 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:
|
|
```php
|
|
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:
|
|
```php
|
|
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`:
|
|
```php
|
|
/**
|
|
* @return array<string, array{teamer: int, office: int}>
|
|
*/
|
|
public function getCancellationStatsByHotel(
|
|
\DateTimeImmutable $startDate,
|
|
\DateTimeImmutable $endDate
|
|
): array;
|
|
```
|
|
|
|
Add to `DispositionRepository`:
|
|
```php
|
|
/**
|
|
* @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
|
|
|
|
1. [ ] Finalize decision on automatic rejection tracking
|
|
2. [ ] Add new constants and fields to `Application` entity
|
|
3. [ ] Create database migration
|
|
4. [ ] Update `ApplicationRepository` with statistics methods
|
|
5. [ ] Update `DispositionRepository` with statistics methods
|
|
6. [ ] Update listeners if tracking automatic rejections
|
|
7. [ ] Add/update controllers for teamer withdrawal flow
|
|
8. [ ] Write tests for new repository methods
|
|
9. [ ] Run php-cs-fixer
|
|
|
|
---
|
|
|
|
## Related Files
|
|
|
|
### Entities
|
|
- `src/Entity/Application.php`
|
|
- `src/Entity/Disposition.php`
|
|
- `src/Entity/Assignment.php`
|
|
|
|
### Repositories
|
|
- `src/Repository/ApplicationRepository.php`
|
|
- `src/Repository/DispositionRepository.php`
|
|
|
|
### Controllers
|
|
- `src/Controller/Administrative/Disposition/CallOffController.php`
|
|
- `src/Controller/Administrative/Assignment/CallOffController.php`
|
|
- `src/Controller/Teamer/Disposition/DetailController.php`
|
|
|
|
### Listeners
|
|
- `src/EventListener/InvalidateApplicationsListener.php`
|
|
- `src/EventListener/RejectApplicationListener.php`
|
|
|
|
### Events
|
|
- `src/Event/DispositionCalledOffEvent.php`
|
|
- `src/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 |