Skip to content

Commit

Permalink
🎉 feat: restrictions for calls (optional) (#573)
Browse files Browse the repository at this point in the history
  • Loading branch information
casperiv0 authored Apr 3, 2022
1 parent 927f973 commit 3518443
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 12 deletions.
3 changes: 3 additions & 0 deletions packages/api/prisma/migrations/20220403064022_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "MiscCadSettings" ADD COLUMN "maxAssignmentsToCalls" INTEGER,
ADD COLUMN "maxAssignmentsToIncidents" INTEGER;
4 changes: 3 additions & 1 deletion packages/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ model MiscCadSettings {
maxPlateLength Int @default(8)
maxBusinessesPerCitizen Int?
maxDivisionsPerOfficer Int?
// max units a user can create with a department
/// max units a user can create with a department
maxDepartmentsEachPerUser Int?
maxAssignmentsToIncidents Int?
maxAssignmentsToCalls Int?
callsignTemplate String @default("{department}{callsign1} - {callsign2}{division}") @db.Text
pairedUnitTemplate String? @default("1A-{callsign1}") @db.Text
pairedUnitSymbol String? @default("A") @db.VarChar(255)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export class ManageCitizensController {
authScreenHeaderImageId: data.authScreenHeaderImageId,
maxOfficersPerUser: data.maxOfficersPerUser,
maxDepartmentsEachPerUser: data.maxDepartmentsEachPerUser,
maxAssignmentsToCalls: data.maxAssignmentsToCalls,
maxAssignmentsToIncidents: data.maxAssignmentsToIncidents,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
DivisionValue,
User,
StatusValue,
MiscCadSettings,
} from "@prisma/client";
import { sendDiscordWebhook } from "lib/discord/webhooks";
import type { cad, Call911 } from "@snailycad/types";
Expand Down Expand Up @@ -91,11 +92,12 @@ export class Calls911Controller {
async create911Call(
@BodyParams() body: unknown,
@Context("user") user: User,
@Context("cad") cad: cad,
@Context("cad") cad: cad & { miscCadSettings: MiscCadSettings },
@HeaderParams("is-from-dispatch") isFromDispatchHeader: string | undefined,
) {
const data = validateSchema(CREATE_911_CALL, body);
const isFromDispatch = isFromDispatchHeader === "true" && user.isDispatch;
const maxAssignmentsToCalls = cad.miscCadSettings.maxAssignmentsToCalls ?? Infinity;

const call = await prisma.call911.create({
data: {
Expand All @@ -112,7 +114,7 @@ export class Calls911Controller {
});

const units = (data.assignedUnits ?? []) as string[];
await this.assignUnitsToCall(call.id, units);
await this.assignUnitsToCall(call.id, units, maxAssignmentsToCalls);
await this.linkOrUnlinkCallDepartmentsAndDivisions({
type: "connect",
departments: (data.departments ?? []) as string[],
Expand Down Expand Up @@ -148,9 +150,11 @@ export class Calls911Controller {
async update911Call(
@PathParams("id") id: string,
@BodyParams() body: unknown,
@Context() ctx: Context,
@Context("user") user: User,
@Context("cad") cad: cad & { miscCadSettings: MiscCadSettings },
) {
const data = validateSchema(CREATE_911_CALL, body);
const maxAssignmentsToCalls = cad.miscCadSettings.maxAssignmentsToCalls ?? Infinity;

const call = await prisma.call911.findUnique({
where: {
Expand Down Expand Up @@ -211,15 +215,15 @@ export class Calls911Controller {
postal: String(data.postal),
description: data.description,
name: data.name,
userId: ctx.get("user").id,
userId: user.id,
positionId: shouldRemovePosition ? null : position?.id ?? call.positionId,
descriptionData: data.descriptionData,
situationCodeId: data.situationCode === null ? null : data.situationCode,
},
});

const units = (data.assignedUnits ?? []) as string[];
await this.assignUnitsToCall(call.id, units);
await this.assignUnitsToCall(call.id, units, maxAssignmentsToCalls);
await this.linkOrUnlinkCallDepartmentsAndDivisions({
type: "connect",
departments: (data.departments ?? []) as string[],
Expand Down Expand Up @@ -440,7 +444,11 @@ export class Calls911Controller {
);
}

protected async assignUnitsToCall(callId: string, units: string[]) {
protected async assignUnitsToCall(
callId: string,
units: string[],
maxAssignmentsToCalls: number,
) {
await Promise.all(
units.map(async (id) => {
const { unit, type } = await findUnit(
Expand All @@ -461,6 +469,18 @@ export class Calls911Controller {
"ems-fd": "emsFdDeputyId",
};

const assignmentCount = await prisma.assignedUnit.count({
where: {
[types[type]]: unit.id,
call911: { ended: false },
},
});

if (assignmentCount >= maxAssignmentsToCalls) {
// skip this officer
return;
}

const status = await prisma.statusValue.findFirst({
where: { shouldDo: "SET_ASSIGNED" },
});
Expand Down
30 changes: 25 additions & 5 deletions packages/api/src/controllers/leo/incidents/IncidentController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { validateSchema } from "lib/validateSchema";
import { Socket } from "services/SocketService";
import type { z } from "zod";
import { UsePermissions, Permissions } from "middlewares/UsePermissions";
import type { MiscCadSettings } from "@snailycad/types";

export const incidentInclude = {
creator: { include: leoProperties },
Expand Down Expand Up @@ -70,10 +71,12 @@ export class IncidentController {
})
async createIncident(
@BodyParams() body: unknown,
@Context("cad") cad: { miscCadSettings: MiscCadSettings },
@Context("activeOfficer") officer: Officer | null,
) {
const data = validateSchema(LEO_INCIDENT_SCHEMA, body);
const officerId = officer?.id ?? null;
const maxAssignmentsToIncidents = cad.miscCadSettings.maxAssignmentsToIncidents ?? Infinity;

const incident = await prisma.leoIncident.create({
data: {
Expand All @@ -88,7 +91,7 @@ export class IncidentController {
},
});

await this.connectOfficersInvolved(incident.id, data);
await this.connectOfficersInvolved(incident.id, data, maxAssignmentsToIncidents);

const updated = await prisma.leoIncident.findUnique({
where: { id: incident.id },
Expand All @@ -113,8 +116,13 @@ export class IncidentController {
permissions: [Permissions.ManageIncidents],
fallback: (u) => u.isDispatch || u.isLeo,
})
async updateIncident(@BodyParams() body: unknown, @PathParams("id") incidentId: string) {
async updateIncident(
@BodyParams() body: unknown,
@Context("cad") cad: { miscCadSettings: MiscCadSettings },
@PathParams("id") incidentId: string,
) {
const data = validateSchema(LEO_INCIDENT_SCHEMA, body);
const maxAssignmentsToIncidents = cad.miscCadSettings.maxAssignmentsToIncidents ?? Infinity;

const incident = await prisma.leoIncident.findUnique({
where: { id: incidentId },
Expand Down Expand Up @@ -154,7 +162,7 @@ export class IncidentController {
},
});

await this.connectOfficersInvolved(incident.id, data);
await this.connectOfficersInvolved(incident.id, data, maxAssignmentsToIncidents);

const updated = await prisma.leoIncident.findUnique({
where: { id: incident.id },
Expand Down Expand Up @@ -196,9 +204,21 @@ export class IncidentController {
protected async connectOfficersInvolved(
incidentId: string,
data: Pick<z.infer<typeof LEO_INCIDENT_SCHEMA>, "involvedOfficers" | "isActive">,
maxAssignmentsToIncidents: number,
) {
await prisma.$transaction(
(data.involvedOfficers ?? []).map((id: string) => {
await Promise.all(
(data.involvedOfficers ?? []).map(async (id: string) => {
const count = await prisma.leoIncident.count({
where: {
officersInvolved: { some: { id } },
isActive: true,
},
});

if (count >= maxAssignmentsToIncidents) {
return;
}

return prisma.leoIncident.update({
where: { id: incidentId },
data: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export function MiscFeatures() {
maxPlateLength: miscSettings.maxPlateLength,
maxDivisionsPerOfficer: miscSettings.maxDivisionsPerOfficer ?? Infinity,
maxDepartmentsEachPerUser: miscSettings.maxDepartmentsEachPerUser ?? Infinity,
maxAssignmentsToIncidents: miscSettings.maxAssignmentsToIncidents ?? Infinity,
maxAssignmentsToCalls: miscSettings.maxAssignmentsToCalls ?? Infinity,
maxOfficersPerUser: miscSettings.maxOfficersPerUser ?? Infinity,
callsignTemplate: miscSettings.callsignTemplate ?? "",
pairedUnitTemplate: miscSettings.pairedUnitTemplate ?? "",
Expand Down Expand Up @@ -210,6 +212,36 @@ export function MiscFeatures() {
/>
</SettingsFormField>

<SettingsFormField
label="Max assignments to incidents per officer"
action="short-input"
description="The maximum amount of how many incidents an officer can be assigned to. (Default: Infinity)"
errorMessage={errors.maxAssignmentsToIncidents}
>
<Input
name="maxAssignmentsToIncidents"
type="number"
value={values.maxAssignmentsToIncidents}
onChange={handleChange}
min={1}
/>
</SettingsFormField>

<SettingsFormField
label="Max assignments to calls per unit"
action="short-input"
description="The maximum amount of how many calls a unit can be assigned to. (Default: Infinity)"
errorMessage={errors.maxAssignmentsToCalls}
>
<Input
name="maxAssignmentsToCalls"
type="number"
value={values.maxAssignmentsToCalls}
onChange={handleChange}
min={1}
/>
</SettingsFormField>

<SettingsFormField
label="Max officers per user"
action="short-input"
Expand Down
2 changes: 2 additions & 0 deletions packages/schemas/src/admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const CAD_MISC_SETTINGS_SCHEMA = z.object({
maxBusinessesPerCitizen: z.number().nullable(),
maxDivisionsPerOfficer: z.number().nullable(),
maxDepartmentsEachPerUser: z.number().nullable(),
maxAssignmentsToIncidents: z.number().nullable(),
maxAssignmentsToCalls: z.number().nullable(),
maxPlateLength: z.number().min(1),
pairedUnitTemplate: z.string().max(255).nullable(),
callsignTemplate: z.string(),
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export interface MiscCadSettings {
maxBusinessesPerCitizen: number | null;
maxDivisionsPerOfficer: number | null;
maxDepartmentsEachPerUser: number | null;
maxAssignmentsToIncidents: number | null;
maxAssignmentsToCalls: number | null;
callsignTemplate: string;
pairedUnitTemplate: string | null;
signal100Enabled: boolean;
Expand Down

0 comments on commit 3518443

Please sign in to comment.