diff --git a/src/back-end/lib/db/opportunity/team-with-us.ts b/src/back-end/lib/db/opportunity/team-with-us.ts
index 06565ea1c..e155e03c0 100644
--- a/src/back-end/lib/db/opportunity/team-with-us.ts
+++ b/src/back-end/lib/db/opportunity/team-with-us.ts
@@ -3,6 +3,7 @@ import {
Connection,
RawTWUOpportunitySubscriber,
readOneOrganizationContactEmail,
+ readOneServiceAreaByServiceAreaId,
readOneTWUAwardedProposal,
readSubmittedTWUProposalCount,
Transaction,
@@ -25,8 +26,9 @@ import {
TWUOpportunityHistoryRecord,
TWUOpportunitySlim,
TWUOpportunityStatus,
+ TWUResource,
TWUResourceQuestion,
- TWUServiceArea
+ ValidatedCreateTWUResourceBody
} from "shared/lib/resources/opportunity/team-with-us";
import { AuthenticatedSession, Session } from "shared/lib/resources/session";
import { User, UserType } from "shared/lib/resources/user";
@@ -37,6 +39,7 @@ import {
TWUProposalStatus
} from "shared/lib/resources/proposal/team-with-us";
import * as twuOpportunityNotifications from "back-end/lib/mailer/notifications/opportunity/team-with-us";
+import { ServiceAreaId } from "shared/lib/resources/service-area";
/**
* @remarks
@@ -45,6 +48,9 @@ import * as twuOpportunityNotifications from "back-end/lib/mailer/notifications/
* with Team With Us Opportunities
*/
+/**
+ * serviceArea is intentionally a number value for CreateTWUOpportunityParams, not an enum (backwards compatibility)
+ */
export interface CreateTWUOpportunityParams
extends Omit<
TWUOpportunity,
@@ -56,11 +62,11 @@ export interface CreateTWUOpportunityParams
| "id"
| "addenda"
| "resourceQuestions"
- | "serviceArea"
+ | "resources"
> {
status: CreateTWUOpportunityStatus;
resourceQuestions: CreateTWUResourceQuestionBody[];
- serviceArea: number;
+ resources: ValidatedCreateTWUResourceBody[];
}
interface UpdateTWUOpportunityParams
@@ -80,14 +86,17 @@ interface TWUOpportunityVersionRecord
opportunity: Id;
}
-interface TWUResourceRecord
- extends Pick<
- TWUOpportunity,
- "targetAllocation" | "optionalSkills" | "mandatorySkills"
- > {
+/**
+ * serviceArea is intentionally a number value here, not an enum (backwards compatibility)
+ */
+interface TWUResourceRecord {
id: Id;
+ serviceArea: ServiceAreaId;
opportunityVersion: Id;
- serviceArea: number;
+ mandatorySkills: string[];
+ optionalSkills: string[];
+ targetAllocation: number;
+ order: number;
}
interface TWUOpportunityStatusRecord {
@@ -99,25 +108,42 @@ interface TWUOpportunityStatusRecord {
note: string;
}
+/**
+ * Raw is a naming convention typically used to indicate that it handles data
+ * after a read action from the database
+ *
+ * @example
+ * resources, for instance only needs to be an array of ids to feed a subsequent db query
+ * for resources that match the array of ids passed to it.
+ */
export interface RawTWUOpportunity
extends Omit<
TWUOpportunity,
- "createdBy" | "updatedBy" | "attachments" | "addenda" | "resourceQuestions"
+ | "createdBy"
+ | "updatedBy"
+ | "attachments"
+ | "addenda"
+ | "resourceQuestions"
+ | "resources"
> {
createdBy?: Id;
updatedBy?: Id;
attachments: Id[];
addenda: Id[];
resourceQuestions: Id[];
+ resources: Id[];
versionId?: Id;
}
+/**
+ * @privateRemarks
+ * removed serviceArea 01/01/2024
+ */
export interface RawTWUOpportunitySlim
extends Omit
If you approve the membership, you can be included as a team - member on future Sprint With Us proposals submitted by{" "} - {organization.legalName}. + member on future Sprint With Us or Team With Us proposals + submitted by {organization.legalName}.
), @@ -124,7 +124,8 @@ export async function approvedRequestToJoinT({memberName} can now be included in proposals submitted by{" "} - {organizationName} to Sprint With Us opportunities. + {organizationName} to Sprint With Us or Team With Us + opportunities.
), @@ -184,7 +185,7 @@ export async function membershipCompleteT({organizationName} can now include you on proposals to Sprint With - Us opportunities. + Us or Team With Us opportunities.
), @@ -214,7 +215,8 @@ export async function memberLeavesT({memberName} has left {organizationName} {"'"}s team on the Digital Marketplace. They will no longer be - able to be included on proposals for Sprint With Us opportunities. + able to be included on proposals for Sprint With Us or Team With + Us opportunities.
), diff --git a/src/back-end/lib/mailer/notifications/opportunity/team-with-us.tsx b/src/back-end/lib/mailer/notifications/opportunity/team-with-us.tsx index 5d20027d6..ca73c4a56 100644 --- a/src/back-end/lib/mailer/notifications/opportunity/team-with-us.tsx +++ b/src/back-end/lib/mailer/notifications/opportunity/team-with-us.tsx @@ -190,13 +190,14 @@ export function makeTWUOpportunityInformation( opportunity: TWUOpportunity, showDueDate = true ): templates.DescriptionListProps { + const serviceAreas = opportunity.resources.map((resource, index) => ({ + name: "Service Area ".concat(`${index + 1}`), + value: `${startCase(lowerCase(resource.serviceArea))}` + })); const items = [ { name: "Type", value: "Team With Us" }, { name: "Value", value: `$${formatAmount(opportunity.maxBudget)}` }, - { - name: "Service Area", - value: `${startCase(lowerCase(opportunity.serviceArea))}` - }, + ...serviceAreas, { name: "Contract Start Date", value: formatDate(opportunity.startDate, false) diff --git a/src/back-end/lib/mailer/notifications/user.tsx b/src/back-end/lib/mailer/notifications/user.tsx index 11d41bd21..271617d97 100644 --- a/src/back-end/lib/mailer/notifications/user.tsx +++ b/src/back-end/lib/mailer/notifications/user.tsx @@ -52,7 +52,7 @@ export async function inviteToRegisterT( />{" "} for a Digital Marketplace account. Once you have signed up, you can join their team and be included in proposals to Sprint With Us - opportunities. + or Team With Us opportunities. ), diff --git a/src/back-end/lib/resources/opportunity/sprint-with-us.ts b/src/back-end/lib/resources/opportunity/sprint-with-us.ts index 114ac0772..13e128521 100644 --- a/src/back-end/lib/resources/opportunity/sprint-with-us.ts +++ b/src/back-end/lib/resources/opportunity/sprint-with-us.ts @@ -488,7 +488,7 @@ const create: crud.Create< const validatedMandatorySkills = genericValidation.validateMandatorySkills(mandatorySkills); const validatedOptionalSkills = - opportunityValidation.validateOptionalSkills(optionalSkills); + genericValidation.validateOptionalSkills(optionalSkills); const validatedDescription = genericValidation.validateDescription(description); const validatedQuestionsWeight = @@ -1077,7 +1077,7 @@ const update: crud.Update< const validatedMandatorySkills = genericValidation.validateMandatorySkills(mandatorySkills); const validatedOptionalSkills = - opportunityValidation.validateOptionalSkills(optionalSkills); + genericValidation.validateOptionalSkills(optionalSkills); const validatedDescription = genericValidation.validateDescription(description); const validatedQuestionsWeight = @@ -1295,7 +1295,7 @@ const update: crud.Update< genericValidation.validateMandatorySkills( validatedSWUOpportunity.value.mandatorySkills ), - opportunityValidation.validateOptionalSkills( + genericValidation.validateOptionalSkills( validatedSWUOpportunity.value.optionalSkills ), genericValidation.validateDescription( diff --git a/src/back-end/lib/resources/opportunity/team-with-us.ts b/src/back-end/lib/resources/opportunity/team-with-us.ts index 63f5cd808..5fdb3ef40 100644 --- a/src/back-end/lib/resources/opportunity/team-with-us.ts +++ b/src/back-end/lib/resources/opportunity/team-with-us.ts @@ -11,7 +11,7 @@ import { import { validateAttachments, validateTWUOpportunityId, - validateServiceArea + validateTWUResources } from "back-end/lib/validation"; import { get, omit } from "lodash"; import { addDays, getNumber, getString, getStringArray } from "shared/lib"; @@ -21,6 +21,7 @@ import { CreateTWUOpportunityStatus, CreateTWUResourceQuestionBody, CreateTWUResourceQuestionValidationErrors, + CreateTWUResourceValidationErrors, CreateValidationErrors, DeleteValidationErrors, isValidStatusChange, @@ -28,7 +29,9 @@ import { TWUOpportunitySlim, TWUOpportunityStatus, UpdateRequestBody, - UpdateValidationErrors + UpdateValidationErrors, + CreateTWUResourceBody, + ValidatedCreateTWUResourceBody } from "shared/lib/resources/opportunity/team-with-us"; import { AuthenticatedSession, Session } from "shared/lib/resources/session"; import { @@ -46,6 +49,10 @@ import * as genericValidation from "shared/lib/validation/opportunity/utility"; import * as twuOpportunityNotifications from "back-end/lib/mailer/notifications/opportunity/team-with-us"; import { ADT, adt, Id } from "shared/lib/types"; +/** + * @remarks + * serviceArea is intentionally declared as a number here, not an enum (backwards compatibility) + */ interface ValidatedCreateRequestBody extends Omit< TWUOpportunity, @@ -59,14 +66,14 @@ interface ValidatedCreateRequestBody | "history" | "publishedAt" | "subscribed" + | "resources" | "resourceQuestions" | "challengeEndDate" - | "serviceArea" > { + resources: ValidatedCreateTWUResourceBody[]; status: CreateTWUOpportunityStatus; session: AuthenticatedSession; resourceQuestions: CreateTWUResourceQuestionBody[]; - serviceArea: number; } interface ValidatedUpdateRequestBody { @@ -81,17 +88,22 @@ interface ValidatedUpdateRequestBody { | ADT<"addAddendum", string>; } +/** + * @remarks + * serviceArea is intentionally a number here (via TWUResource[]), not an enum (backwards compatibility) + * @see ValidatedCreateRequestBody + */ type ValidatedUpdateEditRequestBody = Omit< ValidatedCreateRequestBody, - "status" | "session" | "serviceArea" -> & { serviceArea: number }; + "status" | "session" +>; type CreateRequestBody = Omit< SharedCreateRequestBody, - "status" | "serviceArea" + "status" | "resources" > & { + resources: CreateTWUResourceBody[]; status: string; - serviceArea: string; }; type ValidatedDeleteRequestBody = Id; @@ -176,11 +188,12 @@ const readOne: crud.ReadOne
diff --git a/src/front-end/typescript/lib/pages/opportunity/sprint-with-us/lib/components/form.tsx b/src/front-end/typescript/lib/pages/opportunity/sprint-with-us/lib/components/form.tsx
index 1631545d9..5ec284a7c 100644
--- a/src/front-end/typescript/lib/pages/opportunity/sprint-with-us/lib/components/form.tsx
+++ b/src/front-end/typescript/lib/pages/opportunity/sprint-with-us/lib/components/form.tsx
@@ -359,7 +359,7 @@ export const init: component_.base.Init
To submit a proposal for this opportunity, you must possess the
following skills:
Additionally, possessing the following skills would be considered
a bonus:
{text}
+ Resource {index + 1}
+ {state.removeable ? (
+ disabled ? null : (
+ dispatch(adt("deleteResource", index))}>
+ Delete
+
+ )
+ ) : null}
+ Details
+
- Proponents take note of the following pricing rules and - requirements: -
-- Please provide the hourly rate you are proposing for this - opportunity. -
- -+ + {resource.user.user.name} +
+ ) : ( + EMPTY_STRING + )} + ++ {resource.hourlyRate + ? formatAmount(resource.hourlyRate, "$") + : EMPTY_STRING} +
+ + +{twuServiceAreaToTitleCase(resource.serviceArea)}
+ ++ {resource.targetAllocation}% +
+ +- To satisfy this opportunity{"'"}s requirements, your team member must - only consist of confirmed (non-pending) members of the selected - organization. -
- {disabled ? null : ( - 0 ? "pt-4 mt-2 border-top" : ""}> +{twuServiceAreaToTitleCase(member.resource.serviceArea)}
+ +{member.resource.targetAllocation}%
+ +- Select the team member that you want to propose to be part of - your team for this opportunity. If you do not see the team - member that you want to add, you must send them a{" "} - - request to join your organization - - . -
- {nonAddedMembers.length ? ( -+ To satisfy this opportunity's requirements, your team member + must only consist of confirmed (non-pending) {memberText} of the + selected organization. +
++ Proponents take note of the following pricing rules and + requirements: +
++ Please provide the hourly rate you are proposing for this + opportunity. +
+ +