LP#1048822 - Fuller Title for reporter.super_simple_record
[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,
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
114 CREATE OR REPLACE VIEW reporter.simple_record AS
115 SELECT  r.id,
116         s.metarecord,
117         r.fingerprint,
118         r.quality,
119         r.tcn_source,
120         r.tcn_value,
121         title.value AS title,
122         uniform_title.value AS uniform_title,
123         author.value AS author,
124         publisher.value AS publisher,
125         SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate,
126         series_title.value AS series_title,
127         series_statement.value AS series_statement,
128         summary.value AS summary,
129         ARRAY_AGG( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) AS isbn,
130         ARRAY_AGG( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) AS issn,
131         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '650' AND subfield = 'a' AND record = r.id)) AS topic_subject,
132         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '651' AND subfield = 'a' AND record = r.id)) AS geographic_subject,
133         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '655' AND subfield = 'a' AND record = r.id)) AS genre,
134         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '600' AND subfield = 'a' AND record = r.id)) AS name_subject,
135         ARRAY((SELECT DISTINCT value FROM metabib.full_rec WHERE tag = '610' AND subfield = 'a' AND record = r.id)) AS corporate_subject,
136         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
137   FROM  biblio.record_entry r
138         JOIN metabib.metarecord_source_map s ON (s.source = r.id)
139         LEFT JOIN metabib.full_rec uniform_title ON (r.id = uniform_title.record AND uniform_title.tag = '240' AND uniform_title.subfield = 'a')
140         LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
141         LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a')
142         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')
143         LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND (pubdate.tag = '260' OR (publisher.tag = '264' AND publisher.ind2 = '1')) AND pubdate.subfield = 'c')
144         LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
145         LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
146         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')
147         LEFT JOIN metabib.full_rec series_statement ON (r.id = series_statement.record AND series_statement.tag = '490' AND series_statement.subfield = 'a')
148         LEFT JOIN metabib.full_rec summary ON (r.id = summary.record AND summary.tag = '520' AND summary.subfield = 'a')
149   GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14;
150
151 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
152 SELECT  r.id,
153     r.fingerprint,
154     r.quality,
155     r.tcn_source,
156     r.tcn_value,
157     CONCAT_WS(' ', FIRST(title.value),FIRST(title_np.val)) AS title,
158     FIRST(author.value) AS author,
159     STRING_AGG(DISTINCT publisher.value, ', ') AS publisher,
160     STRING_AGG(DISTINCT SUBSTRING(pubdate.value FROM $$\d+$$), ', ') AS pubdate,
161     CASE WHEN ARRAY_AGG( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') ) = '{NULL}'
162         THEN NULL
163         ELSE ARRAY_AGG( DISTINCT REPLACE(SUBSTRING(isbn.value FROM $$^\S+$$), '-', '') )
164     END AS isbn,
165     CASE WHEN ARRAY_AGG( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') ) = '{NULL}'
166         THEN NULL
167         ELSE ARRAY_AGG( DISTINCT REGEXP_REPLACE(issn.value, E'^\\S*(\\d{4})[-\\s](\\d{3,4}x?)', E'\\1 \\2') )
168     END AS issn
169   FROM  biblio.record_entry r
170     LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a')
171     LEFT JOIN ( -- Grab 245 N and P subfields in the order that they appear.
172       SELECT b.record, string_agg(val, ' ') AS val FROM (
173              SELECT title_np.record, title_np.value AS val
174               FROM metabib.full_rec title_np
175               WHERE
176               title_np.tag = '245'
177                         AND title_np.subfield IN ('p','n')
178                         ORDER BY title_np.id
179                 ) b
180                 GROUP BY 1
181          ) title_np ON (title_np.record=r.id)
182     LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a')
183     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')
184     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')
185     LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z'))
186     LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a')
187   GROUP BY 1,2,3,4,5;
188
189 CREATE TABLE reporter.materialized_simple_record AS SELECT * FROM reporter.old_super_simple_record WHERE 1=0;
190 ALTER TABLE reporter.materialized_simple_record ADD PRIMARY KEY (id);
191
192 CREATE VIEW reporter.super_simple_record AS SELECT * FROM reporter.materialized_simple_record;
193
194 CREATE OR REPLACE FUNCTION reporter.simple_rec_update (r_id BIGINT, deleted BOOL) RETURNS BOOL AS $$
195 BEGIN
196
197     DELETE FROM reporter.materialized_simple_record WHERE id = r_id;
198
199     IF NOT deleted THEN
200         INSERT INTO reporter.materialized_simple_record SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record WHERE id = r_id;
201     END IF;
202
203     RETURN TRUE;
204
205 END;
206 $$ LANGUAGE PLPGSQL;
207
208 CREATE OR REPLACE FUNCTION reporter.simple_rec_update (r_id BIGINT) RETURNS BOOL AS $$
209     SELECT reporter.simple_rec_update($1, FALSE);
210 $$ LANGUAGE SQL;
211
212 CREATE OR REPLACE FUNCTION reporter.simple_rec_delete (r_id BIGINT) RETURNS BOOL AS $$
213     SELECT reporter.simple_rec_update($1, TRUE);
214 $$ LANGUAGE SQL;
215
216 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
217 BEGIN
218     IF TG_OP = 'DELETE' THEN
219         PERFORM reporter.simple_rec_delete(NEW.id);
220     ELSE
221         PERFORM reporter.simple_rec_update(NEW.id);
222     END IF;
223
224     RETURN NEW;
225 END;
226 $func$ LANGUAGE PLPGSQL;
227
228 CREATE OR REPLACE FUNCTION reporter.disable_materialized_simple_record_trigger () RETURNS VOID AS $$
229     DROP TRIGGER IF EXISTS bbb_simple_rec_trigger ON biblio.record_entry;
230 $$ LANGUAGE SQL;
231
232 CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$
233
234     DELETE FROM reporter.materialized_simple_record;
235
236     INSERT INTO reporter.materialized_simple_record
237         (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn)
238         SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record;
239
240     CREATE TRIGGER bbb_simple_rec_trigger
241         AFTER INSERT OR UPDATE OR DELETE ON biblio.record_entry
242         FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger();
243
244 $$ LANGUAGE SQL;
245
246 CREATE OR REPLACE FUNCTION reporter.refresh_materialized_simple_record () RETURNS VOID AS $$
247     SELECT reporter.disable_materialized_simple_record_trigger();
248     SELECT reporter.enable_materialized_simple_record_trigger();
249 $$ LANGUAGE SQL;
250
251 CREATE OR REPLACE VIEW reporter.demographic AS
252 SELECT  u.id,
253         u.dob,
254         CASE
255                 WHEN u.dob IS NULL
256                         THEN 'Adult'
257                 WHEN AGE(u.dob) > '18 years'::INTERVAL
258                         THEN 'Adult'
259                 ELSE 'Juvenile'
260         END AS general_division
261   FROM  actor.usr u;
262
263 CREATE OR REPLACE VIEW reporter.circ_type AS
264 SELECT  id,
265         CASE WHEN opac_renewal OR phone_renewal OR desk_renewal
266                 THEN 'RENEWAL'
267                 ELSE 'CHECKOUT'
268         END AS "type"
269   FROM  action.circulation;
270
271 -- rhrr needs to be a real table, so it can be fast. To that end, we use
272 -- a materialized view updated via a trigger.
273 CREATE TABLE reporter.hold_request_record  AS
274 SELECT  id,
275         target,
276         hold_type,
277         CASE
278                 WHEN hold_type = 'T'
279                         THEN target
280                 WHEN hold_type = 'I'
281                         THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
282                 WHEN hold_type = 'V'
283                         THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
284                 WHEN hold_type IN ('C','R','F')
285                         THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
286                 WHEN hold_type = 'M'
287                         THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
288                 WHEN hold_type = 'P'
289                         THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = ahr.target)
290         END AS bib_record
291   FROM  action.hold_request ahr;
292
293 CREATE UNIQUE INDEX reporter_hold_request_record_pkey_idx ON reporter.hold_request_record (id);
294 CREATE INDEX reporter_hold_request_record_bib_record_idx ON reporter.hold_request_record (bib_record);
295
296 ALTER TABLE reporter.hold_request_record ADD PRIMARY KEY USING INDEX reporter_hold_request_record_pkey_idx;
297
298 CREATE OR REPLACE FUNCTION reporter.hold_request_record_mapper () RETURNS TRIGGER AS $$
299 BEGIN
300     IF TG_OP = 'INSERT' THEN
301         INSERT INTO reporter.hold_request_record (id, target, hold_type, bib_record)
302         SELECT  NEW.id,
303                 NEW.target,
304                 NEW.hold_type,
305                 CASE
306                     WHEN NEW.hold_type = 'T'
307                         THEN NEW.target
308                     WHEN NEW.hold_type = 'I'
309                         THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
310                     WHEN NEW.hold_type = 'V'
311                         THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
312                     WHEN NEW.hold_type IN ('C','R','F')
313                         THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
314                     WHEN NEW.hold_type = 'M'
315                         THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
316                     WHEN NEW.hold_type = 'P'
317                         THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
318                 END AS bib_record;
319     ELSIF TG_OP = 'UPDATE' AND (OLD.target <> NEW.target OR OLD.hold_type <> NEW.hold_type) THEN
320         UPDATE  reporter.hold_request_record
321           SET   target = NEW.target,
322                 hold_type = NEW.hold_type,
323                 bib_record = CASE
324                     WHEN NEW.hold_type = 'T'
325                         THEN NEW.target
326                     WHEN NEW.hold_type = 'I'
327                         THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
328                     WHEN NEW.hold_type = 'V'
329                         THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
330                     WHEN NEW.hold_type IN ('C','R','F')
331                         THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
332                     WHEN NEW.hold_type = 'M'
333                         THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
334                     WHEN NEW.hold_type = 'P'
335                         THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
336                 END
337          WHERE  id = NEW.id;
338     END IF;
339     RETURN NEW;
340 END;
341 $$ LANGUAGE PLPGSQL;
342
343 CREATE TRIGGER reporter_hold_request_record_trigger AFTER INSERT OR UPDATE ON action.hold_request
344     FOR EACH ROW EXECUTE PROCEDURE reporter.hold_request_record_mapper();
345
346 CREATE OR REPLACE VIEW reporter.xact_billing_totals AS
347 SELECT  b.xact,
348         SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided,
349         SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided,
350         SUM( amount ) as total
351   FROM  money.billing b
352   GROUP BY 1;
353
354 CREATE OR REPLACE VIEW reporter.xact_paid_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.payment b
360   GROUP BY 1;
361
362 CREATE OR REPLACE VIEW reporter.overdue_circs AS
363 SELECT  *
364   FROM  "action".circulation
365   WHERE checkin_time is null
366         AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
367         AND due_date < now();
368
369 CREATE OR REPLACE VIEW reporter.overdue_reports AS
370  SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
371    FROM reporter.schedule s
372    JOIN reporter.report r ON r.id = s.report
373    JOIN actor.usr u ON s.runner = u.id
374    JOIN actor.card c ON c.id = u.card
375   WHERE s.start_time IS NULL AND s.run_time < now();
376
377 CREATE OR REPLACE VIEW reporter.pending_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;
384
385 CREATE OR REPLACE VIEW reporter.currently_running 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 NOT NULL AND s.complete_time IS NULL;
392
393 COMMIT;
394