diff --git a/src/back-end/lib/mailer/notifications/proposal/code-with-us.tsx b/src/back-end/lib/mailer/notifications/proposal/code-with-us.tsx index 6d9e6c8f7..9d10d4100 100644 --- a/src/back-end/lib/mailer/notifications/proposal/code-with-us.tsx +++ b/src/back-end/lib/mailer/notifications/proposal/code-with-us.tsx @@ -8,16 +8,13 @@ import * as templates from "back-end/lib/mailer/templates"; import { makeSend } from "back-end/lib/mailer/transport"; import React from "react"; import { CONTACT_EMAIL, EMPTY_STRING } from "shared/config"; -import { - CWUOpportunity, - isCWUOpportunityClosed -} from "shared/lib/resources/opportunity/code-with-us"; +import { CWUOpportunity } from "shared/lib/resources/opportunity/code-with-us"; import { CWUProposal, CWUProposalSlim } from "shared/lib/resources/proposal/code-with-us"; import { AuthenticatedSession } from "shared/lib/resources/session"; -import { User } from "shared/lib/resources/user"; +import { User, UserType } from "shared/lib/resources/user"; import { Id } from "shared/lib/types"; import { getValidValue } from "shared/lib/validation"; @@ -140,7 +137,6 @@ export async function handleCWUProposalWithdrawn( proposalId: Id, session: AuthenticatedSession ): Promise { - //Notify the opportunity author if the opportunity is in an awardable state const proposal = getValidValue( await db.readOneCWUProposal(connection, proposalId, session), null @@ -155,13 +151,6 @@ export async function handleCWUProposalWithdrawn( ), null ); - // Need to read opportunityAuthor separate here, as this session will not be allowed to read from opportunity itself - const opportunityAuthor = - proposal && - getValidValue( - await db.readOneCWUOpportunityAuthor(connection, proposal.opportunity.id), - null - ); if (proposal && opportunity) { const withdrawnProponent = @@ -170,24 +159,29 @@ export async function handleCWUProposalWithdrawn( await db.readOneUser(connection, proposal.createdBy.id), null ); - // Notify opportunity author if opportunity is closed - if ( - opportunityAuthor && - withdrawnProponent && - isCWUOpportunityClosed(opportunity) - ) { - await withdrawnCWUProposalSubmission( - opportunityAuthor, - withdrawnProponent, - opportunity - ); - } // Notify proposal author if (withdrawnProponent) { await withdrawnCWUProposalSubmissionProposalAuthor( withdrawnProponent, opportunity ); + + // Notify admins that the proposal has been withdrawn + const adminUsers = + getValidValue( + await db.readManyUsersByRole(connection, UserType.Admin), + null + ) || []; + await Promise.all( + adminUsers.map( + async (admin) => + await withdrawnCWUProposalSubmission( + admin, + withdrawnProponent, + opportunity + ) + ) + ); } } } diff --git a/src/back-end/lib/mailer/notifications/proposal/sprint-with-us.tsx b/src/back-end/lib/mailer/notifications/proposal/sprint-with-us.tsx index 3c4bd80d3..abf9646fd 100644 --- a/src/back-end/lib/mailer/notifications/proposal/sprint-with-us.tsx +++ b/src/back-end/lib/mailer/notifications/proposal/sprint-with-us.tsx @@ -8,16 +8,13 @@ import * as templates from "back-end/lib/mailer/templates"; import { makeSend } from "back-end/lib/mailer/transport"; import React from "react"; import { CONTACT_EMAIL, EMPTY_STRING } from "shared/config"; -import { - isSWUOpportunityClosed, - SWUOpportunity -} from "shared/lib/resources/opportunity/sprint-with-us"; +import { SWUOpportunity } from "shared/lib/resources/opportunity/sprint-with-us"; import { SWUProposal, SWUProposalSlim } from "shared/lib/resources/proposal/sprint-with-us"; import { AuthenticatedSession } from "shared/lib/resources/session"; -import { User } from "shared/lib/resources/user"; +import { User, UserType } from "shared/lib/resources/user"; import { Id } from "shared/lib/types"; import { getValidValue } from "shared/lib/validation"; @@ -146,7 +143,6 @@ export async function handleSWUProposalWithdrawn( proposalId: Id, session: AuthenticatedSession ): Promise { - //Notify the opportunity author if the opportunity is in an awardable state const proposal = getValidValue( await db.readOneSWUProposal(connection, proposalId, session), null @@ -161,13 +157,6 @@ export async function handleSWUProposalWithdrawn( ), null ); - // Need to read opportunityAuthor separate here, as this session will not be allowed to read from opportunity itself - const opportunityAuthor = - proposal && - getValidValue( - await db.readOneSWUOpportunityAuthor(connection, proposal.opportunity.id), - null - ); if (proposal && opportunity) { const withdrawnProponent = @@ -176,24 +165,29 @@ export async function handleSWUProposalWithdrawn( await db.readOneUser(connection, proposal.createdBy.id), null ); - // Notify opportunity author if opportunity is closed - if ( - opportunityAuthor && - withdrawnProponent && - isSWUOpportunityClosed(opportunity) - ) { - await withdrawnSWUProposalSubmission( - opportunityAuthor, - withdrawnProponent, - opportunity - ); - } // Notify proposal author if (withdrawnProponent) { await withdrawnSWUProposalSubmissionProposalAuthor( withdrawnProponent, opportunity ); + + // Notify admins that the proposal has been withdrawn + const adminUsers = + getValidValue( + await db.readManyUsersByRole(connection, UserType.Admin), + null + ) || []; + await Promise.all( + adminUsers.map( + async (admin) => + await withdrawnSWUProposalSubmission( + admin, + withdrawnProponent, + opportunity + ) + ) + ); } } } diff --git a/src/back-end/lib/mailer/notifications/proposal/team-with-us.tsx b/src/back-end/lib/mailer/notifications/proposal/team-with-us.tsx index c4736869a..c5b315d0d 100644 --- a/src/back-end/lib/mailer/notifications/proposal/team-with-us.tsx +++ b/src/back-end/lib/mailer/notifications/proposal/team-with-us.tsx @@ -8,16 +8,13 @@ import * as templates from "back-end/lib/mailer/templates"; import { makeSend } from "back-end/lib/mailer/transport"; import React from "react"; import { CONTACT_EMAIL, EMPTY_STRING } from "shared/config"; -import { - isTWUOpportunityClosed, - TWUOpportunity -} from "shared/lib/resources/opportunity/team-with-us"; +import { TWUOpportunity } from "shared/lib/resources/opportunity/team-with-us"; import { TWUProposal, TWUProposalSlim } from "shared/lib/resources/proposal/team-with-us"; import { AuthenticatedSession } from "shared/lib/resources/session"; -import { User } from "shared/lib/resources/user"; +import { User, UserType } from "shared/lib/resources/user"; import { Id } from "shared/lib/types"; import { getValidValue } from "shared/lib/validation"; @@ -146,7 +143,6 @@ export async function handleTWUProposalWithdrawn( proposalId: Id, session: AuthenticatedSession ): Promise { - //Notify the opportunity author if the opportunity is in an award-able state const proposal = getValidValue( await db.readOneTWUProposal(connection, proposalId, session), null @@ -161,13 +157,6 @@ export async function handleTWUProposalWithdrawn( ), null ); - // Need to read opportunityAuthor separate here, as this session will not be allowed to read from opportunity itself - const opportunityAuthor = - proposal && - getValidValue( - await db.readOneTWUOpportunityAuthor(connection, proposal.opportunity.id), - null - ); if (proposal && opportunity) { const withdrawnProponent = @@ -176,24 +165,29 @@ export async function handleTWUProposalWithdrawn( await db.readOneUser(connection, proposal.createdBy.id), null ); - // Notify opportunity author if opportunity is closed - if ( - opportunityAuthor && - withdrawnProponent && - isTWUOpportunityClosed(opportunity) - ) { - await withdrawnTWUProposalSubmission( - opportunityAuthor, - withdrawnProponent, - opportunity - ); - } // Notify proposal author if (withdrawnProponent) { await withdrawnTWUProposalSubmissionProposalAuthor( withdrawnProponent, opportunity ); + + // Notify admins that the proposal has been withdrawn + const adminUsers = + getValidValue( + await db.readManyUsersByRole(connection, UserType.Admin), + null + ) || []; + await Promise.all( + adminUsers.map( + async (admin) => + await withdrawnTWUProposalSubmission( + admin, + withdrawnProponent, + opportunity + ) + ) + ); } } } diff --git a/src/front-end/typescript/lib/pages/proposal/code-with-us/edit/tab/proposal.tsx b/src/front-end/typescript/lib/pages/proposal/code-with-us/edit/tab/proposal.tsx index 0f03c8297..4c6da1539 100644 --- a/src/front-end/typescript/lib/pages/proposal/code-with-us/edit/tab/proposal.tsx +++ b/src/front-end/typescript/lib/pages/proposal/code-with-us/edit/tab/proposal.tsx @@ -1169,19 +1169,7 @@ export const component: Tab.Component = { case CWUProposalStatus.UnderReview: case CWUProposalStatus.Evaluated: case CWUProposalStatus.Awarded: - return component_.page.actions.links([ - { - children: "Withdraw", - symbol_: leftPlacement(iconLinkSymbol("ban")), - button: true, - outline: true, - color: "c-nav-fg-alt", - disabled, - loading: isWithdrawLoading, - onClick: () => - dispatch(adt("showModal", "withdrawAfterDeadline" as const)) - } - ]); + return component_.page.actions.none(); case CWUProposalStatus.Withdrawn: if (isAcceptingProposals) { return component_.page.actions.links([ diff --git a/src/front-end/typescript/lib/pages/proposal/sprint-with-us/edit/tab/proposal.tsx b/src/front-end/typescript/lib/pages/proposal/sprint-with-us/edit/tab/proposal.tsx index 2ad5c87b4..2138a78b0 100644 --- a/src/front-end/typescript/lib/pages/proposal/sprint-with-us/edit/tab/proposal.tsx +++ b/src/front-end/typescript/lib/pages/proposal/sprint-with-us/edit/tab/proposal.tsx @@ -1128,19 +1128,7 @@ export const component: Tab.Component = { case SWUProposalStatus.EvaluatedCodeChallenge: case SWUProposalStatus.EvaluatedTeamScenario: case SWUProposalStatus.Awarded: - return component_.page.actions.links([ - { - children: "Withdraw", - symbol_: leftPlacement(iconLinkSymbol("ban")), - button: true, - outline: true, - color: "c-nav-fg-alt", - disabled, - loading: isWithdrawLoading, - onClick: () => - dispatch(adt("showModal", "withdrawAfterDeadline" as const)) - } - ]); + return component_.page.actions.none(); case SWUProposalStatus.Withdrawn: if (isAcceptingProposals) { return component_.page.actions.links([ diff --git a/src/front-end/typescript/lib/pages/proposal/team-with-us/edit/tab/proposal.tsx b/src/front-end/typescript/lib/pages/proposal/team-with-us/edit/tab/proposal.tsx index 4c0522269..d6febbb49 100644 --- a/src/front-end/typescript/lib/pages/proposal/team-with-us/edit/tab/proposal.tsx +++ b/src/front-end/typescript/lib/pages/proposal/team-with-us/edit/tab/proposal.tsx @@ -1146,19 +1146,7 @@ export const component: Tab.Component = { case TWUProposalStatus.EvaluatedResourceQuestions: case TWUProposalStatus.EvaluatedChallenge: case TWUProposalStatus.Awarded: - return component_.page.actions.links([ - { - children: "Withdraw", - symbol_: leftPlacement(iconLinkSymbol("ban")), - button: true, - outline: true, - color: "c-nav-fg-alt", - disabled, - loading: isWithdrawLoading, - onClick: () => - dispatch(adt("showModal", "withdrawAfterDeadline" as const)) - } - ]); + return component_.page.actions.none(); case TWUProposalStatus.Withdrawn: if (isAcceptingProposals) { return component_.page.actions.links([ diff --git a/src/shared/lib/resources/proposal/code-with-us.ts b/src/shared/lib/resources/proposal/code-with-us.ts index a179c23a9..2da5ed3cf 100644 --- a/src/shared/lib/resources/proposal/code-with-us.ts +++ b/src/shared/lib/resources/proposal/code-with-us.ts @@ -319,36 +319,29 @@ export function isValidStatusChange( case CWUProposalStatus.UnderReview: return ( - (([ - CWUProposalStatus.Evaluated, - CWUProposalStatus.Disqualified - ].includes(to) && - userType !== UserType.Vendor) || - (to === CWUProposalStatus.Withdrawn && - userType === UserType.Vendor)) && + [CWUProposalStatus.Evaluated, CWUProposalStatus.Disqualified].includes( + to + ) && + userType !== UserType.Vendor && hasProposalDeadlinePassed ); case CWUProposalStatus.Evaluated: return ( - (([ + [ CWUProposalStatus.Evaluated, CWUProposalStatus.Awarded, CWUProposalStatus.NotAwarded, CWUProposalStatus.Disqualified ].includes(to) && - userType !== UserType.Vendor) || - (to === CWUProposalStatus.Withdrawn && - userType === UserType.Vendor)) && + userType !== UserType.Vendor && hasProposalDeadlinePassed ); case CWUProposalStatus.Awarded: return ( - ((to === CWUProposalStatus.Disqualified && - userType !== UserType.Vendor) || - (to === CWUProposalStatus.Withdrawn && - userType === UserType.Vendor)) && + to === CWUProposalStatus.Disqualified && + userType !== UserType.Vendor && hasProposalDeadlinePassed ); diff --git a/src/shared/lib/resources/proposal/sprint-with-us.ts b/src/shared/lib/resources/proposal/sprint-with-us.ts index ff3a65c30..d99bf257b 100644 --- a/src/shared/lib/resources/proposal/sprint-with-us.ts +++ b/src/shared/lib/resources/proposal/sprint-with-us.ts @@ -425,77 +425,63 @@ export function isValidStatusChange( case SWUProposalStatus.UnderReviewTeamQuestions: return ( - (([ + [ SWUProposalStatus.EvaluatedTeamQuestions, SWUProposalStatus.Disqualified ].includes(to) && - userType !== UserType.Vendor) || - (to === SWUProposalStatus.Withdrawn && - userType === UserType.Vendor)) && + userType !== UserType.Vendor && hasProposalDeadlinePassed ); case SWUProposalStatus.EvaluatedTeamQuestions: return ( - ([ + [ SWUProposalStatus.UnderReviewCodeChallenge, SWUProposalStatus.Disqualified - ].includes(to) && - userType !== UserType.Vendor) || - (to === SWUProposalStatus.Withdrawn && userType === UserType.Vendor) + ].includes(to) && userType !== UserType.Vendor ); case SWUProposalStatus.UnderReviewCodeChallenge: return ( - ([ + [ SWUProposalStatus.EvaluatedCodeChallenge, SWUProposalStatus.Disqualified, SWUProposalStatus.EvaluatedTeamQuestions - ].includes(to) && - userType !== UserType.Vendor) || - (to === SWUProposalStatus.Withdrawn && userType === UserType.Vendor) + ].includes(to) && userType !== UserType.Vendor ); case SWUProposalStatus.EvaluatedCodeChallenge: return ( - ([ + [ SWUProposalStatus.UnderReviewTeamScenario, SWUProposalStatus.Disqualified - ].includes(to) && - userType !== UserType.Vendor) || - (to === SWUProposalStatus.Withdrawn && userType === UserType.Vendor) + ].includes(to) && userType !== UserType.Vendor ); case SWUProposalStatus.UnderReviewTeamScenario: return ( - ([ + [ SWUProposalStatus.EvaluatedTeamScenario, SWUProposalStatus.Disqualified, SWUProposalStatus.EvaluatedCodeChallenge - ].includes(to) && - userType !== UserType.Vendor) || - (to === SWUProposalStatus.Withdrawn && userType === UserType.Vendor) + ].includes(to) && userType !== UserType.Vendor ); case SWUProposalStatus.EvaluatedTeamScenario: return ( - (([ + [ SWUProposalStatus.Awarded, SWUProposalStatus.NotAwarded, SWUProposalStatus.Disqualified ].includes(to) && - userType !== UserType.Vendor) || - (to === SWUProposalStatus.Withdrawn && - userType === UserType.Vendor)) && + userType !== UserType.Vendor && hasProposalDeadlinePassed ); case SWUProposalStatus.Awarded: return ( - ((to === SWUProposalStatus.Disqualified && - userType !== UserType.Vendor) || - (to === SWUProposalStatus.Withdrawn && - userType === UserType.Vendor)) && + to === SWUProposalStatus.Disqualified && + userType !== UserType.Vendor && hasProposalDeadlinePassed ); diff --git a/src/shared/lib/resources/proposal/team-with-us.ts b/src/shared/lib/resources/proposal/team-with-us.ts index 228489453..4a1fe2c0e 100644 --- a/src/shared/lib/resources/proposal/team-with-us.ts +++ b/src/shared/lib/resources/proposal/team-with-us.ts @@ -317,56 +317,46 @@ export function isValidStatusChange( case TWUProposalStatus.UnderReviewResourceQuestions: return ( - (([ + [ TWUProposalStatus.EvaluatedResourceQuestions, TWUProposalStatus.Disqualified ].includes(to) && - userType !== UserType.Vendor) || - (to === TWUProposalStatus.Withdrawn && - userType === UserType.Vendor)) && + userType !== UserType.Vendor && hasProposalDeadlinePassed ); case TWUProposalStatus.EvaluatedResourceQuestions: return ( - ([ + [ TWUProposalStatus.UnderReviewChallenge, TWUProposalStatus.Disqualified - ].includes(to) && - userType !== UserType.Vendor) || - (to === TWUProposalStatus.Withdrawn && userType === UserType.Vendor) + ].includes(to) && userType !== UserType.Vendor ); case TWUProposalStatus.UnderReviewChallenge: return ( - ([ + [ TWUProposalStatus.EvaluatedChallenge, TWUProposalStatus.Disqualified, TWUProposalStatus.EvaluatedResourceQuestions - ].includes(to) && - userType !== UserType.Vendor) || - (to === TWUProposalStatus.Withdrawn && userType === UserType.Vendor) + ].includes(to) && userType !== UserType.Vendor ); case TWUProposalStatus.EvaluatedChallenge: return ( - (([ + [ TWUProposalStatus.Awarded, TWUProposalStatus.NotAwarded, TWUProposalStatus.Disqualified ].includes(to) && - userType !== UserType.Vendor) || - (to === TWUProposalStatus.Withdrawn && - userType === UserType.Vendor)) && + userType !== UserType.Vendor && hasProposalDeadlinePassed ); case TWUProposalStatus.Awarded: return ( - ((to === TWUProposalStatus.Disqualified && - userType !== UserType.Vendor) || - (to === TWUProposalStatus.Withdrawn && - userType === UserType.Vendor)) && + to === TWUProposalStatus.Disqualified && + userType !== UserType.Vendor && hasProposalDeadlinePassed );