Skip to content

Development: Allow applicants to unsubmit a submitted application - #2515

Open
az108 wants to merge 13 commits into
mainfrom
feat/1523-unsubmit-application
Open

Development: Allow applicants to unsubmit a submitted application#2515
az108 wants to merge 13 commits into
mainfrom
feat/1523-unsubmit-application

Conversation

@az108

@az108 az108 commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Checklist

General

Server

  • Important: I implemented the changes with a very good performance and prevented too many (unnecessary) and too complex database calls.
  • I strictly followed the principle of data economy for all database calls.
  • I strictly followed the server coding and design guidelines.
  • I added multiple server tests (JUnit) related to the features.
  • I documented the Java code using JavaDoc style.

Client

Motivation and Context

Closes #1523, implementing the issue's Option B / "Forgiving" approach.

Today, "Withdraw" is irreversible: once an applicant clicks it the application moves to a terminal WITHDRAWN state and the server refuses to let them apply to the same position again. Issue #1523 observed that applicants treat Withdraw as an "Undo" button (typically to fix a typo and resubmit), so the current design silently disqualifies users who were trying to correct their submission.

Option B: turn Withdraw into Unsubmit — a revertible action that puts the application back to SAVED (draft), as long as the job's deadline has not passed. The applicant can then edit and resubmit.

Description

Server

  • ApplicationService.withdrawApplication (src/main/java/de/tum/cit/aet/application/service/ApplicationService.java):
    • Refuses any application not currently in SENT (throws OperationNotAllowedException).
    • Refuses when the job's endDate is in the past.
    • Sets state to SAVED instead of WITHDRAWN.
    • Stops sending the APPLICATION_WITHDRAWN email — the action is now "undo submission", not a permanent goodbye.
  • Endpoint path (PUT /api/applications/withdraw/{id}) and Java method name are unchanged, so no OpenAPI regen / generated-client churn is needed. The user-facing terminology rename is what the issue asked for; renaming the internal API is out of scope.
  • Server tests cover the four meaningful cases: SENT → revert OK, post-deadline rejected, non-SENT state rejected, unauthorised still 403.

Client

  • "Withdraw" button is now labelled "Unsubmit" / "Einreichung zurücknehmen" in both detail and overview pages. Eligibility tightened from [SENT, IN_REVIEW] to SENT only, so once a professor starts reviewing the action disappears and the applicant can't accidentally yank an in-progress evaluation back.
  • Confirmation dialog text rewritten to explain the new semantics: "This will move your application back to draft. The professor will no longer see it, and you can edit and resubmit it before the job's deadline."
  • Success and error toast strings rewritten accordingly. The "could not unsubmit" toast doubles as the user-facing surface for the deadline check (since the client doesn't currently have the job's endDate available — relying on the server reject + a friendly error message keeps the diff small; a follow-up could add endDate to JobCardDTO and hide the button entirely past the deadline).

Email

  • The APPLICATION_WITHDRAWN email type is no longer dispatched. The enum value and template files are left in place so nothing else has to move; they simply stop being used.

Non-goals (deliberately out of scope)

  • Renaming the HTTP endpoint or the Java method.
  • Removing the WITHDRAWN enum value from ApplicationState.
  • Deleting APPLICATION_WITHDRAWN.html and APPLICATION_WITHDRAWN_subject.html template files.
  • Migrating existing WITHDRAWN rows (prod DB is wiped per current state; nothing to migrate).
  • Adding endDate to client-visible DTOs so the Unsubmit button can be hidden past the deadline (server still enforces correctness; this is a follow-up UX polish).

Steps for Testing

Prerequisites:

  1. Log in as an applicant.
  2. Submit an application to a published job whose deadline is in the future.

Test (happy path):

  1. On the application detail page, open the menu — verify the action reads "Unsubmit" (DE: "Einreichung zurücknehmen").
  2. Click it. The confirmation dialog should explain the application will move back to draft and that you can resubmit before the deadline.
  3. Confirm. Verify a success toast appears ("Application moved back to draft"), the application's state badge flips to draft, and the menu now exposes "Edit" / "Delete" again.
  4. Edit and resubmit the application — confirm it lands back in SENT.

