diff --git a/src/CareTogether.Api/OData/LiveODataModelController.cs b/src/CareTogether.Api/OData/LiveODataModelController.cs index 1c3149df..bbcf28e0 100644 --- a/src/CareTogether.Api/OData/LiveODataModelController.cs +++ b/src/CareTogether.Api/OData/LiveODataModelController.cs @@ -41,16 +41,19 @@ public sealed record Address( public sealed record FamilyRoleApproval( [property: ForeignKey("FamilyId")] Family Family, [property: Key] Guid FamilyId, [property: ForeignKey("RoleName")] Role Role, [property: Key] string RoleName, - ImmutableList>? ApprovalStatusRanges); + [property: Key] DateOnly Start, [property: Key] DateOnly End, RoleApprovalStatus? Status); public sealed record IndividualRoleApproval( [property: ForeignKey("PersonId")] Person Person, [property: Key] Guid PersonId, [property: ForeignKey("RoleName")] Role Role, [property: Key] string RoleName, - ImmutableList>? ApprovalStatusRanges); + [property: ForeignKey("FamilyId")] Family Family, Guid FamilyId, + [property: Key] DateOnly Start, [property: Key] DateOnly End, RoleApprovalStatus? Status); - public sealed record IndividualRemovedRole( + public sealed record FamilyRoleRemovedIndividual( [property: ForeignKey("PersonId")] Person Person, [property: Key] Guid PersonId, - [property: ForeignKey("RoleName")] Role Role, [property: Key] string RoleName); + [property: ForeignKey("RoleName")] Role Role, [property: Key] string RoleName, + [property: ForeignKey("FamilyId")] Family Family, Guid FamilyId, + [property: Key] DateOnly Start, [property: Key] DateOnly End); public sealed record Role([property: Key] string Name); @@ -93,6 +96,7 @@ public sealed record LiveModel(IEnumerable Locations, IEnumerable Roles, IEnumerable FamilyRoleApprovals, IEnumerable IndividualRoleApprovals, + IEnumerable FamilyRoleRemovedIndividuals, IEnumerable Referrals, IEnumerable Arrangements, IEnumerable ArrangementTypes, @@ -162,6 +166,13 @@ public async Task> GetIndividualRoleApproval return liveModel.IndividualRoleApprovals; } + [HttpGet("FamilyRoleRemovedIndividuals")] + public async Task> GetFamilyRoleRemovedIndividualsAsync() + { + var liveModel = await RenderLiveModelAsync(); + return liveModel.FamilyRoleRemovedIndividuals; + } + [HttpGet("Referral")] public async Task> GetReferralsAsync() { @@ -259,6 +270,8 @@ private async Task RenderLiveModelInternalAsync(Guid organizationId) .SelectMany(x => RenderFamilyRoleApprovals(x.Item1, x.Item2, roles)).ToArray(); var individualRoleApprovals = familiesWithInfo .SelectMany(x => RenderIndividualRoleApprovals(x.Item1, x.Item2, people, roles)).ToArray(); + var familyRoleRemovedIndividuals = familiesWithInfo + .SelectMany(x => RenderFamilyRoleRemovedIndividuals(x.Item1, x.Item2, people, roles)).ToArray(); var referrals = familiesWithInfo.SelectMany(x => RenderReferrals(x.Item1, x.Item2)).ToArray(); @@ -277,7 +290,7 @@ private async Task RenderLiveModelInternalAsync(Guid organizationId) var individualFunctionAssignments = familiesWithInfo.SelectMany(x => RenderIndividualFunctionAssignments(x.Item1, x.Item2, families, people, arrangements)).ToArray(); return new LiveModel(locations, families, people, - roles, familyRoleApprovals, individualRoleApprovals, + roles, familyRoleApprovals, individualRoleApprovals, familyRoleRemovedIndividuals, referrals, arrangements, arrangementTypes, childLocationRecords, familyFunctionAssignments, individualFunctionAssignments); } @@ -345,12 +358,10 @@ private static IEnumerable RenderFamilyRoleApprovals( CombinedFamilyInfo familyInfo, Family family, Role[] roles) { return familyInfo.VolunteerFamilyInfo?.FamilyRoleApprovals - .Select(fra => - new FamilyRoleApproval(family, family.Id, + .SelectMany(fra => fra.Value.EffectiveRoleApprovalStatus?.Ranges + .Select(range => new FamilyRoleApproval(family, family.Id, roles.Single(role => role.Name == fra.Key), fra.Key, - fra.Value.EffectiveRoleApprovalStatus?.Ranges)) - ?? Enumerable.Empty(); - //TODO: Include *when approval began* (requires returning *all* the approvals and adding a 'Since' date property!) + range.Start, range.End, range.Tag)) ?? []) ?? []; } private static IEnumerable RenderIndividualRoleApprovals( @@ -358,12 +369,26 @@ private static IEnumerable RenderIndividualRoleApprovals { return familyInfo.VolunteerFamilyInfo?.IndividualVolunteers .SelectMany(individual => individual.Value.ApprovalStatusByRole - .Select(ira => - new IndividualRoleApproval( + .SelectMany(ira => ira.Value.EffectiveRoleApprovalStatus?.Ranges + .Select(range => new IndividualRoleApproval( people.Single(person => person.Id == individual.Key), individual.Key, roles.Single(role => role.Name == ira.Key), ira.Key, - ira.Value.EffectiveRoleApprovalStatus?.Ranges))) - ?? Enumerable.Empty(); + family, family.Id, + range.Start, range.End, range.Tag)) ?? []) ?? []) ?? []; + } + + private static IEnumerable RenderFamilyRoleRemovedIndividuals( + CombinedFamilyInfo familyInfo, Family family, Person[] people, Role[] roles) + { + return familyInfo.VolunteerFamilyInfo?.IndividualVolunteers + .SelectMany(individual => individual.Value.RoleRemovals + .Where(removal => + familyInfo.VolunteerFamilyInfo.FamilyRoleApprovals.Keys.Contains(removal.RoleName)) + .Select(removal => new FamilyRoleRemovedIndividual( + people.Single(person => person.Id == individual.Key), individual.Key, + roles.Single(role => role.Name == removal.RoleName), removal.RoleName, + family, family.Id, + removal.EffectiveSince, removal.EffectiveUntil ?? DateOnly.MaxValue))) ?? []; } private static IEnumerable RenderReferrals( diff --git a/src/CareTogether.Api/OData/ODataModelProvider.cs b/src/CareTogether.Api/OData/ODataModelProvider.cs index 00e8b628..78953283 100644 --- a/src/CareTogether.Api/OData/ODataModelProvider.cs +++ b/src/CareTogether.Api/OData/ODataModelProvider.cs @@ -17,7 +17,7 @@ public static IEdmModel GetLiveEdmModel() builder.EntitySet("Role"); builder.EntitySet("FamilyRoleApprovals"); builder.EntitySet("IndividualRoleApprovals"); - builder.EntitySet("IndividualRemovedRoles"); + builder.EntitySet("FamilyRoleRemovedIndividuals"); builder.EntitySet("Referral"); builder.EntitySet("Arrangement"); builder.EntitySet("ArrangementType"); diff --git a/src/CareTogether.Core/Engines/PolicyEvaluation/FamilyApprovalCalculations.cs b/src/CareTogether.Core/Engines/PolicyEvaluation/FamilyApprovalCalculations.cs index f4c7091e..696a81f7 100644 --- a/src/CareTogether.Core/Engines/PolicyEvaluation/FamilyApprovalCalculations.cs +++ b/src/CareTogether.Core/Engines/PolicyEvaluation/FamilyApprovalCalculations.cs @@ -23,15 +23,13 @@ internal static ImmutableDictionary ImmutableDictionary> individualRoleRemovals) { var allFamilyRoleApprovals = volunteerFamilyRoles - .Where(rolePolicy => - !familyRoleRemovals.Any(x => x.RoleName == rolePolicy.Key)) .ToImmutableDictionary( rolePolicy => rolePolicy.Key, rolePolicy => CalculateFamilyRoleApprovalStatus( rolePolicy.Key, rolePolicy.Value, family, completedFamilyRequirements, exemptedFamilyRequirements, - familyRoleRemovals, + familyRoleRemovals.Where(role => role.RoleName == rolePolicy.Key).ToImmutableList(), completedIndividualRequirements, exemptedIndividualRequirements, individualRoleRemovals)); @@ -43,7 +41,7 @@ internal static FamilyRoleApprovalStatus string roleName, VolunteerFamilyRolePolicy rolePolicy, Family family, ImmutableList completedFamilyRequirements, ImmutableList exemptedFamilyRequirements, - ImmutableList familyRoleRemovals, + ImmutableList removalsOfThisRole, ImmutableDictionary> completedIndividualRequirements, ImmutableDictionary> exemptedIndividualRequirements, ImmutableDictionary> individualRoleRemovals) @@ -53,7 +51,7 @@ internal static FamilyRoleApprovalStatus CalculateFamilyRoleVersionApprovalStatus( roleName, policyVersion, family, completedFamilyRequirements, exemptedFamilyRequirements, - familyRoleRemovals, + removalsOfThisRole, completedIndividualRequirements, exemptedIndividualRequirements, individualRoleRemovals)) .ToImmutableList(); @@ -73,7 +71,7 @@ internal static FamilyRoleVersionApprovalStatus Family family, ImmutableList completedFamilyRequirements, ImmutableList exemptedFamilyRequirements, - ImmutableList familyRoleRemovals, + ImmutableList removalsOfThisRole, ImmutableDictionary> completedIndividualRequirements, ImmutableDictionary> exemptedIndividualRequirements, ImmutableDictionary> individualRoleRemovals) @@ -112,8 +110,7 @@ internal static FamilyRoleVersionApprovalStatus SharedCalculations.CalculateRoleVersionApprovalStatus( requirementCompletions .Select(x => (x.Stage, x.WhenMet)).ToImmutableList(), - familyRoleRemovals - .Where(x => x.RoleName == roleName).ToImmutableList()); + removalsOfThisRole); return new FamilyRoleVersionApprovalStatus( policyVersion.Version, roleVersionApprovalStatus, diff --git a/src/caretogether-pwa/src/Families/AdultCard.tsx b/src/caretogether-pwa/src/Families/AdultCard.tsx index fb357094..d55f57dd 100644 --- a/src/caretogether-pwa/src/Families/AdultCard.tsx +++ b/src/caretogether-pwa/src/Families/AdultCard.tsx @@ -81,11 +81,11 @@ export function AdultCard({ familyId, personId }: AdultCardProps) { const participatingFamilyRoles = Object.entries(family.volunteerFamilyInfo?.familyRoleApprovals || {}).filter( - ([role,]) => !family.volunteerFamilyInfo?.individualVolunteers?.[personId]?.removedRoles?.find(x => x.roleName === role)); + ([role,]) => !family.volunteerFamilyInfo?.individualVolunteers?.[personId]?.roleRemovals?.find(x => x.roleName === role)); const participatingIndividualRoles = - Object.entries(family.volunteerFamilyInfo?.individualVolunteers?.[personId]?.individualRoleApprovals || {}).filter( - ([role,]) => !family.volunteerFamilyInfo?.individualVolunteers?.[personId]?.removedRoles?.find(x => x.roleName === role)); - const removedRoles = family.volunteerFamilyInfo?.individualVolunteers?.[personId]?.removedRoles || []; + Object.entries(family.volunteerFamilyInfo?.individualVolunteers?.[personId]?.approvalStatusByRole || {}).filter( + ([role,]) => !family.volunteerFamilyInfo?.individualVolunteers?.[personId]?.roleRemovals?.find(x => x.roleName === role)); + const removedRoles = family.volunteerFamilyInfo?.individualVolunteers?.[personId]?.roleRemovals || []; return <>{adult?.item1 && adult.item1.id && adult.item2 && @@ -130,7 +130,7 @@ export function AdultCard({ familyId, personId }: AdultCardProps) { }} component="div"> {Object.entries(family.volunteerFamilyInfo?.individualVolunteers?.[adult.item1.id].approvalStatusByRole || {}).map(([role, roleApprovalStatus]) => )} - {(family.volunteerFamilyInfo?.individualVolunteers?.[personId]?.removedRoles || []).map(removedRole => + {(family.volunteerFamilyInfo?.individualVolunteers?.[personId]?.roleRemovals || []).map(removedRole => )} {(adult.item2.relationshipToFamily && ) || null} {adult.item2.isInHousehold && } diff --git a/src/caretogether-pwa/src/Families/FamilyScreen.tsx b/src/caretogether-pwa/src/Families/FamilyScreen.tsx index a951bdce..7851fa3c 100644 --- a/src/caretogether-pwa/src/Families/FamilyScreen.tsx +++ b/src/caretogether-pwa/src/Families/FamilyScreen.tsx @@ -72,7 +72,7 @@ export function FamilyScreen() { const participatingFamilyRoles = Object.entries(family?.volunteerFamilyInfo?.familyRoleApprovals || {}).filter( - ([role,]) => !family?.volunteerFamilyInfo?.removedRoles?.find(x => x.roleName === role)); + ([role,]) => !family?.volunteerFamilyInfo?.roleRemovals?.find(x => x.roleName === role)); const [removeRoleParameter, setRemoveRoleParameter] = useState<{ volunteerFamilyId: string, role: string } | null>(null); function selectRemoveRole(role: string) { @@ -180,8 +180,8 @@ export function FamilyScreen() { } {permissions(Permission.EditVolunteerRoleParticipation) && (participatingFamilyRoles.length > 0 || - (family.volunteerFamilyInfo?.removedRoles && - family.volunteerFamilyInfo.removedRoles.length > 0)) && + (family.volunteerFamilyInfo?.roleRemovals && + family.volunteerFamilyInfo.roleRemovals.length > 0)) && setFamilyMoreMenuAnchor(event.currentTarget)} size="large"> @@ -200,7 +200,7 @@ export function FamilyScreen() { ))} {permissions(Permission.EditVolunteerRoleParticipation) && - (family.volunteerFamilyInfo?.removedRoles || []).map(removedRole => ( + (family.volunteerFamilyInfo?.roleRemovals || []).map(removedRole => ( selectResetRole(removedRole.roleName!, removedRole.reason!, removedRole.additionalComments!)}> @@ -335,7 +335,7 @@ export function FamilyScreen() { }}> {Object.entries(family.volunteerFamilyInfo?.familyRoleApprovals || {}).flatMap(([role, roleApprovalStatus]) => )} - {(family.volunteerFamilyInfo?.removedRoles || []).map(removedRole => + {(family.volunteerFamilyInfo?.roleRemovals || []).map(removedRole => )} diff --git a/src/caretogether-pwa/src/Referrals/Arrangements/AssignArrangementFunctionDialog.tsx b/src/caretogether-pwa/src/Referrals/Arrangements/AssignArrangementFunctionDialog.tsx index 3f509136..5ae5c396 100644 --- a/src/caretogether-pwa/src/Referrals/Arrangements/AssignArrangementFunctionDialog.tsx +++ b/src/caretogether-pwa/src/Referrals/Arrangements/AssignArrangementFunctionDialog.tsx @@ -27,7 +27,7 @@ export function AssignArrangementFunctionDialog({ }: AssignArrangementFunctionDialogProps) { const familyIdMaybe = useParams<{ familyId: string }>(); const familyId = familyIdMaybe.familyId as string; - + const visibleFamilies = useRecoilValue(visibleFamiliesQuery); const familyAndPersonLookup = usePersonAndFamilyLookup(); @@ -41,26 +41,26 @@ export function AssignArrangementFunctionDialog({ const candidateVolunteerIndividualAssignees = arrangementFunction.eligibleIndividualVolunteerRoles ? visibleFamilies.flatMap(f => f.volunteerFamilyInfo?.individualVolunteers ? Object.entries(f.volunteerFamilyInfo?.individualVolunteers).filter(([volunteerId, _]) => - f.family!.adults!.find(a => a.item1!.id === volunteerId)!.item1!.active).flatMap(([volunteerId, volunteerInfo]) => volunteerInfo.individualRoleApprovals - ? Object.entries(volunteerInfo.individualRoleApprovals).flatMap(([roleName, roleVersionApproval]) => - arrangementFunction.eligibleIndividualVolunteerRoles!.find(x => x === roleName) && - roleVersionApproval.find(rva => rva.approvalStatus === RoleApprovalStatus.Approved || rva.approvalStatus === RoleApprovalStatus.Onboarded) && - !arrangement.individualVolunteerAssignments?.find(iva => - iva.arrangementFunction === arrangementFunction.functionName && iva.familyId === f.family!.id && iva.personId === volunteerId) - ? [{ family: f.family!, person: f.family!.adults!.find(a => a.item1!.id === volunteerId)!.item1 || null }] + f.family!.adults!.find(a => a.item1!.id === volunteerId)!.item1!.active).flatMap(([volunteerId, volunteerInfo]) => volunteerInfo.approvalStatusByRole + ? Object.entries(volunteerInfo.approvalStatusByRole).flatMap(([roleName, roleApprovalStatus]) => + arrangementFunction.eligibleIndividualVolunteerRoles!.find(x => x === roleName) && + (roleApprovalStatus.currentStatus === RoleApprovalStatus.Approved || roleApprovalStatus.currentStatus === RoleApprovalStatus.Onboarded) && + !arrangement.individualVolunteerAssignments?.find(iva => + iva.arrangementFunction === arrangementFunction.functionName && iva.familyId === f.family!.id && iva.personId === volunteerId) + ? [{ family: f.family!, person: f.family!.adults!.find(a => a.item1!.id === volunteerId)!.item1 || null }] + : []) : []) - : []) : []) : []; const candidateVolunteerFamilyAssignees = arrangementFunction.eligibleVolunteerFamilyRoles ? visibleFamilies.flatMap(f => f.volunteerFamilyInfo?.familyRoleApprovals - ? Object.entries(f.volunteerFamilyInfo.familyRoleApprovals).flatMap(([roleName, roleVersionApproval]) => + ? Object.entries(f.volunteerFamilyInfo.familyRoleApprovals).flatMap(([roleName, roleApprovalStatus]) => arrangementFunction.eligibleVolunteerFamilyRoles!.find(x => x === roleName) && - roleVersionApproval.find(rva => rva.approvalStatus === RoleApprovalStatus.Approved || rva.approvalStatus === RoleApprovalStatus.Onboarded) && - !arrangement.familyVolunteerAssignments?.find(fva => - fva.arrangementFunction === arrangementFunction.functionName && fva.familyId === f.family!.id) - ? [{ family: f.family!, person: null as Person | null }] - : []) + (roleApprovalStatus.currentStatus === RoleApprovalStatus.Approved || roleApprovalStatus.currentStatus === RoleApprovalStatus.Onboarded) && + !arrangement.familyVolunteerAssignments?.find(fva => + fva.arrangementFunction === arrangementFunction.functionName && fva.familyId === f.family!.id) + ? [{ family: f.family!, person: null as Person | null }] + : []) : []) : []; const allCandidateAssignees = candidateNamedPeopleAssignees.concat(candidateVolunteerFamilyAssignees).concat(candidateVolunteerIndividualAssignees); @@ -70,7 +70,7 @@ export function AssignArrangementFunctionDialog({ a.family.primaryFamilyContactPersonId === adult.item1!.id)!.item1!; const bPrimaryContact = b.family!.adults!.find(adult => b.family.primaryFamilyContactPersonId === adult.item1!.id)!.item1!; - + const aFirst = a.person ? a.person.firstName! : null; const aLast = a.person ? a.person.lastName! : aPrimaryContact.lastName!; const bFirst = b.person ? b.person.firstName! : null; @@ -97,15 +97,15 @@ export function AssignArrangementFunctionDialog({ }; } }); - + const [fields, setFields] = useState({ assigneeKey: '', variant: null as string | null }); const { assigneeKey } = fields; - + const referralsModel = useReferralsModel(); - + const withBackdrop = useBackdrop(); function getFamilyName(person: ValueTupleOfPersonAndFamilyAdultRelationshipInfo | undefined) { @@ -129,11 +129,11 @@ export function AssignArrangementFunctionDialog({ return ( - + aria-labelledby="assign-volunteer-title" sx={{ '& .MuiDialog-paperFullWidth': { overflowY: 'visible' } }} > + Assign {arrangementFunction.functionName} - +
{arrangementFunction.variants && arrangementFunction.variants.length > 0 && @@ -143,7 +143,7 @@ export function AssignArrangementFunctionDialog({ setFields({...fields, variant: (event.target as HTMLInputElement).value})} + onChange={(event) => setFields({ ...fields, variant: (event.target as HTMLInputElement).value })} > {arrangementFunction.variants.map(variant => } - + { - setFields({...fields, assigneeKey: newValue?.id as string}) + setFields({ ...fields, assigneeKey: newValue?.id as string }) }} options={candidateAssignees.map(candidate => { return { @@ -176,7 +176,7 @@ export function AssignArrangementFunctionDialog({
- +