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:
$activeWindowManager- Service for temporal assignment management$warrantManager- Service for warrant lifecycle coordination
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:
success === trueon successful assignmentsuccess === falsewithreasoncontaining error message on failure
Assignment Workflow
- Office Validation
- Retrieves office configuration
- Validates warrant requirements against member warrantable status
- End Date Calculation
- If
$endOnis null and office hasterm_length > 0, calculates end date - If
$endOnis null andterm_length == 0, assignment has no end date
- If
- Status Determination
UPCOMING_STATUS- Start date is in the futureCURRENT_STATUS- Start date is today or in the pastEXPIRED_STATUS- End date is in the past
- Reporting Relationship Calculation
- Calculates
reports_to_office_idandreports_to_branch_id - Calculates
deputy_to_office_idanddeputy_to_branch_idfor deputy positions - Navigates branch hierarchy for proper reporting chain
- Calculates
- Current Officer Replacement
- If office has
only_one_per_branch = true, releases existing current officers - Uses
REPLACED_STATUSfor released officers
- If office has
- ActiveWindow Integration
- Starts temporal lifecycle tracking
- Manages automatic status transitions
- Warrant Processing
- For offices with
requires_warrant = true, creates warrant request - Coordinates with WarrantManager for role assignment
- For offices with
- 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:
success === trueon successful releasesuccess === falsewithreasoncontaining error message on failure
Release Workflow
- ActiveWindow Termination
- Stops the temporal lifecycle through ActiveWindowManager
- Updates officer status to release status
- Warrant Cancellation
- For officers in warrant-required offices, cancels associated warrants
- Coordinates role revocation through WarrantManager
- 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:
success === trueon successful recalculationsuccess === falsewithreasoncontaining error message on failuredataarray containing:updated_count- Total officers updatedcurrent_count- Current status officers updatedupcoming_count- Upcoming status officers updated
When to Use
This method should be called when office configuration changes affect:
deputy_to_id- Deputy relationship changesreports_to_id- Reporting structure changesgrants_role_id- Role assignment changes
Recalculation Workflow
- Officer Discovery
- Finds all
CURRENT_STATUSofficers for the office - Finds all
UPCOMING_STATUSofficers for the office
- Finds all
- Reporting Relationship Updates
- Recalculates
reports_to_office_idandreports_to_branch_id - Recalculates
deputy_to_office_idanddeputy_to_branch_id
- Recalculates
- 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
- 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:
reports_to_office_idis set from office configurationreports_to_branch_idis 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
- If
Deputy Reporting
For deputy positions (deputy_to_id is set):
deputy_to_office_idanddeputy_to_branch_idare set to the office they’re deputy to within the same branchreports_to_office_idandreports_to_branch_idmirror the deputy-to relationship
Branch Compatibility Checking
The _calculateOfficerReportingFields() private method uses the OfficesTable::findCompatibleBranchForOffice() method to ensure:
- The reports-to office can actually exist in the reports-to branch
- Branch type compatibility is respected
- Organizational structure integrity is maintained
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}” |