Test (state guard):

  1. Log in as a professor, open the applicant's submitted application, and move it to IN_REVIEW (rate it / open it). As the applicant, refresh the detail page — the "Unsubmit" action should no longer appear.

Test (deadline guard):

  1. Have an admin set a published job's end date to yesterday. As an applicant with a SENT application against that job, click "Unsubmit". Verify the error toast appears ("Couldn't move back to draft / The deadline may have passed") and the application remains in SENT.

Repeat steps 1–6 on the application overview list page (where the same action lives in the row menu).

Review Progress

Code Review

  • Code Review 1

Manual Tests

  • Happy path: unsubmit → draft → edit → resubmit
  • State guard: button hidden once IN_REVIEW
  • Deadline guard: server rejects + friendly error toast
  • Both detail and overview pages

Test Coverage

Client

Class/File Line Coverage Lines Expects Ratio
application-creation-form.component.ts 90.69% 748 123 16.4
application-detail-for-applicant.component.ts 2.87% 349 37 10.6
application-overview-for-applicant.component.ts 0.00% 207 23 11.1

Server

Class/File Line Coverage Lines
ApplicationService.java 78.48% 481
ReferenceRequestService.java 97.36% 413

Last updated: 2026-07-28 18:18:24 UTC

Replace the irreversible "Withdraw" action on submitted applications with a
forgiving "Unsubmit" that reverts the application to draft, provided the job
deadline has not passed. Applicants who clicked Withdraw to fix a typo would
previously lock themselves out of re-applying; with this change they can
edit and resubmit until the deadline.

- ApplicationService.withdrawApplication now sets the state back to SAVED
  instead of WITHDRAWN, refuses any state other than SENT, and refuses
  after the job's endDate has passed. No withdrawal email is sent.
- UI button + dialog + toast text changed across EN/DE; eligibility is
  restricted to SENT so the action is hidden once a professor starts a
  review.
- Endpoint path and Java method name kept as "withdraw" to avoid a
  client/OpenAPI rename; the user-facing terminology change is what
  issue #1523 asked for.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added server Pull requests that update Java code. (Added Automatically!) client Pull requests that update TypeScript code. (Added Automatically!) tests application Code changes in application module. labels May 14, 2026
@codacy-production

codacy-production Bot commented May 14, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

…plication

# Conflicts:
#	src/main/webapp/i18n/en/button.json
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

Comment thread src/main/webapp/i18n/en/button.json Outdated
Stray entry from a merge conflict — nothing references `button.update`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@az108
az108 requested a review from Cathy0123456789 May 20, 2026 14:38
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

1 similar comment
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions

Copy link
Copy Markdown
Contributor

📊 Server Test Coverage Too Low

🔍 View coverage locally:

./gradlew test jacocoTestReport
open build/reports/jacoco/test/html/index.html

🌐 View coverage from GitHub:
Download the "coverage-report-server" artifact from this workflow run.

Resolved conflicts in i18n/de/global.json and i18n/en/global.json for the
errorWithdrawingApplication toast; kept the unsubmit-specific deadline
message added on this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions

Copy link
Copy Markdown
Contributor

There hasn't been any activity on this pull request recently. Therefore, this pull request has been automatically marked as stale and will be closed if no further activity occurs within seven days. Thank you for your contributions.

@github-actions

Copy link
Copy Markdown
Contributor

There hasn't been any activity on this pull request recently. Therefore, this pull request has been automatically marked as stale and will be closed if no further activity occurs within seven days. Thank you for your contributions.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

There hasn't been any activity on this pull request recently. Therefore, this pull request has been automatically marked as stale and will be closed if no further activity occurs within seven days. Thank you for your contributions.

@codacy-production

codacy-production Bot commented Jul 5, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 13 complexity

Metric Results
Complexity 13

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions

