diff --git a/api/v1/_submissions/PKPBackendSubmissionsController.php b/api/v1/_submissions/PKPBackendSubmissionsController.php index f7f09fe4983..137eac12e3f 100644 --- a/api/v1/_submissions/PKPBackendSubmissionsController.php +++ b/api/v1/_submissions/PKPBackendSubmissionsController.php @@ -394,12 +394,24 @@ public function getReviewAssignments(Request $illuminateRequest): JsonResponse foreach ($illuminateRequest->query() as $param => $val) { switch ($param) { - case 'pending': - $collector->filterByIsIncomplete(true); + case 'actionRequired': + $collector->filterByActionRequiredByReviewer(true); + break; + case 'active': + $collector->filterByActive(true); break; case 'archived': $collector->filterByIsArchived(true); break; + case 'declined': + $collector->filterByDeclined(true); + break; + case 'completed': + $collector->filterByCompleted(true); + break; + case 'published': + $collector->filterByPublished(true); + break; } } diff --git a/classes/submission/DashboardView.php b/classes/submission/DashboardView.php index 143428e28db..9d89b13f41b 100644 --- a/classes/submission/DashboardView.php +++ b/classes/submission/DashboardView.php @@ -41,10 +41,14 @@ class DashboardView public const TYPE_SCHEDULED = 'scheduled'; public const TYPE_PUBLISHED = 'published'; public const TYPE_DECLINED = 'declined'; + public const TYPE_INCOMPLETE_SUBMISSIONS = 'incomplete-submissions'; + public const TYPE_REVIEWER_ACTION_REQUIRED = 'reviewer-action-required'; public const TYPE_REVIEWER_ASSIGNMENTS_ALL = 'reviewer-assignments-all'; - public const TYPE_REVIEWER_ASSIGNMENTS_PENDING = 'reviewer-assignments-pending'; + public const TYPE_REVIEWER_ASSIGNMENTS_COMPLETED = 'reviewer-assignments-completed'; + public const TYPE_REVIEWER_ASSIGNMENTS_PUBLISHED = 'reviewer-assignments-published'; public const TYPE_REVIEWER_ASSIGNMENTS_ARCHIVED = 'reviewer-assignments-archived'; + public const TYPE_REVIEWER_ASSIGNMENTS_DECLINED = 'reviewer-assignments-declined'; // The number of submissions in the view protected int $count = 0; diff --git a/classes/submission/Repository.php b/classes/submission/Repository.php index 61bb4a5132d..7d02bf08d52 100644 --- a/classes/submission/Repository.php +++ b/classes/submission/Repository.php @@ -1074,6 +1074,18 @@ protected function mapDashboardViews(Collection $types, Context $context, User $ 'assigned', ['isIncomplete' => true] ); + case DashboardView::TYPE_REVIEWER_ACTION_REQUIRED: + return new DashboardView( + $key, + __('submission.dashboard.view.reviewAssignments.actionRequired'), + [Role::ROLE_ID_REVIEWER], + Repo::reviewAssignment()->getCollector() + ->filterByReviewerIds([$user->getId()]) + ->filterByContextIds([$context->getId()]) + ->filterByActionRequiredByReviewer(true), + 'reviewerAssignments', + ['actionRequired' => true] + ); case DashboardView::TYPE_REVIEWER_ASSIGNMENTS_ALL: return new DashboardView( $key, @@ -1081,20 +1093,34 @@ protected function mapDashboardViews(Collection $types, Context $context, User $ [Role::ROLE_ID_REVIEWER], Repo::reviewAssignment()->getCollector() ->filterByReviewerIds([$user->getId()]) - ->filterByContextIds([$context->getId()]), - 'reviewerAssignments' + ->filterByContextIds([$context->getId()]) + ->filterByActive(true), + 'reviewerAssignments', + ['active' => true] ); - case DashboardView::TYPE_REVIEWER_ASSIGNMENTS_PENDING: + case DashboardView::TYPE_REVIEWER_ASSIGNMENTS_COMPLETED: return new DashboardView( $key, - __('submission.dashboard.view.reviewAssignments.pending'), + __('submission.dashboard.view.reviewAssignments.completed'), [Role::ROLE_ID_REVIEWER], Repo::reviewAssignment()->getCollector() ->filterByReviewerIds([$user->getId()]) ->filterByContextIds([$context->getId()]) - ->filterByIsIncomplete(true), + ->filterByCompleted(true), 'reviewerAssignments', - ['pending' => true] + ['completed' => true] + ); + case DashboardView::TYPE_REVIEWER_ASSIGNMENTS_PUBLISHED: + return new DashboardView( + $key, + __('submission.dashboard.view.reviewAssignments.published'), + [Role::ROLE_ID_REVIEWER], + Repo::reviewAssignment()->getCollector() + ->filterByReviewerIds([$user->getId()]) + ->filterByContextIds([$context->getId()]) + ->filterByPublished(true), + 'reviewerAssignments', + ['published' => true] ); case DashboardView::TYPE_REVIEWER_ASSIGNMENTS_ARCHIVED: return new DashboardView( @@ -1108,6 +1134,19 @@ protected function mapDashboardViews(Collection $types, Context $context, User $ 'reviewerAssignments', ['archived' => true] ); + case DashboardView::TYPE_REVIEWER_ASSIGNMENTS_DECLINED: + return new DashboardView( + $key, + __('submission.dashboard.view.reviewAssignments.declined'), + [Role::ROLE_ID_REVIEWER], + Repo::reviewAssignment()->getCollector() + ->filterByReviewerIds([$user->getId()]) + ->filterByContextIds([$context->getId()]) + ->filterByDeclined(true), + 'reviewerAssignments', + ['declined' => true] + ); + } }); } diff --git a/classes/submission/reviewAssignment/Collector.php b/classes/submission/reviewAssignment/Collector.php index 7dc62b755e2..11ae0fe6cf7 100644 --- a/classes/submission/reviewAssignment/Collector.php +++ b/classes/submission/reviewAssignment/Collector.php @@ -1,4 +1,5 @@ isOverdue = $isOverdue; return $this; } + /** + * Filter by review assignments, which require attention from reviewer: + * awaiting respond from reviewer to accept the review or to finish the review (accepted but not completed) + * due dates are missed + * Don't include assignments that aren't on a correspondent review stage + */ + public function filterByActionRequiredByReviewer(bool $actionsRequired): static + { + $this->actionRequiredByReviewer = $actionsRequired; + return $this; + } + + /** + * Filter by submissions there are not: cancelled, declined, published + */ + public function filterByActive(bool $isActive): static + { + $this->isActive = $isActive; + return $this; + } + + /** + * Filter by completed review assignments, applies for all submissions stages, except submission is published (see filterByPublished) + */ + public function filterByCompleted(bool $isCompleted): static + { + $this->isCompleted = $isCompleted; + return $this; + } + + /** + * Filter by complete review assignments made on submissions which subsequently were published + */ + public function filterByPublished(bool $isPublished): static + { + $this->isPublished = $isPublished; + return $this; + } + + /** + * Filter by declined review assignments + */ + public function filterByDeclined(bool $isDeclined): static + { + $this->isDeclined = $isDeclined; + return $this; + } + /** * Filter by review round ids */ @@ -271,13 +326,81 @@ public function getQueryBuilder(): Builder ); }); + $q->when( + $this->actionRequiredByReviewer, + fn (Builder $q) => $q + ->whereNull('ra.date_completed') + ->where('ra.declined', '<>', 1) + ->where('ra.cancelled', '<>', 1) + ->whereIn( + 'ra.submission_id', + fn (Builder $q) => $q + ->select('s.submission_id') + ->from('submissions AS s') + ->whereColumn('s.submission_id', 'ra.submission_id') + ->whereColumn('s.stage_id', 'ra.stage_id') + ) + ); + + $q->when( + $this->isActive, + fn (Builder $q) => $q + ->where('ra.declined', '<>', 1) + ->where('ra.cancelled', '<>', 1) + ->whereIn( + 'ra.submission_id', + fn (Builder $q) => $q + ->select('s.submission_id') + ->from('submissions AS s') + ->whereColumn('s.submission_id', 'ra.submission_id') + ->where('s.status', '<>', PKPSubmission::STATUS_PUBLISHED) + ) + ); + + $q->when( + $this->isDeclined, + fn (Builder $q) => $q->where('ra.declined', 1) + ); + + $q->when( + $this->isPublished || $this->isCompleted, + fn (Builder $q) => $q + ->whereNotNull('ra.date_completed') + ->whereIn( + 'ra.submission_id', + fn (Builder $q) => $q + ->select('s.submission_id') + ->from('submissions AS s') + ->whereColumn('s.submission_id', 'ra.submission_id') + ->when( + $this->isPublished, + fn (Builder $q) => $q + ->where('s.status', PKPSubmission::STATUS_PUBLISHED) + ) + ->when( + $this->isCompleted, + fn (Builder $q) => $q + // Don't include published submissions + ->where('s.status', '<>', PKPSubmission::STATUS_PUBLISHED) + // If the submission is returned to the submission stage, exclude it + ->where('s.stage_id', '<>', WORKFLOW_STAGE_ID_SUBMISSION) + ) + ) + ); + $q->when( $this->isArchived, - fn (Builder $q) => - $q->where( + fn (Builder $q) => $q->where( fn (Builder $q) => $q - ->whereNotNull('ra.date_completed') - ->orWhere('declined', 1) + ->whereNull('ra.date_completed') + ->whereIn( + 'ra.submission_id', + fn (Builder $q) => $q + ->select('s.submission_id') + ->from('submissions AS s') + ->whereColumn('s.submission_id', 'ra.submission_id') + ->whereIn('s.stage_id', [WORKFLOW_STAGE_ID_EDITING, WORKFLOW_STAGE_ID_PRODUCTION]) + ) ) ); diff --git a/locale/en/submission.po b/locale/en/submission.po index 9a7198177ed..bca25d5047d 100644 --- a/locale/en/submission.po +++ b/locale/en/submission.po @@ -2458,11 +2458,20 @@ msgstr "Completed / Declined" msgid "submission.dashboard.view.reviewAssignments.all" msgstr "All assignments" -msgid "submission.dashboard.view.reviewAssignments.pending" -msgstr "Pending" +msgid "submission.dashboard.view.reviewAssignments.actionRequired" +msgstr "Action Required by me" msgid "submission.dashboard.view.reviewAssignments.archived" -msgstr "Completed / Declined" +msgstr "Archived" + +msgid "submission.dashboard.view.reviewAssignments.declined" +msgstr "Declined" + +msgid "submission.dashboard.view.reviewAssignments.completed" +msgstr "Completed" + +msgid "submission.dashboard.view.reviewAssignments.published" +msgstr "Published" msgid "submission.stage.externalReviewWithRound" msgstr "Review (Round {$round})"