Skip to the content.

← Back to Officers Plugin

5.1.1 Officers Plugin Services

Last Updated: December 4, 2025
Status: Complete
Plugin: Officers

This document provides comprehensive documentation for the Officers plugin service layer, including the OfficerManagerInterface and its default implementation DefaultOfficerManager.

Service Architecture Overview

The Officers plugin uses a service-oriented architecture with dependency injection for managing officer lifecycle operations. The primary service interface is OfficerManagerInterface, which is implemented by DefaultOfficerManager.

Service Dependencies

Dependency Purpose
ActiveWindowManagerInterface Temporal assignment management and status transitions
WarrantManagerInterface Warrant lifecycle coordination and role assignment
QueuedMailerAwareTrait Asynchronous notification processing

OfficerManagerInterface

The OfficerManagerInterface defines the contract for officer lifecycle management operations.

Interface Methods

interface OfficerManagerInterface
{
    public function assign(
        int $officeId,
        int $memberId,
        int $branchId,
        DateTime $startOn,
        ?DateTime $endOn,
        ?string $deputyDescription,
        int $approverId,
        ?string $emailAddress
    ): ServiceResult;

    public function release(
        int $officerId,
        int $revokerId,
        DateTime $revokedOn,
        ?string $revokedReason
    ): ServiceResult;

    public function recalculateOfficersForOffice(
        int $officeId,
        int $updaterId
    ): ServiceResult;
}

DefaultOfficerManager

The DefaultOfficerManager is the standard implementation providing comprehensive officer lifecycle management.

Constructor

public function __construct(
    ActiveWindowManagerInterface $activeWindowManager,
    WarrantManagerInterface $warrantManager
)

Parameters:


Assignment Operations

assign()

Assigns a member to an office within a branch, establishing all necessary relationships and triggering appropriate workflows.

Method Signature

public function assign(
    int $officeId,
    int $memberId,
    int $branchId,
    DateTime $startOn,
    ?DateTime $endOn,
    ?string $deputyDescription,
    int $approverId,
    ?string $emailAddress
): ServiceResult

Parameters

Parameter Type Description
$officeId int Office identifier for the assignment target
$memberId int Member identifier being assigned
$branchId int Branch identifier for organizational context
$startOn DateTime Assignment start date
$endOn DateTime|null Optional end date; derived from office term length if null
$deputyDescription string|null Optional description for deputy assignments
$approverId int Identifier of the approving administrator
$emailAddress string|null Optional email address for the officer record

Return Value

Returns a ServiceResult with:

Assignment Workflow

  1. Office Validation
    • Retrieves office configuration
    • Validates warrant requirements against member warrantable status
  2. End Date Calculation
    • If $endOn is null and office has term_length > 0, calculates end date
    • If $endOn is null and term_length == 0, assignment has no end date
  3. Status Determination
    • UPCOMING_STATUS - Start date is in the future
    • CURRENT_STATUS - Start date is today or in the past
    • EXPIRED_STATUS - End date is in the past
  4. Reporting Relationship Calculation
    • Calculates reports_to_office_id and reports_to_branch_id
    • Calculates deputy_to_office_id and deputy_to_branch_id for deputy positions
    • Navigates branch hierarchy for proper reporting chain
  5. Current Officer Replacement
    • If office has only_one_per_branch = true, releases existing current officers
    • Uses REPLACED_STATUS for released officers
  6. ActiveWindow Integration
    • Starts temporal lifecycle tracking
    • Manages automatic status transitions
  7. Warrant Processing
    • For offices with requires_warrant = true, creates warrant request
    • Coordinates with WarrantManager for role assignment
  8. Notification
    • Queues hire notification email to the assigned member

Example Usage

$officerManager = $this->getOfficerManager();

$result = $officerManager->assign(
    officeId: 5,
    memberId: 123,
    branchId: 10,
    startOn: DateTime::now(),
    endOn: null, // Will use office term_length
    deputyDescription: null,
    approverId: 1,
    emailAddress: 'officer@example.com'
);

if (!$result->success) {
    $this->Flash->error($result->reason);
}

Release Operations

release()

Releases an officer from their position, handling all cleanup operations including warrant cancellation and notifications.

Method Signature

public function release(
    int $officerId,
    int $revokerId,
    DateTime $revokedOn,
    ?string $revokedReason
): ServiceResult

Parameters

Parameter Type Description
$officerId int Officer record identifier
$revokerId int Identifier of the administrator performing release
$revokedOn DateTime Effective release date
$revokedReason string|null Optional reason for release

Return Value

Returns a ServiceResult with:

