feat: Write wrong assignment reports to the database (#27405)

* Add DB table for wrong assignment reports

* When report is submitted write to the db

* Prevent duplicate reportings

* test: add migration and tests for WrongAssignmentReport table

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: add unique constraint on bookingUid and booking access check for hasWrongAssignmentReport

- Add @unique constraint on bookingUid in WrongAssignmentReport model to prevent duplicate reports at DB level
- Add booking ownership check using BookingAccessService in hasWrongAssignmentReport endpoint
- Refactor hasWrongAssignmentReport into separate handler and schema files

Addresses Cubic AI review feedback on PR #27405

Co-Authored-By: unknown <>

* feat: add routingFormId to WrongAssignmentReport and fix Select clearing

- Add routingFormId field to WrongAssignmentReport model in schema.prisma
- Add relation to App_RoutingForms_Form with SetNull on delete
- Update WrongAssignmentReportRepository.createReport to accept routingFormId
- Update BookingRepository.findByUidIncludeEventTypeAndTeamAndAssignmentReason to include routedFromRoutingFormReponse
- Extract routingFormId from booking in reportWrongAssignment handler
- Fix Select clearing issue: handle null case when user clears team member selection
- Update tests to include routingFormId field

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* chore: add migration for routingFormId in WrongAssignmentReport

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: address Udit's review comments

- hasWrongAssignmentReport: throw UNAUTHORIZED error instead of returning false
- reportWrongAssignment: add try-catch for Prisma P2002 unique constraint error
- WrongAssignmentReport: add Team relation to teamId field
- WrongAssignmentReportRepository: use findUnique instead of findFirst
- reportWrongAssignment: use i18n for error messages

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: revert hasWrongAssignmentReport to return false when user lacks access

Per PR checklist, hasWrongAssignmentReport should return { hasReport: false }
when user lacks access to booking, not throw an error. This allows the UI
to gracefully treat 'no access' as 'no report exists'.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* test: update mocks for findUnique and i18n in unit tests

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: throw UNAUTHORIZED in hasWrongAssignmentReport and squash migrations

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: change User relation onDelete from Cascade to SetNull in WrongAssignmentReport

Address Hariom's review feedback:
- Changed reportedById from Int to Int? (nullable)
- Changed reportedBy relation from onDelete: Cascade to onDelete: SetNull
- Updated migration SQL to reflect these changes

This preserves wrong assignment reports even when the reporting user is deleted,
as the data is still useful for analysis.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-02-10 08:00:01 -03:00
1 parent c3a8301b0c
commit c0504993a6
12 files changed
+513 -2

No files matched your search

@@ -164,7 +164,7 @@ function AssigneeSection(props: AssigneeSectionProps): JSX.Element {
field: ControllerRenderProps<FormValues, "correctAssignee">;
}): JSX.Element => {
const handleChange = (option: TeamMemberOption | null): void => {
if (option) field.onChange(option.value);
field.onChange(option ? option.value : "");
};
return (
<Select
@@ -234,6 +234,12 @@ export function WrongAssignmentDialog(props: IWrongAssignmentDialog): JSX.Elemen
},
});
const { data: existingReport, isPending: isCheckingReport } = trpc.viewer.bookings.hasWrongAssignmentReport.useQuery(
{ bookingUid },
{ enabled: isOpenDialog }
);
const alreadyReported = existingReport?.hasReport ?? false;
const teamIdForQuery = teamId ?? 0;
const { data: teamMembersData } = trpc.viewer.teams.listMembers.useQuery(
{ teamId: teamIdForQuery, limit: 100 },
@@ -311,6 +317,10 @@ export function WrongAssignmentDialog(props: IWrongAssignmentDialog): JSX.Elemen
errorMessage={errors.additionalNotes?.message}
/>
{alreadyReported && (
<Alert severity="warning" message={t("wrong_assignment_already_reported")} className="mb-4" />
)}
<Alert severity="info" title={t("did_you_know")} message={t("wrong_assignment_crm_info")} />
</div>
</div>
@@ -319,7 +329,7 @@ export function WrongAssignmentDialog(props: IWrongAssignmentDialog): JSX.Elemen
<Button type="button" color="secondary" onClick={handleCloseClick} disabled={isPending}>
{t("close")}
</Button>
<Button type="submit" color="primary" disabled={isPending} loading={isPending}>
<Button type="submit" color="primary" disabled={isPending || alreadyReported || isCheckingReport} loading={isPending || isCheckingReport}>
{t("submit")}
</Button>
</DialogFooter>
@@ -840,6 +840,7 @@
"report_wrong_assignment": "Report wrong assignment",
"wrong_assignment": "Wrong Assignment",
"wrong_assignment_reported": "Wrong assignment reported successfully",
"wrong_assignment_already_reported": "A wrong assignment report has already been submitted for this booking.",
"routing_reason": "Routing Reason",
"no_routing_reason": "No routing reason available",
"who_booked_it": "Who booked it?",