]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/090.schema.action.sql
Modify a pre-delete trigger, and add a post-delete trigger, to ensure that
[Evergreen.git] / Open-ILS / src / sql / Pg / 090.schema.action.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 action CASCADE;
19
20 BEGIN;
21
22 CREATE SCHEMA action;
23
24 CREATE TABLE action.in_house_use (
25         id              SERIAL                          PRIMARY KEY,
26         item            BIGINT                          NOT NULL REFERENCES asset.copy (id) DEFERRABLE INITIALLY DEFERRED,
27         staff           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
28         org_unit        INT                             NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
29         use_time        TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW()
30 );
31 CREATE INDEX action_in_house_use_staff_idx      ON action.in_house_use ( staff );
32
33 CREATE TABLE action.non_cataloged_circulation (
34         id              SERIAL                          PRIMARY KEY,
35         patron          INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
36         staff           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
37         circ_lib        INT                             NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
38         item_type       INT                             NOT NULL REFERENCES config.non_cataloged_type (id) DEFERRABLE INITIALLY DEFERRED,
39         circ_time       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW()
40 );
41 CREATE INDEX action_non_cat_circ_patron_idx ON action.non_cataloged_circulation ( patron );
42 CREATE INDEX action_non_cat_circ_staff_idx  ON action.non_cataloged_circulation ( staff );
43
44 CREATE TABLE action.non_cat_in_house_use (
45         id              SERIAL                          PRIMARY KEY,
46         item_type       BIGINT                          NOT NULL REFERENCES config.non_cataloged_type(id) DEFERRABLE INITIALLY DEFERRED,
47         staff           INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
48         org_unit        INT                             NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
49         use_time        TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW()
50 );
51 CREATE INDEX non_cat_in_house_use_staff_idx ON action.non_cat_in_house_use ( staff );
52
53 CREATE TABLE action.survey (
54         id              SERIAL                          PRIMARY KEY,
55         owner           INT                             NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
56         start_date      TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
57         end_date        TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW() + '10 years'::INTERVAL,
58         usr_summary     BOOL                            NOT NULL DEFAULT FALSE,
59         opac            BOOL                            NOT NULL DEFAULT FALSE,
60         poll            BOOL                            NOT NULL DEFAULT FALSE,
61         required        BOOL                            NOT NULL DEFAULT FALSE,
62         name            TEXT                            NOT NULL,
63         description     TEXT                            NOT NULL
64 );
65 CREATE UNIQUE INDEX asv_once_per_owner_idx ON action.survey (owner,name);
66
67 CREATE TABLE action.survey_question (
68         id              SERIAL  PRIMARY KEY,
69         survey          INT     NOT NULL REFERENCES action.survey DEFERRABLE INITIALLY DEFERRED,
70         question        TEXT    NOT NULL
71 );
72
73 CREATE TABLE action.survey_answer (
74         id              SERIAL  PRIMARY KEY,
75         question        INT     NOT NULL REFERENCES action.survey_question DEFERRABLE INITIALLY DEFERRED,
76         answer          TEXT    NOT NULL
77 );
78
79 CREATE SEQUENCE action.survey_response_group_id_seq;
80
81 CREATE TABLE action.survey_response (
82         id                      BIGSERIAL                       PRIMARY KEY,
83         response_group_id       INT,
84         usr                     INT, -- REFERENCES actor.usr
85         survey                  INT                             NOT NULL REFERENCES action.survey DEFERRABLE INITIALLY DEFERRED,
86         question                INT                             NOT NULL REFERENCES action.survey_question DEFERRABLE INITIALLY DEFERRED,
87         answer                  INT                             NOT NULL REFERENCES action.survey_answer DEFERRABLE INITIALLY DEFERRED,
88         answer_date             TIMESTAMP WITH TIME ZONE,
89         effective_date          TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW()
90 );
91 CREATE INDEX action_survey_response_usr_idx ON action.survey_response ( usr );
92
93 CREATE OR REPLACE FUNCTION action.survey_response_answer_date_fixup () RETURNS TRIGGER AS '
94 BEGIN
95         NEW.answer_date := NOW();
96         RETURN NEW;
97 END;
98 ' LANGUAGE 'plpgsql';
99 CREATE TRIGGER action_survey_response_answer_date_fixup_tgr
100         BEFORE INSERT ON action.survey_response
101         FOR EACH ROW
102         EXECUTE PROCEDURE action.survey_response_answer_date_fixup ();
103
104
105 CREATE TABLE action.circulation (
106         target_copy             BIGINT                          NOT NULL, -- asset.copy.id
107         circ_lib                INT                             NOT NULL, -- actor.org_unit.id
108         circ_staff              INT                             NOT NULL, -- actor.usr.id
109         checkin_staff           INT,                                      -- actor.usr.id
110         checkin_lib             INT,                                      -- actor.org_unit.id
111         renewal_remaining       INT                             NOT NULL, -- derived from "circ duration" rule
112         due_date                TIMESTAMP WITH TIME ZONE,
113         stop_fines_time         TIMESTAMP WITH TIME ZONE,
114         checkin_time            TIMESTAMP WITH TIME ZONE,
115         create_time             TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
116         duration                INTERVAL,                                 -- derived from "circ duration" rule
117         fine_interval           INTERVAL                        NOT NULL DEFAULT '1 day'::INTERVAL, -- derived from "circ fine" rule
118         recuring_fine           NUMERIC(6,2),                             -- derived from "circ fine" rule
119         max_fine                NUMERIC(6,2),                             -- derived from "max fine" rule
120         phone_renewal           BOOL                            NOT NULL DEFAULT FALSE,
121         desk_renewal            BOOL                            NOT NULL DEFAULT FALSE,
122         opac_renewal            BOOL                            NOT NULL DEFAULT FALSE,
123         duration_rule           TEXT                            NOT NULL, -- name of "circ duration" rule
124         recuring_fine_rule      TEXT                            NOT NULL, -- name of "circ fine" rule
125         max_fine_rule           TEXT                            NOT NULL, -- name of "max fine" rule
126         stop_fines              TEXT                            CHECK (stop_fines IN ('CHECKIN','CLAIMSRETURNED','LOST','MAXFINES','RENEW','LONGOVERDUE')),
127         workstation         INT        REFERENCES actor.workstation(id)
128                                        ON DELETE SET NULL
129                                                                    DEFERRABLE INITIALLY DEFERRED,
130         checkin_workstation INT        REFERENCES actor.workstation(id)
131                                        ON DELETE SET NULL
132                                                                    DEFERRABLE INITIALLY DEFERRED,
133         checkin_scan_time   TIMESTAMP WITH TIME ZONE
134 ) INHERITS (money.billable_xact);
135 ALTER TABLE action.circulation ADD PRIMARY KEY (id);
136 ALTER TABLE action.circulation
137         ADD COLUMN parent_circ BIGINT
138         REFERENCES action.circulation( id )
139         DEFERRABLE INITIALLY DEFERRED;
140 CREATE INDEX circ_open_xacts_idx ON action.circulation (usr) WHERE xact_finish IS NULL;
141 CREATE INDEX circ_outstanding_idx ON action.circulation (usr) WHERE checkin_time IS NULL;
142 CREATE INDEX circ_checkin_time ON "action".circulation (checkin_time) WHERE checkin_time IS NOT NULL;
143 CREATE INDEX circ_circ_lib_idx ON "action".circulation (circ_lib);
144 CREATE INDEX circ_open_date_idx ON "action".circulation (xact_start) WHERE xact_finish IS NULL;
145 CREATE INDEX circ_all_usr_idx       ON action.circulation ( usr );
146 CREATE INDEX circ_circ_staff_idx    ON action.circulation ( circ_staff );
147 CREATE INDEX circ_checkin_staff_idx ON action.circulation ( checkin_staff );
148 CREATE UNIQUE INDEX circ_parent_idx ON action.circulation ( parent_circ ) WHERE parent_circ IS NOT NULL;
149
150
151 CREATE TRIGGER mat_summary_create_tgr AFTER INSERT ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_create ();
152 CREATE TRIGGER mat_summary_change_tgr AFTER UPDATE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_update ();
153 CREATE TRIGGER mat_summary_remove_tgr AFTER DELETE ON action.circulation FOR EACH ROW EXECUTE PROCEDURE money.mat_summary_delete ();
154
155
156 CREATE TABLE action.aged_circulation (
157         usr_post_code           TEXT,
158         usr_home_ou             INT     NOT NULL,
159         usr_profile             INT     NOT NULL,
160         usr_birth_year          INT,
161         copy_call_number        INT     NOT NULL,
162         copy_location           INT     NOT NULL,
163         copy_owning_lib         INT     NOT NULL,
164         copy_circ_lib           INT     NOT NULL,
165         copy_bib_record         BIGINT  NOT NULL,
166         LIKE action.circulation
167
168 );
169 ALTER TABLE action.aged_circulation ADD PRIMARY KEY (id);
170 ALTER TABLE action.aged_circulation DROP COLUMN usr;
171 CREATE INDEX aged_circ_circ_lib_idx ON "action".aged_circulation (circ_lib);
172 CREATE INDEX aged_circ_start_idx ON "action".aged_circulation (xact_start);
173 CREATE INDEX aged_circ_copy_circ_lib_idx ON "action".aged_circulation (copy_circ_lib);
174 CREATE INDEX aged_circ_copy_owning_lib_idx ON "action".aged_circulation (copy_owning_lib);
175 CREATE INDEX aged_circ_copy_location_idx ON "action".aged_circulation (copy_location);
176
177 CREATE OR REPLACE VIEW action.all_circulation AS
178     SELECT  id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
179         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
180         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
181         stop_fines_time, checkin_time, create_time, duration, fine_interval, recuring_fine,
182         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recuring_fine_rule,
183         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
184       FROM  action.aged_circulation
185             UNION ALL
186     SELECT  DISTINCT circ.id,COALESCE(a.post_code,b.post_code) AS usr_post_code, p.home_ou AS usr_home_ou, p.profile AS usr_profile, EXTRACT(YEAR FROM p.dob)::INT AS usr_birth_year,
187         cp.call_number AS copy_call_number, cp.location AS copy_location, cn.owning_lib AS copy_owning_lib, cp.circ_lib AS copy_circ_lib,
188         cn.record AS copy_bib_record, circ.xact_start, circ.xact_finish, circ.target_copy, circ.circ_lib, circ.circ_staff, circ.checkin_staff,
189         circ.checkin_lib, circ.renewal_remaining, circ.due_date, circ.stop_fines_time, circ.checkin_time, circ.create_time, circ.duration,
190         circ.fine_interval, circ.recuring_fine, circ.max_fine, circ.phone_renewal, circ.desk_renewal, circ.opac_renewal, circ.duration_rule,
191         circ.recuring_fine_rule, circ.max_fine_rule, circ.stop_fines, circ.workstation, circ.checkin_workstation, circ.checkin_scan_time,
192         circ.parent_circ
193       FROM  action.circulation circ
194         JOIN asset.copy cp ON (circ.target_copy = cp.id)
195         JOIN asset.call_number cn ON (cp.call_number = cn.id)
196         JOIN actor.usr p ON (circ.usr = p.id)
197         LEFT JOIN actor.usr_address a ON (p.mailing_address = a.id)
198         LEFT JOIN actor.usr_address b ON (p.billing_address = a.id);
199
200 CREATE OR REPLACE FUNCTION action.age_circ_on_delete () RETURNS TRIGGER AS $$
201 DECLARE
202 found char := 'N';
203 BEGIN
204
205     -- If there are any renewals for this circulation, don't archive or delete
206     -- it yet.   We'll do so later, when we archive and delete the renewals.
207
208     SELECT 'Y' INTO found
209     FROM action.circulation
210     WHERE parent_circ = OLD.id
211     LIMIT 1;
212
213     IF found = 'Y' THEN
214         RETURN NULL;  -- don't delete
215         END IF;
216
217     -- Archive a copy of the old row to action.aged_circulation
218
219     INSERT INTO action.aged_circulation
220         (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
221         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
222         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
223         stop_fines_time, checkin_time, create_time, duration, fine_interval, recuring_fine,
224         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recuring_fine_rule,
225         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ)
226       SELECT
227         id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
228         copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
229         circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, due_date,
230         stop_fines_time, checkin_time, create_time, duration, fine_interval, recuring_fine,
231         max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recuring_fine_rule,
232         max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ
233         FROM action.all_circulation WHERE id = OLD.id;
234
235     RETURN OLD;
236 END;
237 $$ LANGUAGE 'plpgsql';
238
239 CREATE TRIGGER action_circulation_aging_tgr
240         BEFORE DELETE ON action.circulation
241         FOR EACH ROW
242         EXECUTE PROCEDURE action.age_circ_on_delete ();
243
244
245 CREATE OR REPLACE FUNCTION action.age_parent_circ_on_delete () RETURNS TRIGGER AS $$
246 BEGIN
247
248     -- Having deleted a renewal, we can delete the original circulation (or a previous
249     -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
250     -- deletion of any prior parents, etc. recursively.
251
252     IF OLD.parent_circ IS NOT NULL THEN
253         DELETE FROM action.circulation
254         WHERE id = OLD.parent_circ;
255     END IF;
256
257     RETURN OLD;
258 END;
259 $$ LANGUAGE 'plpgsql';
260
261 CREATE TRIGGER age_parent_circ
262         AFTER DELETE ON action.circulation
263         FOR EACH ROW
264         EXECUTE PROCEDURE action.age_parent_circ_on_delete ();
265
266
267 CREATE OR REPLACE VIEW action.open_circulation AS
268         SELECT  *
269           FROM  action.circulation
270           WHERE checkin_time IS NULL
271           ORDER BY due_date;
272                 
273
274 CREATE OR REPLACE VIEW action.billable_circulations AS
275         SELECT  *
276           FROM  action.circulation
277           WHERE xact_finish IS NULL;
278
279 CREATE VIEW stats.fleshed_circulation AS
280         SELECT  c.*,
281                 CAST(c.xact_start AS DATE) AS start_date_day,
282                 CAST(c.xact_finish AS DATE) AS finish_date_day,
283                 DATE_TRUNC('hour', c.xact_start) AS start_date_hour,
284                 DATE_TRUNC('hour', c.xact_finish) AS finish_date_hour,
285                 cp.call_number_label,
286                 cp.owning_lib,
287                 cp.item_lang,
288                 cp.item_type,
289                 cp.item_form
290         FROM    "action".circulation c
291                 JOIN stats.fleshed_copy cp ON (cp.id = c.target_copy);
292
293
294 CREATE OR REPLACE FUNCTION action.circulation_claims_returned () RETURNS TRIGGER AS $$
295 BEGIN
296         IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
297                 IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
298                         UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
299                 END IF;
300                 IF NEW.stop_fines = 'LOST' THEN
301                         UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
302                 END IF;
303         END IF;
304         RETURN NEW;
305 END;
306 $$ LANGUAGE 'plpgsql';
307 CREATE TRIGGER action_circulation_stop_fines_tgr
308         BEFORE UPDATE ON action.circulation
309         FOR EACH ROW
310         EXECUTE PROCEDURE action.circulation_claims_returned ();
311
312 CREATE TABLE action.hold_request_cancel_cause (
313     id      SERIAL  PRIMARY KEY,
314     label   TEXT    UNIQUE
315 );
316 INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (1,'Untargeted expiration');
317 INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (2,'Hold Shelf expiration');
318 INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (3,'Patron via phone');
319 INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (4,'Patron in person');
320 INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (5,'Staff forced');
321 INSERT INTO action.hold_request_cancel_cause (id,label) VALUES (6,'Patron via OPAC');
322 SELECT SETVAL('action.hold_request_cancel_cause_id_seq', 100);
323
324 CREATE TABLE action.hold_request (
325         id                      SERIAL                          PRIMARY KEY,
326         request_time            TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
327         capture_time            TIMESTAMP WITH TIME ZONE,
328         fulfillment_time        TIMESTAMP WITH TIME ZONE,
329         checkin_time            TIMESTAMP WITH TIME ZONE,
330         return_time             TIMESTAMP WITH TIME ZONE,
331         prev_check_time         TIMESTAMP WITH TIME ZONE,
332         expire_time             TIMESTAMP WITH TIME ZONE,
333         cancel_time             TIMESTAMP WITH TIME ZONE,
334         cancel_cause    INT REFERENCES action.hold_request_cancel_cause (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
335         cancel_note             TEXT,
336         target                  BIGINT                          NOT NULL, -- see hold_type
337         current_copy            BIGINT                          REFERENCES asset.copy (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
338         fulfillment_staff       INT                             REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
339         fulfillment_lib         INT                             REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
340         request_lib             INT                             NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
341         requestor               INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
342         usr                     INT                             NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
343         selection_ou            INT                             NOT NULL,
344         selection_depth         INT                             NOT NULL DEFAULT 0,
345         pickup_lib              INT                             NOT NULL REFERENCES actor.org_unit DEFERRABLE INITIALLY DEFERRED,
346         hold_type               TEXT                            NOT NULL CHECK (hold_type IN ('M','T','V','C')),
347         holdable_formats        TEXT,
348         phone_notify            TEXT,
349         email_notify            BOOL                            NOT NULL DEFAULT TRUE,
350         frozen                  BOOL                            NOT NULL DEFAULT FALSE,
351         thaw_date               TIMESTAMP WITH TIME ZONE,
352         shelf_time              TIMESTAMP WITH TIME ZONE
353 );
354
355 CREATE INDEX hold_request_target_idx ON action.hold_request (target);
356 CREATE INDEX hold_request_usr_idx ON action.hold_request (usr);
357 CREATE INDEX hold_request_pickup_lib_idx ON action.hold_request (pickup_lib);
358 CREATE INDEX hold_request_current_copy_idx ON action.hold_request (current_copy);
359 CREATE INDEX hold_request_prev_check_time_idx ON action.hold_request (prev_check_time);
360 CREATE INDEX hold_request_fulfillment_staff_idx ON action.hold_request ( fulfillment_staff );
361 CREATE INDEX hold_request_requestor_idx         ON action.hold_request ( requestor );
362
363
364 CREATE TABLE action.hold_request_note (
365
366     id     BIGSERIAL PRIMARY KEY,
367     hold   BIGINT    NOT NULL REFERENCES action.hold_request (id)
368                               ON DELETE CASCADE
369                               DEFERRABLE INITIALLY DEFERRED,
370     title  TEXT      NOT NULL,
371     body   TEXT      NOT NULL,
372     slip   BOOL      NOT NULL DEFAULT FALSE,
373     pub    BOOL      NOT NULL DEFAULT FALSE,
374     staff  BOOL      NOT NULL DEFAULT FALSE  -- created by staff
375
376 );
377 CREATE INDEX ahrn_hold_idx ON action.hold_request_note (hold);
378
379
380 CREATE TABLE action.hold_notification (
381         id              SERIAL                          PRIMARY KEY,
382         hold            INT                             NOT NULL REFERENCES action.hold_request (id)
383                                                                         ON DELETE CASCADE
384                                                                         DEFERRABLE INITIALLY DEFERRED,
385         notify_staff    INT                     REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
386         notify_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
387         method          TEXT                            NOT NULL, -- email address or phone number
388         note            TEXT
389 );
390 CREATE INDEX ahn_hold_idx ON action.hold_notification (hold);
391 CREATE INDEX ahn_notify_staff_idx ON action.hold_notification ( notify_staff );
392
393 CREATE TABLE action.hold_copy_map (
394         id              SERIAL  PRIMARY KEY,
395         hold            INT     NOT NULL REFERENCES action.hold_request (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
396         target_copy     BIGINT  NOT NULL REFERENCES asset.copy (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
397         CONSTRAINT copy_once_per_hold UNIQUE (hold,target_copy)
398 );
399 -- CREATE INDEX acm_hold_idx ON action.hold_copy_map (hold);
400 CREATE INDEX acm_copy_idx ON action.hold_copy_map (target_copy);
401
402 CREATE TABLE action.transit_copy (
403         id                      SERIAL                          PRIMARY KEY,
404         source_send_time        TIMESTAMP WITH TIME ZONE,
405         dest_recv_time          TIMESTAMP WITH TIME ZONE,
406         target_copy             BIGINT                          NOT NULL REFERENCES asset.copy (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
407         source                  INT                             NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
408         dest                    INT                             NOT NULL REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED,
409         prev_hop                INT                             REFERENCES action.transit_copy (id) DEFERRABLE INITIALLY DEFERRED,
410         copy_status             INT                             NOT NULL REFERENCES config.copy_status (id) DEFERRABLE INITIALLY DEFERRED,
411         persistant_transfer     BOOL                            NOT NULL DEFAULT FALSE
412 );
413 CREATE INDEX active_transit_dest_idx ON "action".transit_copy (dest); 
414 CREATE INDEX active_transit_source_idx ON "action".transit_copy (source);
415 CREATE INDEX active_transit_cp_idx ON "action".transit_copy (target_copy);
416
417
418 CREATE TABLE action.hold_transit_copy (
419         hold    INT     REFERENCES action.hold_request (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED
420 ) INHERITS (action.transit_copy);
421 ALTER TABLE action.hold_transit_copy ADD PRIMARY KEY (id);
422 ALTER TABLE action.hold_transit_copy ADD CONSTRAINT ahtc_tc_fkey FOREIGN KEY (target_copy) REFERENCES asset.copy (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
423 CREATE INDEX active_hold_transit_dest_idx ON "action".hold_transit_copy (dest);
424 CREATE INDEX active_hold_transit_source_idx ON "action".hold_transit_copy (source);
425 CREATE INDEX active_hold_transit_cp_idx ON "action".hold_transit_copy (target_copy);
426
427
428 CREATE TABLE action.unfulfilled_hold_list (
429         id              BIGSERIAL                       PRIMARY KEY,
430         current_copy    BIGINT                          NOT NULL,
431         hold            INT                             NOT NULL,
432         circ_lib        INT                             NOT NULL,
433         fail_time       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW()
434 );
435 CREATE INDEX uhr_hold_idx ON action.unfulfilled_hold_list (hold);
436
437 CREATE OR REPLACE VIEW action.unfulfilled_hold_loops AS
438     SELECT  u.hold,
439             c.circ_lib,
440             count(*)
441       FROM  action.unfulfilled_hold_list u
442             JOIN asset.copy c ON (c.id = u.current_copy)
443       GROUP BY 1,2;
444
445 CREATE OR REPLACE VIEW action.unfulfilled_hold_min_loop AS
446     SELECT  hold,
447             min(count)
448       FROM  action.unfulfilled_hold_loops
449       GROUP BY 1;
450
451 CREATE OR REPLACE VIEW action.unfulfilled_hold_innermost_loop AS
452     SELECT  DISTINCT l.*
453       FROM  action.unfulfilled_hold_loops l
454             JOIN action.unfulfilled_hold_min_loop m USING (hold)
455       WHERE l.count = m.min;
456
457
458 COMMIT;
459