Files
myep-team/docs/PLAN-cancellation-statistics.md
T

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:

  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:

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

  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

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