@@ -112,19 +112,33 @@ private function withEmployeeLock(string $employeeUid, callable $fn): mixed {
112112 }
113113
114114 /**
115- * Validate the nominated replacement for a leave type that requires one (§5.1).
116- * Returns the (trimmed) replacement uid, or null when not required .
115+ * Validate the nominated replacement (§5.1). Returns the trimmed uid, or null
116+ * when none was given and none is demanded .
117117 *
118+ * @param bool $required whether an absent replacement is an error for a type that
119+ * demands one. True on the self-service paths; false when HR
120+ * records or corrects somebody else's absence.
118121 * @throws ValidationException
119122 */
120- private function resolveReplacement (string $ employeeUid , LeaveType $ type , ?string $ replacementUid ): ?string {
121- if (!$ type ->getRequiresReplacement ()) {
122- return $ replacementUid !== null && trim ($ replacementUid ) !== '' ? trim ($ replacementUid ) : null ;
123- }
124- $ replacementUid = trim ((string )$ replacementUid );
123+ private function resolveReplacement (string $ employeeUid , LeaveType $ type , ?string $ replacementUid , bool $ required = true ): ?string {
124+ $ replacementUid = $ replacementUid !== null ? trim ($ replacementUid ) : '' ;
125+
125126 if ($ replacementUid === '' ) {
126- throw new ValidationException ('Please choose a replacement for this leave. ' );
127+ // The mandatory-replacement rule is about somebody applying for their own
128+ // leave: they know who can cover and are asked to arrange it before going.
129+ // HR recording or correcting an absence for somebody else is stating a fact,
130+ // often after the event, and cannot be expected to nominate cover on their
131+ // behalf — so there the field is offered but never demanded.
132+ if ($ required && $ type ->getRequiresReplacement ()) {
133+ throw new ValidationException ('Please choose a replacement for this leave. ' );
134+ }
135+ return null ;
127136 }
137+
138+ // Validated whether or not the type demands a replacement. Only the *demand*
139+ // is conditional; who may be named is not, and skipping these when the type
140+ // happened not to require one let a guest — or the employee themselves — be
141+ // recorded as covering.
128142 if ($ replacementUid === $ employeeUid ) {
129143 throw new ValidationException ('You cannot be your own replacement. ' );
130144 }
@@ -289,7 +303,8 @@ public function create(string $actorUid, array $data): LeaveRequest {
289303 $ start = $ this ->normaliseDate ((string )($ data ['startDate ' ] ?? '' ));
290304 $ end = $ this ->normaliseDate ((string )($ data ['endDate ' ] ?? '' ));
291305 $ this ->validateRange ($ actorUid , $ start , $ end , $ type , (string )($ data ['reason ' ] ?? '' ), (string )($ data ['attachmentNote ' ] ?? '' ));
292- $ replacementUid = $ this ->resolveReplacement ($ employeeUid , $ type , $ data ['replacementUid ' ] ?? null );
306+ // Not demanded when HR is recording for somebody else — see resolveReplacement().
307+ $ replacementUid = $ this ->resolveReplacement ($ employeeUid , $ type , $ data ['replacementUid ' ] ?? null , required: !$ onBehalf );
293308
294309 // The employee enters the number of working days; the manager verifies it (§7).
295310 $ workingDays = $ this ->normaliseWorkingDays ($ data ['workingDays ' ] ?? null );
@@ -626,7 +641,7 @@ private function hrEdit(string $actorUid, LeaveRequest $request, array $data): L
626641 } catch (DoesNotExistException ) {
627642 throw new ValidationException ('This request refers to a leave type that no longer exists. ' );
628643 }
629- $ request ->setReplacementUid ($ this ->resolveReplacement ($ request ->getEmployeeUid (), $ type , $ data ['replacementUid ' ]));
644+ $ request ->setReplacementUid ($ this ->resolveReplacement ($ request ->getEmployeeUid (), $ type , $ data ['replacementUid ' ], required: false ));
630645 }
631646 // HR may correct the working-day count (§5.5); otherwise it is kept as entered.
632647 if (array_key_exists ('workingDays ' , $ data ) && $ data ['workingDays ' ] !== null ) {
0 commit comments