Copy link
Copy Markdown
Contributor

There hasn't been any activity on this pull request recently. Therefore, this pull request has been automatically marked as stale and will be closed if no further activity occurs within seven days. Thank you for your contributions.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

Resolve the conflict in ApplicationService#withdrawApplication, where this
branch and main changed the same method in incompatible ways:

- this branch repurposes it from "withdraw" to "unsubmit" (SENT -> SAVED,
  deadline guard, no email)
- main added reference-request cancellation plus a withdrawal email to the
  old withdraw behaviour

Kept this branch's unsubmit semantics, since the feature intentionally
replaces withdrawal with a reversible move back to draft.

Main's cancellation is deliberately not carried into the unsubmit flow: it
flips pending requests to CANCELLED, but dispatchInvitations only re-invites
entries that are ADDED with no token, so those referees would never be
re-invited on resubmit. An applicant unsubmitting to fix a typo would lose
their reference requests for good, after referees were told the letter was
no longer needed.

With withdrawal gone, cancelPendingForWithdrawnApplication and its
sendCancellationEmail helper had no remaining caller and were removed, along
with the WithdrawCancellation tests covering them. ReferenceRequestStatus
CANCELLED and EmailType REFERENCE_LETTER_CANCELLED are kept: both are still
part of the API contract and rendered by the client and PDF export.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

…flow

Reinstates cancelPendingForWithdrawnApplication and sendCancellationEmail
together with their tests, and calls the cancellation again from the unsubmit
flow. Both files are byte-identical to main again, so the feature is back
exactly as it was written.

Removing it here was the wrong call. The reasoning at the time only weighed the
applicant who unsubmits and resubmits shortly after; it did not account for the
applicant who never resubmits, whose referees would then never hear anything.

One consequence is still open and needs a decision rather than a guess.
dispatchInvitations only re-invites entries that are ADDED with no token, while
cancelling leaves the entry CANCELLED with its token set, so a referee cancelled
by an unsubmit is not invited again if the application is resubmitted.

Separately, findReminderCandidates filters on request status alone and never
looks at the application state, so referees attached to an application sitting
in draft keep receiving reminders.

Both are recorded here so the follow-up decision is made deliberately.

Verified: 786 server tests pass, including the two restored cancellation tests.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

… submitted again

Settles the withdraw behaviour discussed in review. There is one button, called
Withdraw, available while the application is still SENT and therefore not once
it is in review. It puts the application back into draft and cancels the pending
reference requests, which is what makes the links already emailed to the
recommenders stop working. Submitting again now sends those recommenders a fresh
request.

The cancellation and the dead links were already in place. The missing piece was
the second submission: dispatchInvitations only issued a token for entries that
had never been invited, so a recommender cancelled by a withdrawal was skipped
and never heard from us again. It now also re-issues for cancelled entries, with
a new token so the pre-withdrawal link stays dead. Recommenders who already
submitted, declined or let their link expire are still skipped, so a second
submission never asks the same person twice.

Naming goes back to Withdraw across the button, dialog and toasts. The wording
does not go back with it: the pre-branch text claimed the action could not be
undone, which is no longer true, so it now says the application returns to draft,
that the existing recommendation links stop working, and that submitting again
sends a new request.

The submit confirmation warns about that re-invitation when it applies. The
component already holds the reference requests, so the check needs no extra
request and mirrors the server condition exactly. Its base message also claimed
that submitting made further edits impossible, which withdrawing now disproves,
so that has been corrected too.

Verified: 788 server tests, 2010 client tests, typecheck, eslint with no errors,
a11y lint, the production build and prettier.

Co-Authored-By: Claude <noreply@anthropic.com>
@az108
az108 requested a review from Cathy0123456789 July 25, 2026 16:59
@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 No OpenAPI or client changes needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

application Code changes in application module. client Pull requests that update TypeScript code. (Added Automatically!) ready for review server Pull requests that update Java code. (Added Automatically!) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Define Application Withdrawal Logic

2 participants