2 * Copyright (C) 2004-2008 Georgia Public Library Service
3 * Copyright (C) 2007-2008 Equinox Software, Inc.
4 * Mike Rylander <miker@esilibrary.com>
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.
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.
18 DROP SCHEMA IF EXISTS reporter CASCADE;
22 CREATE SCHEMA reporter;
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(),
30 shared BOOL NOT NULL DEFAULT FALSE,
31 share_with INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
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;
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(),
43 shared BOOL NOT NULL DEFAULT FALSE,
44 share_with INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
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;
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(),
56 shared BOOL NOT NULL DEFAULT FALSE,
57 share_with INT REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED
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;
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(),
69 description TEXT NOT NULL DEFAULT '',
71 folder INT NOT NULL REFERENCES reporter.template_folder (id) DEFERRABLE INITIALLY DEFERRED
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);
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,
85 folder INT NOT NULL REFERENCES reporter.report_folder (id) DEFERRABLE INITIALLY DEFERRED,
86 recur BOOL NOT NULL DEFAULT FALSE,
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);
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,
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,
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,''));
115 CREATE OR REPLACE VIEW reporter.simple_record AS
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;
152 CREATE OR REPLACE VIEW reporter.old_super_simple_record AS
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'
164 ELSE (SELECT ARRAY(SELECT json_array_elements_text(d.isbn::JSON)))
166 CASE WHEN d.issn = 'null'
168 ELSE (SELECT ARRAY(SELECT json_array_elements_text(d.issn::JSON)))
170 FROM biblio.record_entry r
171 JOIN metabib.wide_display_entry d ON (r.id = d.source);
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);
176 CREATE VIEW reporter.super_simple_record AS SELECT * FROM reporter.materialized_simple_record;
178 CREATE OR REPLACE FUNCTION reporter.simple_rec_update (r_id BIGINT, deleted BOOL) RETURNS BOOL AS $$
181 DELETE FROM reporter.materialized_simple_record WHERE id = r_id;
184 INSERT INTO reporter.materialized_simple_record SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record WHERE id = r_id;
192 CREATE OR REPLACE FUNCTION reporter.simple_rec_update (r_id BIGINT) RETURNS BOOL AS $$
193 SELECT reporter.simple_rec_update($1, FALSE);
196 CREATE OR REPLACE FUNCTION reporter.simple_rec_delete (r_id BIGINT) RETURNS BOOL AS $$
197 SELECT reporter.simple_rec_update($1, TRUE);
200 CREATE OR REPLACE FUNCTION reporter.simple_rec_trigger () RETURNS TRIGGER AS $func$
202 IF TG_OP = 'DELETE' THEN
203 PERFORM reporter.simple_rec_delete(NEW.id);
205 PERFORM reporter.simple_rec_update(NEW.id);
210 $func$ LANGUAGE PLPGSQL;
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;
216 CREATE OR REPLACE FUNCTION reporter.enable_materialized_simple_record_trigger () RETURNS VOID AS $$
218 TRUNCATE TABLE reporter.materialized_simple_record;
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;
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();
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();
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))
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))
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))
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))
255 END AS dewey_range_hundreds
256 FROM asset.call_number
257 WHERE call_number_dewey(label) ~ '^[0-9]'::text;
259 CREATE OR REPLACE VIEW reporter.demographic AS
265 WHEN AGE(u.dob) > '18 years'::INTERVAL
268 END AS general_division,
271 THEN 'No Date of Birth Entered'::text
272 WHEN age(u.dob::timestamp with time zone) >= '0 years'::interval and age(u.dob::timestamp with time zone) < '6 years'::interval
273 THEN 'Child 0-5 Years Old'::text
274 WHEN age(u.dob::timestamp with time zone) >= '6 years'::interval and age(u.dob::timestamp with time zone) < '13 years'::interval
275 THEN 'Child 6-12 Years Old'::text
276 WHEN age(u.dob::timestamp with time zone) >= '13 years'::interval and age(u.dob::timestamp with time zone) < '18 years'::interval
277 THEN 'Teen 13-17 Years Old'::text
278 WHEN age(u.dob::timestamp with time zone) >= '18 years'::interval and age(u.dob::timestamp with time zone) < '26 years'::interval
279 THEN 'Adult 18-25 Years Old'::text
280 WHEN age(u.dob::timestamp with time zone) >= '26 years'::interval and age(u.dob::timestamp with time zone) < '50 years'::interval
281 THEN 'Adult 26-49 Years Old'::text
282 WHEN age(u.dob::timestamp with time zone) >= '50 years'::interval and age(u.dob::timestamp with time zone) < '60 years'::interval
283 THEN 'Adult 50-59 Years Old'::text
284 WHEN age(u.dob::timestamp with time zone) >= '60 years'::interval and age(u.dob::timestamp with time zone) < '70 years'::interval
285 THEN 'Adult 60-69 Years Old'::text
286 WHEN age(u.dob::timestamp with time zone) >= '70 years'::interval
287 THEN 'Adult 70+'::text
292 CREATE OR REPLACE VIEW reporter.circ_type AS
294 CASE WHEN opac_renewal OR phone_renewal OR desk_renewal OR auto_renewal
298 FROM action.circulation;
300 -- rhrr needs to be a real table, so it can be fast. To that end, we use
301 -- a materialized view updated via a trigger.
302 CREATE TABLE reporter.hold_request_record AS
310 THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = ahr.target)
312 THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target)
313 WHEN hold_type IN ('C','R','F')
314 THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target)
316 THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target)
318 THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = ahr.target)
320 FROM action.hold_request ahr;
322 CREATE UNIQUE INDEX reporter_hold_request_record_pkey_idx ON reporter.hold_request_record (id);
323 CREATE INDEX reporter_hold_request_record_bib_record_idx ON reporter.hold_request_record (bib_record);
325 ALTER TABLE reporter.hold_request_record ADD PRIMARY KEY USING INDEX reporter_hold_request_record_pkey_idx;
327 CREATE OR REPLACE FUNCTION reporter.hold_request_record_mapper () RETURNS TRIGGER AS $$
329 IF TG_OP = 'INSERT' THEN
330 INSERT INTO reporter.hold_request_record (id, target, hold_type, bib_record)
335 WHEN NEW.hold_type = 'T'
337 WHEN NEW.hold_type = 'I'
338 THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
339 WHEN NEW.hold_type = 'V'
340 THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
341 WHEN NEW.hold_type IN ('C','R','F')
342 THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
343 WHEN NEW.hold_type = 'M'
344 THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
345 WHEN NEW.hold_type = 'P'
346 THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
348 ELSIF TG_OP = 'UPDATE' AND (OLD.target <> NEW.target OR OLD.hold_type <> NEW.hold_type) THEN
349 UPDATE reporter.hold_request_record
350 SET target = NEW.target,
351 hold_type = NEW.hold_type,
353 WHEN NEW.hold_type = 'T'
355 WHEN NEW.hold_type = 'I'
356 THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
357 WHEN NEW.hold_type = 'V'
358 THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
359 WHEN NEW.hold_type IN ('C','R','F')
360 THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
361 WHEN NEW.hold_type = 'M'
362 THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
363 WHEN NEW.hold_type = 'P'
364 THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
372 CREATE TRIGGER reporter_hold_request_record_trigger AFTER INSERT OR UPDATE ON action.hold_request
373 FOR EACH ROW EXECUTE PROCEDURE reporter.hold_request_record_mapper();
375 CREATE OR REPLACE VIEW reporter.xact_billing_totals AS
377 SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided,
378 SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided,
379 SUM( amount ) as total
383 CREATE OR REPLACE VIEW reporter.xact_paid_totals AS
385 SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided,
386 SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided,
387 SUM( amount ) as total
391 CREATE OR REPLACE VIEW reporter.overdue_circs AS
393 FROM "action".circulation
394 WHERE checkin_time is null
395 AND (stop_fines NOT IN ('LOST','CLAIMSRETURNED') OR stop_fines IS NULL)
396 AND due_date < now();
398 CREATE OR REPLACE VIEW reporter.overdue_reports AS
399 SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
400 FROM reporter.schedule s
401 JOIN reporter.report r ON r.id = s.report
402 JOIN actor.usr u ON s.runner = u.id
403 JOIN actor.card c ON c.id = u.card
404 WHERE s.start_time IS NULL AND s.run_time < now();
406 CREATE OR REPLACE VIEW reporter.pending_reports AS
407 SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
408 FROM reporter.schedule s
409 JOIN reporter.report r ON r.id = s.report
410 JOIN actor.usr u ON s.runner = u.id
411 JOIN actor.card c ON c.id = u.card
412 WHERE s.start_time IS NULL;
414 CREATE OR REPLACE VIEW reporter.currently_running AS
415 SELECT s.id, c.barcode AS runner_barcode, r.name, s.run_time, s.run_time - now() AS scheduled_wait_time
416 FROM reporter.schedule s
417 JOIN reporter.report r ON r.id = s.report
418 JOIN actor.usr u ON s.runner = u.id
419 JOIN actor.card c ON c.id = u.card
420 WHERE s.start_time IS NOT NULL AND s.complete_time IS NULL;