Skip to content

Commit c716ca7

Browse files
authored
Merge pull request ytgov#101 from icefoganalytics/main
Aug updates
2 parents 2c6d6cf + 5b5af2c commit c716ca7

File tree

12 files changed

+250
-72
lines changed

12 files changed

+250
-72
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as knex from "knex";
2+
3+
export async function up(knex: knex.Knex) {
4+
await knex.schema.alterTable("incidents", function (table) {
5+
table.string("source", 50).nullable();
6+
});
7+
}
8+
9+
export async function down(knex: knex.Knex) {
10+
await knex.schema.alterTable("incidents", function (table) {
11+
table.dropColumn("source");
12+
});
13+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import * as knex from "knex";
2+
3+
export async function up(knex: knex.Knex) {
4+
await knex.schema.raw(`DROP VIEW "incident_users_view"`);
5+
6+
await knex.schema.raw(`
7+
CREATE VIEW "incident_users_view" AS
8+
SELECT DISTINCT * FROM (
9+
SELECT "incident_id", "user_email", "reason"
10+
FROM "incident_users"
11+
UNION
12+
SELECT "id", 'System Admin', 'System Admin'
13+
FROM "incidents"
14+
UNION
15+
SELECT "incident_id", "actor_user_email", 'action'
16+
FROM "actions"
17+
UNION
18+
SELECT "id", "reporting_person_email", 'reporter'
19+
FROM "incidents"
20+
UNION
21+
SELECT "id", "supervisor_email", 'supervisor'
22+
FROM "incidents"
23+
UNION
24+
SELECT "incidents"."id", "users"."email", "role_types"."name"
25+
FROM "incidents"
26+
INNER JOIN "user_roles" ON ("incidents"."department_code" = "user_roles"."department_code")
27+
INNER JOIN "users" ON ("users"."id" = "user_roles"."user_id")
28+
INNER JOIN "role_types" ON "user_roles"."role_type_id" = "role_types"."id"
29+
WHERE "user_roles"."role_type_id" IN (8,9)
30+
UNION
31+
SELECT "incidents"."id", "users"."email", 'committee'
32+
FROM "committees"
33+
INNER JOIN "committee_users" ON "committees"."id" = "committee_users"."committee_id"
34+
INNER JOIN "users" ON "users"."id" = "committee_users"."user_id"
35+
INNER JOIN "incidents" ON "incidents"."department_code" = "committees"."department_code"
36+
INNER JOIN "incident_types" ON "incident_types"."id" = "incidents"."incident_type_id" AND "incident_types"."name" = 'inspection'
37+
) "t"
38+
ORDER BY 1,2,3`);
39+
}
40+
41+
export async function down(knex: knex.Knex) {
42+
await knex.schema.raw(`DROP VIEW "incident_users_view"`);
43+
44+
await knex.schema.raw(`
45+
CREATE VIEW "incident_users_view" AS
46+
SELECT DISTINCT * FROM (
47+
SELECT "incident_id", "user_email", "reason"
48+
FROM "incident_users"
49+
UNION
50+
SELECT "id", 'System Admin', 'System Admin'
51+
FROM "incidents"
52+
UNION
53+
SELECT "incident_id", "actor_user_email", 'action'
54+
FROM "actions"
55+
UNION
56+
SELECT "id", "reporting_person_email", 'reporter'
57+
FROM "incidents"
58+
UNION
59+
SELECT "id", "supervisor_email", 'supervisor'
60+
FROM "incidents"
61+
UNION
62+
SELECT "incidents"."id", "users"."email", "role_types"."name"
63+
FROM "incidents"
64+
INNER JOIN "user_roles" ON ("incidents"."department_code" = "user_roles"."department_code")
65+
INNER JOIN "users" ON ("users"."id" = "user_roles"."user_id")
66+
INNER JOIN "role_types" ON "user_roles"."role_type_id" = "role_types"."id"
67+
WHERE "user_roles"."role_type_id" IN (8,9)
68+
) "t"
69+
ORDER BY 1,2,3`);
70+
}

api/src/data/models/incident-model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface Incident {
3030
slug: string;
3131
identifier?: string;
3232
inspection_location_id?: number;
33+
source?: string;
3334

3435
attachments?: any[];
3536
steps?: IncidentStep[];

api/src/routes/offline-report-router.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ offlineReportRouter.post("/", async (req: Request, res: Response) => {
9999
location_detail,
100100
slug: generateSlug(),
101101
identifier: await generateIdentifier(trx),
102+
source: "offline",
102103
} as Incident;
103104

104105
const insertedIncidents = await trx("incidents").insert(incident).returning("*");

api/src/routes/report-router.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ reportRouter.post("/", async (req: Request, res: Response) => {
291291
location_detail,
292292
slug: generateSlug(),
293293
identifier: await generateIdentifier(trx),
294+
source: "online"
294295
} as Incident;
295296

296297
const insertedIncidents = await trx("incidents").insert(incident).returning("*");

api/src/services/incident-service.ts

Lines changed: 13 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,12 @@ import { Knex } from "knex";
44
import { isArray } from "lodash";
55

66
export class IncidentService {
7-
async getAll(
8-
email: string,
9-
where: (query: Knex.QueryBuilder) => Knex.QueryBuilder
10-
): Promise<Incident[]> {
7+
async getAll(email: string, where: (query: Knex.QueryBuilder) => Knex.QueryBuilder): Promise<Incident[]> {
118
return db<Incident>("incidents")
129
.innerJoin("incident_types", "incident_types.id", "incidents.incident_type_id")
1310
.innerJoin("incident_statuses", "incident_statuses.code", "incidents.status_code")
1411
.innerJoin("departments", "departments.code", "incidents.department_code")
15-
.whereRaw(
16-
`"incidents"."id" IN (SELECT "incident_id" FROM "incident_users_view" WHERE "user_email" = ?)`,
17-
[email]
18-
)
12+
.whereRaw(`"incidents"."id" IN (SELECT "incident_id" FROM "incident_users_view" WHERE "user_email" = ?)`, [email])
1913
.whereNot("incident_types.name", "inspection")
2014
.modify(where)
2115
.select(
@@ -27,18 +21,12 @@ export class IncidentService {
2721
)
2822
.orderBy("incidents.created_at", "desc");
2923
}
30-
async getCount(
31-
email: string,
32-
where: (query: Knex.QueryBuilder) => Knex.QueryBuilder
33-
): Promise<{ count: number }> {
24+
async getCount(email: string, where: (query: Knex.QueryBuilder) => Knex.QueryBuilder): Promise<{ count: number }> {
3425
return db<Incident>("incidents")
3526
.innerJoin("incident_types", "incident_types.id", "incidents.incident_type_id")
3627
.innerJoin("incident_statuses", "incident_statuses.code", "incidents.status_code")
3728
.innerJoin("departments", "departments.code", "incidents.department_code")
38-
.whereRaw(
39-
`"incidents"."id" IN (SELECT "incident_id" FROM "incident_users_view" WHERE "user_email" = ?)`,
40-
[email]
41-
)
29+
.whereRaw(`"incidents"."id" IN (SELECT "incident_id" FROM "incident_users_view" WHERE "user_email" = ?)`, [email])
4230
.whereNot("incident_types.name", "inspection")
4331
.modify(where)
4432
.count("* as count")
@@ -58,10 +46,7 @@ export class IncidentService {
5846
.innerJoin("incident_statuses", "incident_statuses.code", "incidents.status_code")
5947
.innerJoin("departments", "departments.code", "incidents.department_code")
6048
.innerJoin("locations", "incidents.location_code", "locations.code")
61-
.whereRaw(
62-
`"incidents"."id" IN (SELECT "incident_id" FROM "incident_users_view" WHERE "user_email" = ?)`,
63-
[email]
64-
)
49+
.whereRaw(`"incidents"."id" IN (SELECT "incident_id" FROM "incident_users_view" WHERE "user_email" = ?)`, [email])
6550
.whereNot("incident_types.name", "inspection")
6651
.select<Incident>(
6752
"incidents.*",
@@ -77,21 +62,10 @@ export class IncidentService {
7762

7863
item.attachments = await db("incident_attachments")
7964
.where({ incident_id: item.id })
80-
.select(
81-
"id",
82-
"incident_id",
83-
"added_by_email",
84-
"file_name",
85-
"file_type",
86-
"file_size",
87-
"added_date"
88-
);
65+
.select("id", "incident_id", "added_by_email", "file_name", "file_type", "file_size", "added_date");
8966

9067
item.steps = await db("incident_steps").where({ incident_id: item.id }).orderBy("order");
91-
item.actions = await db("actions")
92-
.where({ incident_id: item.id })
93-
.orderBy("due_date")
94-
.orderBy("id");
68+
item.actions = await db("actions").where({ incident_id: item.id }).orderBy("due_date").orderBy("id");
9569
item.investigation = await db("investigations").where({ incident_id: item.id }).first();
9670
item.access = await db("incident_users_view").where({
9771
incident_id: item.id,
@@ -104,23 +78,15 @@ export class IncidentService {
10478

10579
item.hazards = await db("incident_hazards")
10680
.where({ incident_id: item.id })
107-
.innerJoin(
108-
"incident_hazard_types",
109-
"incident_hazards.incident_hazard_type_code",
110-
"incident_hazard_types.code"
111-
)
81+
.innerJoin("incident_hazard_types", "incident_hazards.incident_hazard_type_code", "incident_hazard_types.code")
11282
.select("incident_hazards.*", "incident_hazard_types.name as incident_hazard_type_name");
11383

11484
for (let hazard of item.hazards ?? []) {
11585
hazard.hazard = await db("hazards")
11686
.where({ "hazards.id": hazard.hazard_id })
11787
.innerJoin("hazard_types", "hazards.hazard_type_id", "hazard_types.id")
11888
.innerJoin("locations", "hazards.location_code", "locations.code")
119-
.select(
120-
"hazards.*",
121-
"hazard_types.name as hazard_type_name",
122-
"locations.name as location_name"
123-
)
89+
.select("hazards.*", "hazard_types.name as hazard_type_name", "locations.name as location_name")
12490
.first();
12591
}
12692

@@ -130,24 +96,21 @@ export class IncidentService {
13096
await db("role_types").where({ id: action.actor_role_type_id }).first()
13197
).description;
13298
} else if (action.actor_user_id) {
133-
action.actor_display_name = (
134-
await db("users").where({ id: action.actor_user_id }).first()
135-
).display_name;
99+
action.actor_display_name = (await db("users").where({ id: action.actor_user_id }).first()).display_name;
136100
} else if (action.actor_user_email) {
137101
action.actor_display_name = action.actor_user_email;
138102
}
139103

140104
action.categories = action.categories ?? [];
141-
if (!isArray(action.categories))
142-
action.categories = action.categories.split(",").filter((c) => c);
105+
if (!isArray(action.categories)) action.categories = action.categories.split(",").filter((c) => c);
143106
}
144107

145108
return item;
146109
}
147110

148111
async getByReportingEmail(email: string): Promise<Incident[]> {
149112
return db<Incident>("incidents")
150-
.where("incident_users_view.user_email", email)
113+
.whereILike("incident_users_view.user_email", email.toLowerCase())
151114
.where("incident_users_view.reason", "reporter")
152115
.whereNotIn("status_code", ["Closed", "Dup", "NoAct"])
153116
.whereNot("incident_types.name", "inspection")
@@ -166,7 +129,7 @@ export class IncidentService {
166129

167130
async getBySupervisorEmail(email: string): Promise<Incident[]> {
168131
return db<Incident>("incidents")
169-
.where("incident_users_view.user_email", email)
132+
.whereILike("incident_users_view.user_email", email.toLowerCase())
170133
.where("incident_users_view.reason", "supervisor")
171134
.whereNot("incident_types.name", "inspection")
172135
.whereNotIn("status_code", ["Closed", "Dup", "NoAct"])

web/src/components/action/ActionCompleteForm.vue

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@
3131
</v-col>
3232
<v-col cols="12">
3333
<v-label>Notes</v-label>
34-
<v-textarea v-model="props.action.notes" :readonly="!isNil(props.action.complete_date)" rows="3" hide-details />
34+
<div class="d-flex">
35+
<v-textarea
36+
v-model="props.action.notes"
37+
:readonly="!isNil(props.action.complete_date)"
38+
rows="3"
39+
hide-details />
40+
<v-btn class="ml-3 mt-auto" size="small" color="primary" @click="saveNotes"> Save</v-btn>
41+
</div>
3542
</v-col>
3643
<v-col v-if="!isNil(props.action.complete_name)" cols="12">
3744
<v-label>Completed</v-label>
@@ -108,7 +115,7 @@ const userStore = useUserStore();
108115
const { isSystemAdmin } = storeToRefs(userStore);
109116
110117
const actionStore = useActionStore();
111-
const { deleteAction, completeAction, revertAction, hazardAction } = actionStore;
118+
const { deleteAction, completeAction, revertAction, hazardAction, saveAction } = actionStore;
112119
113120
onMounted(() => {
114121
loadCurrentStepUser();
@@ -122,6 +129,17 @@ function closeClick() {
122129
emit("doClose");
123130
}
124131
132+
async function saveNotes() {
133+
showOverlay("Saving Notes");
134+
props.action.notes = props.action.notes.trim();
135+
if (!props.action.notes) {
136+
hideOverlay();
137+
return;
138+
}
139+
await saveAction(props.action);
140+
hideOverlay();
141+
}
142+
125143
async function deleteClick() {
126144
showOverlay("Delete Action");
127145
deleteAction(props.action).then(() => {

web/src/components/incident/CreatePage.vue

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,33 @@
66
the <em>Workers' Safety and Compensation Act</em>.
77
</p>
88

9+
<v-alert type="warning" color="#fb8c0088" class="mt-3" style="font-size: 0.95rem">
10+
If the incident resulted in a fatality or serious incident as per WSCA you must report immediately to your
11+
supervisor and WSCB at their 24-hour phone line: 867-667-5450.
12+
<v-tooltip location="top" width="600" open-delay="250">
13+
<template #activator="{ props }">
14+
<v-icon color="primary" class="cursor-pointer" v-bind="props">mdi-information</v-icon>
15+
</template>
16+
<strong>This includes:</strong>
17+
<ul class="mx-5 my-3">
18+
<li>An incident that results in serious injury to or the death of a worker</li>
19+
<li>An incident or injury that results in a worker's admission to a hospital as an inpatient</li>
20+
<li>
21+
A major structural failure or collapse of a bridge, building, crane, excavation, hoist, mine, mining
22+
development, temporary construction support system, tower or any other like structure
23+
</li>
24+
<li>A major release of a hazardous substance</li>
25+
<li>
26+
An explosion or fire that has the potential to cause serious injury to or the death of a worker or other
27+
person
28+
</li>
29+
<li>
30+
An incident, injury or death that is required to be reported by the regulations or by order of the board
31+
</li>
32+
</ul>
33+
</v-tooltip>
34+
</v-alert>
35+
936
<v-form class="mt-6" v-model="isValid">
1037
<section>
1138
<v-card class="default">
@@ -47,7 +74,7 @@
4774
style="width: 60px; height: 40px"
4875
hide-details />
4976
<div>
50-
<strong>No Loss Incident</strong><br />
77+
<strong>No Loss Incident (near miss)</strong><br />
5178
Something has happened. No damage or injury occurred, but could have
5279
</div>
5380
</v-col>

web/src/components/incident/CreatePageOffline.vue

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@
55
Protection of Privacy Act. It is collected and used for the purposes of hazard and incident management related to
66
the <em>Workers' Safety and Compensation Act</em>.
77
</p>
8+
9+
<v-alert type="warning" color="#fb8c0088" class="mt-3" style="font-size: 0.95rem">
10+
If the incident resulted in a fatality or serious incident as per WSCA you must report immediately to your
11+
supervisor and WSCB at their 24-hour phone line: 867-667-5450.
12+
<v-tooltip location="top" width="600" open-delay="250">
13+
<template #activator="{ props }">
14+
<v-icon color="primary" class="cursor-pointer" v-bind="props">mdi-information</v-icon>
15+
</template>
16+
<strong>This includes:</strong>
17+
<ul class="mx-5 my-3">
18+
<li>An incident that results in serious injury to or the death of a worker</li>
19+
<li>An incident or injury that results in a worker's admission to a hospital as an inpatient</li>
20+
<li>
21+
A major structural failure or collapse of a bridge, building, crane, excavation, hoist, mine, mining
22+
development, temporary construction support system, tower or any other like structure
23+
</li>
24+
<li>A major release of a hazardous substance</li>
25+
<li>
26+
An explosion or fire that has the potential to cause serious injury to or the death of a worker or other
27+
person
28+
</li>
29+
<li>
30+
An incident, injury or death that is required to be reported by the regulations or by order of the board
31+
</li>
32+
</ul>
33+
</v-tooltip>
34+
</v-alert>
35+
836
<v-overlay v-model="isLoading" class="align-center justify-center">
937
<div class="text-center">
1038
<v-progress-circular indeterminate size="64" class="mb-5" color="#f3b228" width="6"></v-progress-circular>
@@ -51,7 +79,7 @@
5179
style="width: 60px; height: 40px"
5280
hide-details />
5381
<div>
54-
<strong>No Loss Incident</strong><br />
82+
<strong>No Loss Incident (near miss)</strong><br />
5583
Something has happened. No damage or injury occurred, but could have
5684
</div>
5785
</v-col>

0 commit comments

Comments
 (0)