Skip to content

Commit

Permalink
Merge pull request #2805 from compucorp/PCHR-4051-allow-own-approver-…
Browse files Browse the repository at this point in the history
…cancel

PCHR-4051: Allow Own Approver And Admin Cancel Own Leave Request Even When Absence Type Does Not Allow It
  • Loading branch information
tunbola authored Jul 31, 2018
2 parents e1bee09 + 38f1808 commit da99782
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ public static function validateParams($params, $validationMode = self::VALIDATIO
self::validateAbsenceTypeIsActiveAndValid($params, $absenceType);
self::validateTOILRequest($params, $absenceType, $absencePeriod);
self::validateLeaveDaysAgainstAbsenceTypeMaxConsecutiveLeaveDays($params, $absenceType);
self::validateAbsenceTypeAllowRequestCancellationForLeaveRequestCancellation($params, $absenceType);
self::validateAbsencePeriod($params, $absencePeriod);

if($validationMode != self::IMPORT_VALIDATION) {
Expand Down Expand Up @@ -731,46 +730,6 @@ private static function validateLeaveDaysAgainstAbsenceTypeMaxConsecutiveLeaveDa
}
}

/**
* This method checks if the absence type allows cancellation in advance of start date and that the leave request from_date
* should not be in the past in the event of a leave request cancellation by a user.
*
* Also checks that a user's leave request should not be cancelled if the absence type does not
* allow leave request cancellation
*
* @param array $params
* The params array received by the create method
* @param AbsenceType $absenceType
*
* @throws \CRM_HRLeaveAndAbsences_Exception_InvalidLeaveRequestException
*/
private static function validateAbsenceTypeAllowRequestCancellationForLeaveRequestCancellation($params, $absenceType) {
$leaveRequestStatuses = self::getStatuses();
$leaveRequestIsForCurrentUser = CRM_Core_Session::getLoggedInContactID() == $params['contact_id'];
$isACancellationRequest = ($params['status_id'] == $leaveRequestStatuses['cancelled']);

if($leaveRequestIsForCurrentUser && $isACancellationRequest) {
$today = new DateTime('today');
$fromDate = new DateTime($params['from_date']);

if($absenceType->allow_request_cancelation == AbsenceType::REQUEST_CANCELATION_IN_ADVANCE_OF_START_DATE && $fromDate < $today) {
throw new InvalidLeaveRequestException(
'Leave Request with past days cannot be cancelled',
'leave_request_past_days_cannot_be_cancelled',
'type_id'
);
}

if($absenceType->allow_request_cancelation == AbsenceType::REQUEST_CANCELATION_NO) {
throw new InvalidLeaveRequestException(
'Absence Type does not allow leave request cancellation',
'leave_request_absence_type_disallows_cancellation',
'type_id'
);
}
}
}

/**
* This method validates that the absence type is active and is valid for the
* type of request. That is, if this is a Sickness Request, then only absence
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,17 @@ protected function update($params) {
$this->getErrorMessageForInvalidStatusTransition($params));
}

$isTOILWithPastDates = LeaveRequest::isTOILWithPastDates($params);
$isCancelledStatus = $this->isCancelledStatus($params);

if ($isCancelledStatus) {
if (!$this->canCancelForAbsenceType($params)) {
throw new RuntimeException(
'You cannot cancel leave requests for this Absence type'
);
}
}

$isTOILWithPastDates = LeaveRequest::isTOILWithPastDates($params);
$canCanCancelTOILWithPastDates = $this->canCanCancelTOILWithPastDates($params);

if ($isTOILWithPastDates && $isCancelledStatus && !$canCanCancelTOILWithPastDates) {
Expand All @@ -128,6 +137,22 @@ protected function update($params) {
return $this->createRequestWithBalanceChanges($params);
}

/**
* Whether the current user can cancel a leave request for the absence
* type.
*
* @param array $params
*
* @return bool
*/
private function canCancelForAbsenceType($params) {
return $this->leaveRequestRightsService->canCancelForAbsenceType(
$params['type_id'],
$params['contact_id'],
new DateTime($params['from_date'])
);
}