Release Workflow

  1. ActiveWindow Termination
    • Stops the temporal lifecycle through ActiveWindowManager
    • Updates officer status to release status
  2. Warrant Cancellation
    • For officers in warrant-required offices, cancels associated warrants
    • Coordinates role revocation through WarrantManager
  3. Notification
    • Queues release notification email to the released member

Release Status Values

Status Constant Description
Released Officer::RELEASED_STATUS Standard voluntary/administrative release
Replaced Officer::REPLACED_STATUS Released due to replacement by new officer
Expired Officer::EXPIRED_STATUS Term ended naturally

Example Usage

$result = $officerManager->release(
    officerId: 456,
    revokerId: 1,
    revokedOn: DateTime::now(),
    revokedReason: 'Resigned from position'
);

if (!$result->success) {
    $this->Flash->error($result->reason);
}

Recalculation Operations

recalculateOfficersForOffice()

Recalculates reporting relationships and role assignments for all current and upcoming officers when office configuration changes.

Method Signature

public function recalculateOfficersForOffice(
    int $officeId,
    int $updaterId
): ServiceResult

Parameters

Parameter Type Description
$officeId int Office identifier for officers requiring recalculation
$updaterId int Identifier of the administrator triggering recalculation

Return Value

Returns a ServiceResult with:

When to Use

This method should be called when office configuration changes affect:

Recalculation Workflow

  1. Officer Discovery
    • Finds all CURRENT_STATUS officers for the office
    • Finds all UPCOMING_STATUS officers for the office
  2. Reporting Relationship Updates
    • Recalculates reports_to_office_id and reports_to_branch_id
    • Recalculates deputy_to_office_id and deputy_to_branch_id
  3. Role Synchronization
    • If office no longer grants a role: ends existing officer roles
    • If office role changed: ends old role, creates new role
    • If office now grants a role: creates role for officers without one
  4. Error Handling
    • Fail-fast approach: stops on first error
    • Returns detailed error message identifying member, office, and branch

Example Usage

// After updating office configuration
$result = $officerManager->recalculateOfficersForOffice(
    officeId: 5,
    updaterId: 1
);

if ($result->success) {
    $this->Flash->success(sprintf(
        'Updated %d officers (%d current, %d upcoming)',
        $result->data['updated_count'],
        $result->data['current_count'],
        $result->data['upcoming_count']
    ));
} else {
    $this->Flash->error($result->reason);
}

Reporting Relationship Logic

Branch Hierarchy Navigation

The service implements sophisticated branch hierarchy navigation for determining reporting relationships:

Standard Reporting (Non-Deputy)

For officers that are not deputies:

  1. reports_to_office_id is set from office configuration
  2. reports_to_branch_id is determined by:
    • If can_skip_report = false: Finds the nearest compatible parent branch
    • If can_skip_report = true: Searches up hierarchy for a branch where the reporting office is actually filled

Deputy Reporting

For deputy positions (deputy_to_id is set):

  1. deputy_to_office_id and deputy_to_branch_id are set to the office they’re deputy to within the same branch
  2. reports_to_office_id and reports_to_branch_id mirror the deputy-to relationship

Branch Compatibility Checking

The _calculateOfficerReportingFields() private method uses the OfficesTable::findCompatibleBranchForOffice() method to ensure:


Integration with Other Services

ActiveWindowManager Integration

The service integrates with ActiveWindowManagerInterface for:

Operation Purpose
start() Begins temporal lifecycle tracking for new assignments
stop() Ends temporal lifecycle for released officers and role changes

WarrantManager Integration

The service integrates with WarrantManagerInterface for:

Operation Purpose
request() Creates warrant requests for warrant-required offices
cancelByEntity() Cancels warrants when officers are released

Notification System

The service uses QueuedMailerAwareTrait for:

Mailer Purpose
notifyOfHire Sends assignment notification to new officers
notifyOfRelease Sends release notification to released officers

Error Handling

ServiceResult Pattern

All methods return ServiceResult objects following the KMP service pattern:

// Success
return new ServiceResult(true);

// Success with data
return new ServiceResult(true, null, ['key' => 'value']);

// Failure
return new ServiceResult(false, "Error message describing the failure");

Common Error Scenarios

Scenario Error Message Pattern
Non-warrantable member for warrant-required office “Member is not warrantable”
ActiveWindow start failure Forwards error from ActiveWindowManager
Officer save failure “Failed to save officer”
Warrant request failure Forwards error from WarrantManager
Role creation failure “Failed to create role for {member} ({office} at {branch})”
Role termination failure “Failed to end role for {member} ({office} at {branch}): {reason}”

References