]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/sql/Pg/reporter-schema.sql
LP1813191: Dots Need Chaperones
[working/Evergreen.git] / Open-ILS / src / sql / Pg / reporter-schema.sql
1 /*
2  * Copyright (C) 2004-2008  Georgia Public Library Service
3  * Copyright (C) 2007-2008  Equinox Software, Inc.
4  * Mike Rylander <miker@esilibrary.com> 
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  */
17
18 DROP SCHEMA IF EXISTS reporter CASCADE;
19
20 BEGIN;
21
22 CREATE SCHEMA reporter;
23
24 CREATE TABLE reporter.template_folder (
25         id              SERIAL                          PRIMARY KEY,
26         parent          INT                             REFERENCES reporter.template_folder (id) DEFERRABLE INITIALLY DEFERRED,
27         owner           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
28         create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
29         name            TEXT                            NOT NULL,
30         shared          BOOL                            NOT NULL DEFAULT FALSE,
31         share_with      INT                             REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
32 );
33 CREATE INDEX rpt_tmpl_fldr_owner_idx ON reporter.template_folder (owner);
34 CREATE UNIQUE INDEX rpt_template_folder_once_parent_idx ON reporter.template_folder (name,parent);
35 CREATE UNIQUE INDEX rpt_template_folder_once_idx ON reporter.template_folder (name,owner) WHERE parent IS NULL;
36
37 CREATE TABLE reporter.report_folder (
38         id              SERIAL                          PRIMARY KEY,
39         parent          INT                             REFERENCES reporter.report_folder (id) DEFERRABLE INITIALLY DEFERRED,
40         owner           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
41         create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
42         name            TEXT                            NOT NULL,
43         shared          BOOL                            NOT NULL DEFAULT FALSE,
44         share_with      INT                             REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
45 );
46 CREATE INDEX rpt_rpt_fldr_owner_idx ON reporter.report_folder (owner);
47 CREATE UNIQUE INDEX rpt_report_folder_once_parent_idx ON reporter.report_folder (name,parent);
48 CREATE UNIQUE INDEX rpt_report_folder_once_idx ON reporter.report_folder (name,owner) WHERE parent IS NULL;
49
50 CREATE TABLE reporter.output_folder (
51         id              SERIAL                          PRIMARY KEY,
52         parent          INT                             REFERENCES reporter.output_folder (id) DEFERRABLE INITIALLY DEFERRED,
53         owner           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
54         create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
55         name            TEXT                            NOT NULL,
56         shared          BOOL                            NOT NULL DEFAULT FALSE,
57         share_with      INT                             REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
58 );
59 CREATE INDEX rpt_output_fldr_owner_idx ON reporter.output_folder (owner);
60 CREATE UNIQUE INDEX rpt_output_folder_once_parent_idx ON reporter.output_folder (name,parent);
61 CREATE UNIQUE INDEX rpt_output_folder_once_idx ON reporter.output_folder (name,owner) WHERE parent IS NULL;
62
63
64 CREATE TABLE reporter.template (
65         id              SERIAL                          PRIMARY KEY,
66         owner           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
67         create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
68         name            TEXT                            NOT NULL,
69         description     TEXT                            NOT NULL DEFAULT '',
70         data            TEXT                            NOT NULL,
71         folder          INT                             NOT NULL REFERENCES reporter.template_folder (id) DEFERRABLE INITIALLY DEFERRED
72 );
73 CREATE INDEX rpt_tmpl_owner_idx ON reporter.template (owner);
74 CREATE INDEX rpt_tmpl_fldr_idx ON reporter.template (folder);
75 CREATE UNIQUE INDEX rtp_template_folder_once_idx ON reporter.template (name,folder);
76
77 CREATE TABLE reporter.report (
78         id              SERIAL                          PRIMARY KEY,
79         owner           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
80         create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
81         name            TEXT                            NOT NULL DEFAULT '',
82         description     TEXT                            NOT NULL DEFAULT '',
83         template        INT                             NOT NULL REFERENCES reporter.template (id) DEFERRABLE INITIALLY DEFERRED,
84         data            TEXT                            NOT NULL,
85         folder          INT                             NOT NULL REFERENCES reporter.report_folder (id) DEFERRABLE INITIALLY DEFERRED,
86         recur           BOOL                            NOT NULL DEFAULT FALSE,
87         recurrence      INTERVAL
88 );
89 CREATE INDEX rpt_rpt_owner_idx ON reporter.report (owner);
90 CREATE INDEX rpt_rpt_fldr_idx ON reporter.report (folder);
91 CREATE UNIQUE INDEX rtp_report_folder_once_idx ON reporter.report (name,folder);
92
93 CREATE TABLE reporter.schedule (
94         id              SERIAL                          PRIMARY KEY,
95         report          INT                             NOT NULL REFERENCES reporter.report (id) DEFERRABLE INITIALLY DEFERRED,
96         folder          INT                             NOT NULL REFERENCES reporter.output_folder (id) DEFERRABLE INITIALLY DEFERRED,
97         runner          INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
98         run_time        TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
99         start_time      TIMESTAMP WITH TIME ZONE,
100         complete_time   TIMESTAMP WITH TIME ZONE,
101         email           TEXT,
102         excel_format    BOOL                            NOT NULL DEFAULT TRUE,
103         html_format     BOOL                            NOT NULL DEFAULT TRUE,
104         csv_format      BOOL                            NOT NULL DEFAULT TRUE,
105         chart_pie       BOOL                            NOT NULL DEFAULT FALSE,
106         chart_bar       BOOL                            NOT NULL DEFAULT FALSE,
107         chart_line      BOOL                            NOT NULL DEFAULT FALSE,
108         error_code      INT,
109         error_text      TEXT
110 );
111 CREATE INDEX rpt_sched_runner_idx ON reporter.schedule (runner);
112 CREATE INDEX rpt_sched_folder_idx ON reporter.schedule (folder);
113 CREATE UNIQUE INDEX rpt_sched_recurrence_once_idx ON reporter.schedule (report,folder,runner,run_time,COALESCE(email,''));
114
115 CREATE OR REPLACE VIEW reporter.simple_record AS
116 SELECT  r.id,
117         s.metarecord,
118         r.fingerprint,
119         r.quality,
120         r.tcn_source,
121         r.tcn_value,
122         title.value AS title,
123         uniform_title.value AS uniform_title,
124         author.value AS author,
125         publisher.value AS publisher,
126         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
127         series_title.value AS series_title,
128         series_statement.value AS series_statement,
129         summary.value AS summary,
130         ARRAY_AGG( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
131         ARRAY_AGG( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
132         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
133         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
134         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
135         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
136         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
137         ARRAY((SELECT value FROM metabib.full_rec WHERE tag = '856' AND subfield IN ('3','y','u') AND record = r.id ORDER BY CASE WHEN subfield IN ('3','y') THEN 0 ELSE 1 END)) AS external_uri
138   FROM  biblio.record_entry r
139         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
140         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
141         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
142         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
143         LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND (publisher.tag = '260' OR (publisher.tag = '264' AND publisher.ind2 = '1')) AND publisher.subfield = 'b')
144         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND (pubdate.tag = '260' OR (pubdate.tag = '264' AND pubdate.ind2 = '1')) AND pubdate.subfield = 'c')
145         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
146         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
147         LEFT JOIN metabib.full_rec series_title ON (r.id = series_title.record AND series_title.tag IN ('830','440') AND series_title.subfield = 'a')
148         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
149         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
150   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
151
152 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
153 SELECT  r.id,
154     r.fingerprint,
155     r.quality,
156     r.tcn_source,
157     r.tcn_value,
158     evergreen.oils_json_to_text(d.title) AS title,
159     evergreen.oils_json_to_text(d.author) AS author,
160     evergreen.oils_json_to_text(d.publisher) AS publisher,
161     evergreen.oils_json_to_text(d.pubdate) AS pubdate,
162     CASE WHEN d.isbn = 'null'
163         THEN NULL
164         ELSE (SELECT ARRAY(SELECT json_array_elements_text(d.isbn::JSON)))
165     END AS isbn,
166     CASE WHEN d.issn = 'null'
167         THEN NULL
168         ELSE (SELECT ARRAY(SELECT json_array_elements_text(d.issn::JSON)))
169     END AS issn
170   FROM  biblio.record_entry r
171         JOIN metabib.wide_display_entry d ON (r.id = d.source);
172
173 CREATE TABLE reporter.materialized_simple_record AS SELECT * FROM reporter.old_super_simple_record WHERE 1=0;
174 ALTER TABLE reporter.materialized_simple_record ADD PRIMARY KEY (id);
175
176 CREATE VIEW reporter.super_simple_record AS SELECT * FROM reporter.materialized_simple_record;
177
178 CREATE OR REPLACE FUNCTION reporter.simple_rec_update (r_id BIGINT, deleted BOOL) RETURNS BOOL AS $$
179 BEGIN
180
181     DELETE FROM reporter.materialized_simple_record WHERE id = r_id;
182
183     IF NOT deleted THEN
184         INSERT INTO reporter.materialized_simple_record SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record WHERE id = r_id;
185     END IF;
186
187     RETURN TRUE;
188
189 END;
190 $$ LANGUAGE PLPGSQL;
191
192 CREATE OR REPLACE FUNCTION reporter.simple_rec_update (r_id BIGINT) RETURNS BOOL AS $$
193     SELECT reporter.simple_rec_update($1, FALSE);
194 $$ LANGUAGE SQL;
195
196 CREATE OR REPLACE FUNCTION reporter.simple_rec_delete (r_id BIGINT) RETURNS BOOL AS $$
197     SELECT reporter.simple_rec_update($1, TRUE);
198 $$ LANGUAGE SQL;
199
200 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
201 BEGIN
202     IF TG_OP = 'DELETE' THEN
203         PERFORM reporter.simple_rec_delete(NEW.id);
204     ELSE
205         PERFORM reporter.simple_rec_update(NEW.id);
206     END IF;
207
208     RETURN NEW;
209 END;
210 $func$ LANGUAGE PLPGSQL;
211
212 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
213     DROP TRIGGER IF EXISTS bbb_simple_rec_trigger ON biblio.record_entry;
214 $$ LANGUAGE SQL;
215
216 CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$
217
218     TRUNCATE TABLE reporter.materialized_simple_record;
219
220     INSERT INTO reporter.materialized_simple_record
221         (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn)
222         SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record;
223
224     CREATE TRIGGER bbb_simple_rec_trigger
225         AFTER INSERT OR UPDATE OR DELETE ON biblio.record_entry
226         FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger();
227
228 $$ LANGUAGE SQL;
229
230 CREATE OR REPLACE FUNCTION reporter.refresh_materialized_simple_record () RETURNS VOID AS $$
231     SELECT reporter.disable_materialized_simple_record_trigger();
232     SELECT reporter.enable_materialized_simple_record_trigger();
233 $$ LANGUAGE SQL;
234
235 CREATE OR REPLACE VIEW reporter.asset_call_number_dewey AS
236   SELECT id AS call_number,
237     call_number_dewey(label) AS dewey,
238     CASE WHEN call_number_dewey(label) ~ '^[0-9]+\.?[0-9]*$'::text
239       THEN btrim(to_char(10::double precision * floor(call_number_dewey(label)::double precision / 10::double precision), '000'::text))
240       ELSE NULL::text
241     END AS dewey_block_tens,
242     CASE WHEN call_number_dewey(label) ~ '^[0-9]+\.?[0-9]*$'::text
243       THEN btrim(to_char(100::double precision * floor(call_number_dewey(label)::double precision / 100::double precision), '000'::text))
244       ELSE NULL::text
245     END AS dewey_block_hundreds,
246     CASE WHEN call_number_dewey(label) ~ '^[0-9]+\.?[0-9]*$'::text
247       THEN (btrim(to_char(10::double precision * floor(call_number_dewey(label)::double precision / 10::double precision), '000'::text)) || '-'::text)
248       || btrim(to_char(10::double precision * floor(call_number_dewey(label)::double precision / 10::double precision) + 9::double precision, '000'::text))
249       ELSE NULL::text
250     END AS dewey_range_tens,
251     CASE WHEN call_number_dewey(label) ~ '^[0-9]+\.?[0-9]*$'::text
252       THEN (btrim(to_char(100::double precision * floor(call_number_dewey(label)::double precision / 100::double precision), '000'::text)) || '-'::text)
253       || btrim(to_char(100::double precision * floor(call_number_dewey(label)::double precision / 100::double precision) + 99::double precision, '000'::text))
254       ELSE NULL::text
255     END AS dewey_range_hundreds
256   FROM asset.call_number
257   WHERE call_number_dewey(label) ~ '^[0-9]'::text;
258
259 CREATE OR REPLACE VIEW reporter.demographic AS
260 SELECT  u.id,
261         u.dob,
262         CASE
263                 WHEN u.dob IS NULL
264                         THEN 'Adult'
265                 WHEN AGE(u.dob) > '18 years'::INTERVAL
266                         THEN 'Adult'
267                 ELSE 'Juvenile'
268         END AS general_division
269   FROM  actor.usr u;
270
271 CREATE OR REPLACE VIEW reporter.circ_type AS
272 SELECT  id,
273         CASE WHEN opac_renewal OR phone_renewal OR desk_renewal OR auto_renewal
274                 THEN 'RENEWAL'
275                 ELSE 'CHECKOUT'
276         END AS "type"
277   FROM  action.circulation;
278
279 -- rhrr needs to be a real table, so it can be fast. To that end, we use
280 -- a materialized view updated via a trigger.
281 CREATE TABLE reporter.hold_request_record  AS
282 SELECT  id,
283         target,
284         hold_type,
285         CASE
286                 WHEN hold_type = 'T'
287                         THEN target
288                 WHEN hold_type = 'I'
289                         THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
290                 WHEN hold_type = 'V'
291                         THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
292                 WHEN hold_type IN ('C','R','F')
293                         THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
294                 WHEN hold_type = 'M'
295                         THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
296                 WHEN hold_type = 'P'
297                         THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = ahr.target)
298         END AS bib_record
299   FROM  action.hold_request ahr;
300
301 CREATE UNIQUE INDEX reporter_hold_request_record_pkey_idx ON reporter.hold_request_record (id);
302 CREATE INDEX reporter_hold_request_record_bib_record_idx ON reporter.hold_request_record (bib_record);
303
304 ALTER TABLE reporter.hold_request_record ADD PRIMARY KEY USING INDEX reporter_hold_request_record_pkey_idx;
305
306 CREATE OR REPLACE FUNCTION reporter.hold_request_record_mapper () RETURNS TRIGGER AS $$
307 BEGIN
308     IF TG_OP = 'INSERT' THEN
309         INSERT INTO reporter.hold_request_record (id, target, hold_type, bib_record)
310         SELECT  NEW.id,
311                 NEW.target,
312                 NEW.hold_type,
313                 CASE
314                     WHEN NEW.hold_type = 'T'
315                         THEN NEW.target
316                     WHEN NEW.hold_type = 'I'
317                         THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
318                     WHEN NEW.hold_type = 'V'
319                         THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
320                     WHEN NEW.hold_type IN ('C','R','F')
321                         THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
322                     WHEN NEW.hold_type = 'M'
323                         THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
324                     WHEN NEW.hold_type = 'P'
325                         THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
326                 END AS bib_record;
327     ELSIF TG_OP = 'UPDATE' AND (OLD.target <> NEW.target OR OLD.hold_type <> NEW.hold_type) THEN
328         UPDATE  reporter.hold_request_record
329           SET   target = NEW.target,
330                 hold_type = NEW.hold_type,
331                 bib_record = CASE
332                     WHEN NEW.hold_type = 'T'
333                         THEN NEW.target
334                     WHEN NEW.hold_type = 'I'
335                         THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
336                     WHEN NEW.hold_type = 'V'
337                         THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
338                     WHEN NEW.hold_type IN ('C','R','F')
339                         THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
340                     WHEN NEW.hold_type = 'M'
341                         THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
342                     WHEN NEW.hold_type = 'P'
343                         THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
344                 END
345          WHERE  id = NEW.id;
346     END IF;
347     RETURN NEW;
348 END;
349 $$ LANGUAGE PLPGSQL;
350
351 CREATE TRIGGER reporter_hold_request_record_trigger AFTER INSERT OR UPDATE ON action.hold_request
352     FOR EACH ROW EXECUTE PROCEDURE reporter.hold_request_record_mapper();
353
354 CREATE OR REPLACE VIEW reporter.xact_billing_totals AS
355 SELECT  b.xact,
356         SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided,
357         SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided,
358         SUM( amount ) as total
359   FROM  money.billing b
360   GROUP BY 1;
361
362 CREATE OR REPLACE VIEW reporter.xact_paid_totals AS
363 SELECT  b.xact,
364         SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided,
365         SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided,
366         SUM( amount ) as total
367   FROM  money.payment b
368   GROUP BY 1;
369
370 CREATE OR REPLACE VIEW reporter.overdue_circs AS
371 SELECT  *
372   FROM  "action".circulation
373   WHERE checkin_time is null
374         AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
375         AND due_date < now();
376
377 CREATE OR REPLACE VIEW reporter.overdue_reports AS
378  SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
379    FROM reporter.schedule s
380    JOIN reporter.report r ON r.id = s.report
381    JOIN actor.usr u ON s.runner = u.id
382    JOIN actor.card c ON c.id = u.card
383   WHERE s.start_time IS NULL AND s.run_time < now();
384
385 CREATE OR REPLACE VIEW reporter.pending_reports AS
386  SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
387    FROM reporter.schedule s
388    JOIN reporter.report r ON r.id = s.report
389    JOIN actor.usr u ON s.runner = u.id
390    JOIN actor.card c ON c.id = u.card
391   WHERE s.start_time IS NULL;
392
393 CREATE OR REPLACE VIEW reporter.currently_running AS
394  SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
395    FROM reporter.schedule s
396    JOIN reporter.report r ON r.id = s.report
397    JOIN actor.usr u ON s.runner = u.id
398    JOIN actor.card c ON c.id = u.card
399   WHERE s.start_time IS NOT NULL AND s.complete_time IS NULL;
400
401 COMMIT;
402