/**
* Return an error message either for specific invalid status transition cases
* or a default generic message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,35 @@ public function getLeaveContactsCurrentUserHasAccessTo() {

return $results;
}

/**
* Checks if the current user can cancel the leave request for the given absence type,
* leave contact and leave from date.
*
* @param int $absenceTypeId
* @param int $contactId
* @param \DateTime $leaveFromDate
*
* @return bool
*/
public function canCancelForAbsenceType($absenceTypeId, $contactId, DateTime $leaveFromDate) {
if ($this->currentUserIsAdmin() || $this->currentUserIsLeaveManagerOf($contactId)) {
return TRUE;
}

$absenceType = AbsenceType::findById($absenceTypeId);
if ($absenceType->allow_request_cancelation == AbsenceType::REQUEST_CANCELATION_ALWAYS) {
return TRUE;
}

$today = new DateTime('today');

$absenceTypeAllowsFutureCancellation =
$absenceType->allow_request_cancelation == AbsenceType::REQUEST_CANCELATION_IN_ADVANCE_OF_START_DATE;
if ($absenceTypeAllowsFutureCancellation && $leaveFromDate > $today) {
return TRUE;
}

return FALSE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1000,72 +1000,6 @@ public function testLeaveRequestCanBeCreatedWhenNumberOfLeaveWorkingDaysNotGreat
]);
}

public function testAUserCannotCancelOwnLeaveRequestWhenAbsenceTypeDoesNotAllowIt() {
$contactID = 5;
$this->registerCurrentLoggedInContactInSession($contactID);

$absenceType = AbsenceTypeFabricator::fabricate([
'allow_request_cancelation' => AbsenceType::REQUEST_CANCELATION_NO
]);

$leaveRequestStatuses = array_flip(LeaveRequest::buildOptions('status_id', 'validate'));

$leaveRequest = LeaveRequestFabricator::fabricateWithoutValidation([
'type_id' => $absenceType->id,
'contact_id' => $contactID,
'status_id' => $leaveRequestStatuses['awaiting_approval'],
'from_date' => CRM_Utils_Date::processDate('now'),
'to_date' => CRM_Utils_Date::processDate('+4 days')
]);

$this->setExpectedException('CRM_HRLeaveAndAbsences_Exception_InvalidLeaveRequestException', 'Absence Type does not allow leave request cancellation');
//cancel leave request
LeaveRequest::create([
'id' => $leaveRequest->id,
'type_id' => $absenceType->id,
'contact_id' => $contactID,
'status_id' => $leaveRequestStatuses['cancelled'],
'from_date' => CRM_Utils_Date::processDate('now'),
'from_date_type' => 1,
'to_date' => CRM_Utils_Date::processDate('+4 days'),
'to_date_type' => 1,
'request_type' => LeaveRequest::REQUEST_TYPE_LEAVE
]);
}

public function testAUserCannotCancelOwnLeaveRequestWhenAbsenceTypeAllowsItInAdvanceOfStartDateAndFromDateIsLessThanToday() {
$contactID = 5;
$this->registerCurrentLoggedInContactInSession($contactID);

$absenceType = AbsenceTypeFabricator::fabricate([
'allow_request_cancelation' => AbsenceType::REQUEST_CANCELATION_IN_ADVANCE_OF_START_DATE
]);

$leaveRequestStatuses = array_flip(LeaveRequest::buildOptions('status_id', 'validate'));

$leaveRequest = LeaveRequestFabricator::fabricateWithoutValidation([
'type_id' => $absenceType->id,
'contact_id' => $contactID,
'status_id' => $leaveRequestStatuses['awaiting_approval'],
'from_date' => CRM_Utils_Date::processDate('-1 day'),
'to_date' => CRM_Utils_Date::processDate('+4 days')
]);

$this->setExpectedException('CRM_HRLeaveAndAbsences_Exception_InvalidLeaveRequestException', 'Leave Request with past days cannot be cancelled');
//cancel leave request
LeaveRequest::create([
'id' => $leaveRequest->id,
'type_id' => $absenceType->id,
'contact_id' => $contactID,
'status_id' => $leaveRequestStatuses['cancelled'],
'from_date' => CRM_Utils_Date::processDate('-1 day'),
'from_date_type' => 1,
'to_date' => CRM_Utils_Date::processDate('+4 days'),
'to_date_type' => 1,
'request_type' => LeaveRequest::REQUEST_TYPE_LEAVE
]);
}

