Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
72520e5
refactor: move evaluation panel component
IanFonzie Mar 26, 2025
2c49647
feat: add change evaluation panel status check helper
IanFonzie Mar 26, 2025
2a02164
feat: add evaluation panel tab for opportunity
IanFonzie Mar 26, 2025
894e905
feat: connect panel evaluation tab to opportunity
IanFonzie Mar 26, 2025
5a18745
feat: edit evaluation panel permission
IanFonzie Mar 28, 2025
8e34aeb
feat: add adt for editing evaluation panel directly
IanFonzie Mar 28, 2025
1a3ff48
feat: save evaluation panel changes from evaluation panel tab
IanFonzie Mar 28, 2025
c78ec70
refactor: generate swu team question response evaluations from most c…
IanFonzie Apr 2, 2025
929d2ef
feat: port old evaluations
IanFonzie Apr 2, 2025
c3e190f
refactor: allow panel changes up to consensus
IanFonzie Apr 2, 2025
280a8fd
refactor: check all submitted at opportunity level
IanFonzie Apr 3, 2025
fb5631a
fix: cast returned counts from all evaluations submitted query
IanFonzie Apr 3, 2025
316234c
refactor: remove references to proposal evaluation statuses
IanFonzie Apr 3, 2025
1f59ef7
refactor: remove proposal evaluation statuses from migrations
IanFonzie Apr 4, 2025
fe7873b
refactor: remove references to unused opportunity evaluation statuses
IanFonzie Apr 4, 2025
74dca82
refactor: remove unused opportunity evaluation statuses from migrations
IanFonzie Apr 4, 2025
4a4e9e4
feat: add ability to edit consensus after submission
IanFonzie Apr 4, 2025
196fca5
refactor: remove redundant aliases
IanFonzie Apr 15, 2025
027b469
fix: await permissions check
IanFonzie Apr 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 126 additions & 19 deletions src/back-end/lib/db/evaluations/sprint-with-us/team-questions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import {
RawSWUEvaluationPanelMember,
readOneSWUEvaluationPanelMember,
readOneSWUOpportunity
} from "back-end/lib/db/opportunity/sprint-with-us";
import { getValidValue, isInvalid, valid } from "shared/lib/validation";
import { Connection, readOneSWUProposalSlim, tryDb } from "back-end/lib/db";
import {
Connection,
readOneSWUProposalSlim,
Transaction,
tryDb
} from "back-end/lib/db";
import {
AuthenticatedSession,
Session,
Expand Down Expand Up @@ -380,6 +386,80 @@ export const updateSWUTeamQuestionResponseEvaluation = tryDb<
);
});

/**
* All evaluations have been submitted when every evaluator has evaluated every
* question for every proponent
*/
export async function allSWUTeamQuestionResponseEvaluatorEvaluationsSubmitted(
connection: Connection,
trx: Transaction,
opportunityId: string,
proposalsCount: number
) {
const [
[{ count: submittedEvaluatorEvaluationsCount }],
[{ count: evaluatorsCount }],
[{ count: questionsCount }]
] = await Promise.all([
generateSWUTeamQuestionResponseEvaluationQuery(connection)
.transacting(trx)
.clearSelect()
.where({
"statuses.status": SWUTeamQuestionResponseEvaluationStatus.Submitted
})
.count("*"),
// Evaluators for the most recent version
connection<RawSWUEvaluationPanelMember>(
"swuEvaluationPanelMembers as members"
)
.transacting(trx)
.join(
connection.raw(
"(??) as versions",
connection("swuOpportunityVersions")
.select("opportunity", "id")
.rowNumber("rn", function () {
this.orderBy("createdAt", "desc").partitionBy("opportunity");
})
),
function () {
this.on("members.opportunityVersion", "=", "versions.id");
}
)
.where({
evaluator: true,
"versions.opportunity": opportunityId,
"versions.rn": 1
})
.count("*"),
// Questions for the most recent version
connection<RawSWUEvaluationPanelMember>("swuTeamQuestions as questions")
.transacting(trx)
.join(
connection.raw(
"(??) as versions",
connection("swuOpportunityVersions")
.select("opportunity", "id")
.rowNumber("rn", function () {
this.orderBy("createdAt", "desc").partitionBy("opportunity");
})
),
function () {
this.on("questions.opportunityVersion", "=", "versions.id");
}
)
.where({
"versions.opportunity": opportunityId,
"versions.rn": 1
})
.count("*")
]);
return (
Number(submittedEvaluatorEvaluationsCount) ===
Number(evaluatorsCount) * proposalsCount * Number(questionsCount)
);
}