public function testFindOverlappingLeaveRequestsForOneOverlappingLeaveRequest() {
$contactID = 1;
$fromDate1 = new DateTime('2016-11-02');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use CRM_HRLeaveAndAbsences_BAO_LeaveRequest as LeaveRequest;
use CRM_HRLeaveAndAbsences_Test_Fabricator_AbsenceType as AbsenceTypeFabricator;
use CRM_HRCore_Test_Fabricator_Contact as ContactFabricator;
use CRM_HRLeaveAndAbsences_BAO_AbsenceType as AbsenceType;

/**
* Class CRM_HRLeaveAndAbsences_Service_LeaveRequestRightsTest
Expand Down Expand Up @@ -323,6 +324,62 @@ public function testAdminShouldHaveAccessToAllContacts() {
$this->assertEquals([], $accessibleContacts);
}

public function testCanCancelForAbsenceTypeReturnsTrueWhenUserIsAdmin() {
$typeId = 1;
$contactID = 2;
$leaveDate = new DateTime();
$leaveRequestRightsService = $this->getLeaveRequestRightsForAdminAsCurrentUser();
$result = $leaveRequestRightsService->canCancelForAbsenceType($typeId, $contactID, $leaveDate);
$this->assertTrue($result);
}

public function testCanCancelForAbsenceTypeReturnsTrueWhenUserIsLeaveManager() {
$typeId = 1;
$contactID = 2;
$leaveDate = new DateTime();
$leaveRequestRightsService = $this->getLeaveRequestRightsForLeaveManagerAsCurrentUser();
$result = $leaveRequestRightsService->canCancelForAbsenceType($typeId, $contactID, $leaveDate);
$this->assertTrue($result);
}

public function testCanCancelForAbsenceTypeReturnsTrueWhenAbsenceTypeAllowsCancellationForStaff() {
$absenceType = AbsenceTypeFabricator::fabricate([
'allow_request_cancelation' => AbsenceType::REQUEST_CANCELATION_ALWAYS
]);
$contactID = 2;
$leaveDate = new DateTime();
$leaveRequestRightsService = $this->getLeaveRightsService();
$result = $leaveRequestRightsService->canCancelForAbsenceType($absenceType->id, $contactID, $leaveDate);
$this->assertTrue($result);
}

public function testCanCancelForAbsenceTypeReturnsFalseWhenAbsenceTypeDoesNotAllowCancellationForStaff() {
$absenceType = AbsenceTypeFabricator::fabricate(['allow_request_cancelation' => AbsenceType::REQUEST_CANCELATION_NO]);
$contactID = 2;
$leaveDate = new DateTime();
$leaveRequestRightsService = $this->getLeaveRightsService();
$result = $leaveRequestRightsService->canCancelForAbsenceType($absenceType->id, $contactID, $leaveDate);
$this->assertFalse($result);
}

public function testCanCancelForAbsenceTypeReturnsFalseWhenAbsenceTypeAllowsCancellationForFutureDateButLeaveDateIsPast() {
$absenceType = AbsenceTypeFabricator::fabricate(['allow_request_cancelation' => AbsenceType::REQUEST_CANCELATION_IN_ADVANCE_OF_START_DATE]);
$contactID = 2;
$leaveDate = new DateTime('yesterday');
$leaveRequestRightsService = $this->getLeaveRightsService();
$result = $leaveRequestRightsService->canCancelForAbsenceType($absenceType->id, $contactID, $leaveDate);
$this->assertFalse($result);
}

public function testCanCancelForAbsenceTypeReturnsTrueWhenAbsenceTypeAllowsCancellationForFutureDateAndLeaveDateIsInFuture() {
$absenceType = AbsenceTypeFabricator::fabricate(['allow_request_cancelation' => AbsenceType::REQUEST_CANCELATION_IN_ADVANCE_OF_START_DATE]);
$contactID = 2;
$leaveDate = new DateTime('tomorrow');
$leaveRequestRightsService = $this->getLeaveRightsService();
$result = $leaveRequestRightsService->canCancelForAbsenceType($absenceType->id, $contactID, $leaveDate);
$this->assertTrue($result);
}

private function getLeaveRightsService($isAdmin = FALSE, $isManager = FALSE) {
$leaveManagerService = $this->createLeaveManagerServiceMock($isAdmin, $isManager);
return new LeaveRequestRightsService($leaveManagerService);
Expand Down
Loading

0 comments on commit da99782

Please sign in to comment.