function generateSWUTeamQuestionResponseEvaluationQuery(
connection: Connection,
consensus = false
Expand All @@ -391,24 +471,47 @@ function generateSWUTeamQuestionResponseEvaluationQuery(
? CHAIR_EVALUATION_STATUS_TABLE_NAME
: EVALUATOR_EVALUATION_STATUS_TABLE_NAME;
const query = connection(`${evaluationTableName} as evaluations`)
.join(`${evaluationStatusTableName} as statuses`, function () {
this.on(
"evaluations.evaluationPanelMember",
"=",
"statuses.evaluationPanelMember"
)
.andOn("evaluations.proposal", "=", "statuses.proposal")
.andOnNotNull("statuses.status")
.andOn(
"statuses.createdAt",
.join(
connection.raw(
"(??) as statuses",
connection(`${evaluationStatusTableName}`)
.select("evaluationPanelMember", "proposal", "status", "createdAt")
.rowNumber("rn", function () {
this.orderBy("createdAt", "desc").partitionBy([
"evaluationPanelMember",
"proposal"
]);
})
),
function () {
this.on(
"evaluations.evaluationPanelMember",
"=",
connection.raw(
`(select max("createdAt") from "${evaluationStatusTableName}" as statuses2 where
statuses2."evaluationPanelMember" = evaluations."evaluationPanelMember" and statuses2."proposal" = evaluations."proposal"
and statuses2.status is not null)`
)
);
})
"statuses.evaluationPanelMember"
)
.andOn("evaluations.proposal", "=", "statuses.proposal")
.andOn("statuses.rn", "=", connection.raw(1));
}
)
.join("swuProposals as proposals", "evaluations.proposal", "proposals.id")
.join(
"swuEvaluationPanelMembers as members",
"evaluations.evaluationPanelMember",
"members.user"
)
.join(
connection.raw(
"(??) as versions",
connection("swuOpportunityVersions")
.select("opportunity", "id")
.rowNumber("rn", function () {
this.orderBy("createdAt", "desc").partitionBy("opportunity");
})
),
function () {
this.on("proposals.opportunity", "=", "versions.opportunity");
}
)
.select<RawSWUTeamQuestionResponseEvaluation[]>(
"evaluations.proposal",
"evaluations.evaluationPanelMember",
Expand All @@ -420,7 +523,11 @@ function generateSWUTeamQuestionResponseEvaluationQuery(
"evaluations.notes",
"statuses.status",
"statuses.createdAt"
);
)
.where({
"members.opportunityVersion": connection.raw("versions.id"),
"versions.rn": 1
});

return query;
}
110 changes: 53 additions & 57 deletions src/back-end/lib/db/opportunity/sprint-with-us.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { generateUuid } from "back-end/lib";
import {
allSWUTeamQuestionResponseEvaluatorEvaluationsSubmitted,
CHAIR_EVALUATION_STATUS_TABLE_NAME,
CHAIR_EVALUATION_TABLE_NAME,
Connection,
Expand All @@ -13,11 +14,8 @@ import {
import { readOneFileById } from "back-end/lib/db/file";
import { readOneOrganizationContactEmail } from "back-end/lib/db/organization";
import {
allIndividualSWUTeamQuestionResponseEvaluationsComplete,
readManySWUProposals,
readOneSWUAwardedProposal,
readSubmittedSWUProposalCount,
updateSWUProposalStatus
readSubmittedSWUProposalCount
} from "back-end/lib/db/proposal/sprint-with-us";
import { RawSWUOpportunitySubscriber } from "back-end/lib/db/subscribers/sprint-with-us";
import { readOneUser, readOneUserSlim } from "back-end/lib/db/user";
Expand Down Expand Up @@ -1215,6 +1213,25 @@ export const updateSWUOpportunityVersion = tryDb<
...restOfOpportunity
} = opportunity;
const opportunityVersion = await connection.transaction(async (trx) => {
const prevPanel: RawSWUEvaluationPanelMember[] =
await connection<RawSWUEvaluationPanelMember>(
"swuEvaluationPanelMembers as sepm"
)
.select("sepm.*")
.join(
"swuOpportunityVersions as sov",
"sepm.opportunityVersion",
"=",
"sov.id"
)
.where(
"sov.createdAt",
"=",
connection<Date>("swuOpportunityVersions as sov2")
.max("createdAt")
.where("sov2.opportunity", "=", restOfOpportunity.id)
);

const [versionRecord] = await connection<SWUOpportunityVersionRecord>(
"swuOpportunityVersions"
)
Expand Down Expand Up @@ -1285,12 +1302,30 @@ export const updateSWUOpportunityVersion = tryDb<

// Create evaluation panel
for (const member of evaluationPanel) {
await connection<RawSWUEvaluationPanelMember>("swuEvaluationPanelMembers")
.transacting(trx)
.insert({
...member,
opportunityVersion: versionRecord.id
});
const prevMember = prevPanel.find(({ user }) => user === member.user);
if (prevMember) {
await connection<RawSWUEvaluationPanelMember>(
"swuEvaluationPanelMembers"
)
.transacting(trx)
.where({
user: prevMember.user,
opportunityVersion: prevMember.opportunityVersion
})
.update({
...member,
opportunityVersion: versionRecord.id
});
} else {
await connection<RawSWUEvaluationPanelMember>(
"swuEvaluationPanelMembers"
)
.transacting(trx)
.insert({
...member,
opportunityVersion: versionRecord.id
});
}
}

// Add an 'edit' change record
Expand Down Expand Up @@ -1467,12 +1502,12 @@ export const closeSWUOpportunities = tryDb<[], number>(async (connection) => {
)?.map((result) => result.id) || [];

for (const [index, proposalId] of proposalIds.entries()) {
// Set the proposal to EVALUATION_QUESTIONS_INDIVIDUAL status
// Set the proposal to UNDER_REVIEW_QUESTIONS status
await connection("swuProposalStatuses").transacting(trx).insert({
id: generateUuid(),
createdAt: now,
proposal: proposalId,
status: SWUProposalStatus.EvaluationTeamQuestionsIndividual,
status: SWUProposalStatus.UnderReviewTeamQuestions,
note: ""
});

Expand Down Expand Up @@ -1698,44 +1733,17 @@ export const submitIndividualQuestionEvaluations = tryDb<
if (!statusRecord) {
throw new Error("unable to update team question evaluation");
}

if (
await allIndividualSWUTeamQuestionResponseEvaluationsComplete(
connection,
trx,
proposalId,
id
)
) {
const result = await updateSWUProposalStatus(
connection,
proposalId,
SWUProposalStatus.EvaluationTeamQuestionsConsensus,
"",
session
);
if (isInvalid(result)) {
throw new Error("unable to update proposal");
}
}
}
)
);

// Update opportunity status if all evaluations complete for proposal
const updatedSWUProposals = getValidValue(
await readManySWUProposals(connection, session, id),
undefined
);
if (!updatedSWUProposals) {
throw new Error("unable to read proposals");
}

// Update opportunity status if all evaluations complete
if (
updatedSWUProposals.every(
(proposal) =>
proposal.status !==
SWUProposalStatus.EvaluationTeamQuestionsIndividual
await allSWUTeamQuestionResponseEvaluatorEvaluationsSubmitted(
connection,
trx,
id,
evaluationParams.evaluations.map(({ proposal }) => proposal.id).length
)
) {
const result = await updateSWUOpportunityStatus(
Expand Down Expand Up @@ -1812,18 +1820,6 @@ export const submitConsensusQuestionEvaluations = tryDb<
}
)
);

const result = await updateSWUOpportunityStatus(
connection,
id,
SWUOpportunityStatus.EvaluationTeamQuestionsReview,
"",
session
);

if (!result) {
throw new Error("unable to update opportunity");
}
});

const dbResult = await readOneSWUOpportunity(connection, id, session);
Expand Down
Loading
Loading