great shuffling, not finished
[migration-tools.git] / sql / base / 99-deprecated.sql
1
2 CREATE OR REPLACE FUNCTION migration_tools.base_profile_map (TEXT) RETURNS TEXT AS $$
3     DECLARE
4         migration_schema ALIAS FOR $1;
5         output TEXT;
6     BEGIN
7         FOR output IN
8             EXECUTE 'SELECT ''' || migration_schema || '.'' || value FROM ' || migration_schema || '.config WHERE key = ''base_profile_map'';'
9         LOOP
10             RETURN output;
11         END LOOP;
12     END;
13 $$ LANGUAGE PLPGSQL STRICT STABLE;
14
15 CREATE OR REPLACE FUNCTION migration_tools.base_item_dynamic_field_map (TEXT) RETURNS TEXT AS $$
16     DECLARE
17         migration_schema ALIAS FOR $1;
18         output TEXT;
19     BEGIN
20         FOR output IN
21             EXECUTE 'SELECT ''' || migration_schema || '.'' || value FROM ' || migration_schema || '.config WHERE key = ''base_item_dynamic_field_map'';'
22         LOOP
23             RETURN output;
24         END LOOP;
25     END;
26 $$ LANGUAGE PLPGSQL STRICT STABLE;
27
28 CREATE OR REPLACE FUNCTION migration_tools.base_copy_location_map (TEXT) RETURNS TEXT AS $$
29     DECLARE
30         migration_schema ALIAS FOR $1;
31         output TEXT;
32     BEGIN
33         FOR output IN
34             EXECUTE 'SELECT ''' || migration_schema || '.'' || value FROM ' || migration_schema || '.config WHERE key = ''base_copy_location_map'';'
35         LOOP
36             RETURN output;
37         END LOOP;
38     END;
39 $$ LANGUAGE PLPGSQL STRICT STABLE;
40
41 CREATE OR REPLACE FUNCTION migration_tools.base_circ_field_map (TEXT) RETURNS TEXT AS $$
42     DECLARE
43         migration_schema ALIAS FOR $1;
44         output TEXT;
45     BEGIN
46         FOR output IN
47             EXECUTE 'SELECT ''' || migration_schema || '.'' || value FROM ' || migration_schema || '.config WHERE key = ''base_circ_field_map'';'
48         LOOP
49             RETURN output;
50         END LOOP;
51     END;
52 $$ LANGUAGE PLPGSQL STRICT STABLE;
53
54 CREATE OR REPLACE FUNCTION migration_tools.map_base_patron_profile (TEXT,TEXT,INTEGER) RETURNS VOID AS $$
55     DECLARE
56         migration_schema ALIAS FOR $1;
57         profile_map TEXT;
58         patron_table ALIAS FOR $2;
59         default_patron_profile ALIAS FOR $3;
60         sql TEXT;
61         sql_update TEXT;
62         sql_where1 TEXT := '';
63         sql_where2 TEXT := '';
64         sql_where3 TEXT := '';
65         output RECORD;
66     BEGIN
67         SELECT migration_tools.base_profile_map(migration_schema) INTO STRICT profile_map;
68         FOR output IN 
69             EXECUTE 'SELECT * FROM ' || profile_map || E' ORDER BY id;'
70         LOOP
71             sql_update := 'UPDATE ' || patron_table || ' AS u SET profile = perm_grp_id FROM ' || profile_map || ' AS m WHERE ';
72             sql_where1 := NULLIF(output.legacy_field1,'') || ' = ' || quote_literal( output.legacy_value1 ) || ' AND legacy_field1 = ' || quote_literal(output.legacy_field1) || ' AND legacy_value1 = ' || quote_literal(output.legacy_value1);
73             sql_where2 := NULLIF(output.legacy_field2,'') || ' = ' || quote_literal( output.legacy_value2 ) || ' AND legacy_field2 = ' || quote_literal(output.legacy_field2) || ' AND legacy_value2 = ' || quote_literal(output.legacy_value2);
74             sql_where3 := NULLIF(output.legacy_field3,'') || ' = ' || quote_literal( output.legacy_value3 ) || ' AND legacy_field3 = ' || quote_literal(output.legacy_field3) || ' AND legacy_value3 = ' || quote_literal(output.legacy_value3);
75             sql := sql_update || COALESCE(sql_where1,'') || CASE WHEN sql_where1 <> '' AND sql_where2<> ''  THEN ' AND ' ELSE '' END || COALESCE(sql_where2,'') || CASE WHEN sql_where2 <> '' AND sql_where3 <> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where3,'') || ';';
76             --RAISE INFO 'sql = %', sql;
77             PERFORM migration_tools.exec( $1, sql );
78         END LOOP;
79         PERFORM migration_tools.exec( $1, 'UPDATE ' || patron_table || ' AS u SET profile = ' || quote_literal(default_patron_profile) || ' WHERE profile IS NULL;'  );
80         BEGIN
81             PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''last_base_patron_mapping_profile'', now() );' );
82         EXCEPTION
83             WHEN OTHERS THEN PERFORM migration_tools.exec( $1, 'UPDATE ' || migration_schema || '.config SET value = now() WHERE key = ''last_base_patron_mapping_profile'';' );
84         END;
85     END;
86 $$ LANGUAGE PLPGSQL STRICT STABLE;
87
88 CREATE OR REPLACE FUNCTION migration_tools.map_base_item_table_dynamic (TEXT,TEXT) RETURNS VOID AS $$
89     DECLARE
90         migration_schema ALIAS FOR $1;
91         field_map TEXT;
92         item_table ALIAS FOR $2;
93         sql TEXT;
94         sql_update TEXT;
95         sql_where1 TEXT := '';
96         sql_where2 TEXT := '';
97         sql_where3 TEXT := '';
98         output RECORD;
99     BEGIN
100         SELECT migration_tools.base_item_dynamic_field_map(migration_schema) INTO STRICT field_map;
101         FOR output IN 
102             EXECUTE 'SELECT * FROM ' || field_map || E' ORDER BY id;'
103         LOOP
104             sql_update := 'UPDATE ' || item_table || ' AS i SET ' || output.evergreen_field || E' = ' || quote_literal(output.evergreen_value) || '::' || output.evergreen_datatype || E' FROM ' || field_map || ' AS m WHERE ';
105             sql_where1 := NULLIF(output.legacy_field1,'') || ' = ' || quote_literal( output.legacy_value1 ) || ' AND legacy_field1 = ' || quote_literal(output.legacy_field1) || ' AND legacy_value1 = ' || quote_literal(output.legacy_value1);
106             sql_where2 := NULLIF(output.legacy_field2,'') || ' = ' || quote_literal( output.legacy_value2 ) || ' AND legacy_field2 = ' || quote_literal(output.legacy_field2) || ' AND legacy_value2 = ' || quote_literal(output.legacy_value2);
107             sql_where3 := NULLIF(output.legacy_field3,'') || ' = ' || quote_literal( output.legacy_value3 ) || ' AND legacy_field3 = ' || quote_literal(output.legacy_field3) || ' AND legacy_value3 = ' || quote_literal(output.legacy_value3);
108             sql := sql_update || COALESCE(sql_where1,'') || CASE WHEN sql_where1 <> '' AND sql_where2<> ''  THEN ' AND ' ELSE '' END || COALESCE(sql_where2,'') || CASE WHEN sql_where2 <> '' AND sql_where3 <> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where3,'') || ';';
109             --RAISE INFO 'sql = %', sql;
110             PERFORM migration_tools.exec( $1, sql );
111         END LOOP;
112         BEGIN
113             PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''last_base_item_mapping_dynamic'', now() );' );
114         EXCEPTION
115             WHEN OTHERS THEN PERFORM migration_tools.exec( $1, 'UPDATE ' || migration_schema || '.config SET value = now() WHERE key = ''last_base_item_mapping_dynamic'';' );
116         END;
117     END;
118 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
119
120 CREATE OR REPLACE FUNCTION migration_tools.map_base_item_table_locations (TEXT,TEXT) RETURNS VOID AS $$
121     DECLARE
122         migration_schema ALIAS FOR $1;
123         base_copy_location_map TEXT;
124         item_table ALIAS FOR $2;
125         sql TEXT;
126         sql_update TEXT;
127         sql_where1 TEXT := '';
128         sql_where2 TEXT := '';
129         sql_where3 TEXT := '';
130         output RECORD;
131     BEGIN
132         SELECT migration_tools.base_copy_location_map(migration_schema) INTO STRICT base_copy_location_map;
133         FOR output IN 
134             EXECUTE 'SELECT * FROM ' || base_copy_location_map || E' ORDER BY id;'
135         LOOP
136             sql_update := 'UPDATE ' || item_table || ' AS i SET location = m.location FROM ' || base_copy_location_map || ' AS m WHERE ';
137             sql_where1 := NULLIF(output.legacy_field1,'') || ' = ' || quote_literal( output.legacy_value1 ) || ' AND legacy_field1 = ' || quote_literal(output.legacy_field1) || ' AND legacy_value1 = ' || quote_literal(output.legacy_value1);
138             sql_where2 := NULLIF(output.legacy_field2,'') || ' = ' || quote_literal( output.legacy_value2 ) || ' AND legacy_field2 = ' || quote_literal(output.legacy_field2) || ' AND legacy_value2 = ' || quote_literal(output.legacy_value2);
139             sql_where3 := NULLIF(output.legacy_field3,'') || ' = ' || quote_literal( output.legacy_value3 ) || ' AND legacy_field3 = ' || quote_literal(output.legacy_field3) || ' AND legacy_value3 = ' || quote_literal(output.legacy_value3);
140             sql := sql_update || COALESCE(sql_where1,'') || CASE WHEN sql_where1 <> '' AND sql_where2<> ''  THEN ' AND ' ELSE '' END || COALESCE(sql_where2,'') || CASE WHEN sql_where2 <> '' AND sql_where3 <> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where3,'') || ';';
141             --RAISE INFO 'sql = %', sql;
142             PERFORM migration_tools.exec( $1, sql );
143         END LOOP;
144         BEGIN
145             PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''last_base_item_mapping_locations'', now() );' );
146         EXCEPTION
147             WHEN OTHERS THEN PERFORM migration_tools.exec( $1, 'UPDATE ' || migration_schema || '.config SET value = now() WHERE key = ''last_base_item_mapping_locations'';' );
148         END;
149     END;
150 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
151
152 -- circulate       loan period     max renewals    max out fine amount     fine interval   max fine        item field 1    item value 1    item field 2    item value 2    patron field 1  patron value 1  patron field 2  patron value 2
153 CREATE OR REPLACE FUNCTION migration_tools.map_base_circ_table_dynamic (TEXT,TEXT,TEXT,TEXT) RETURNS VOID AS $$
154     DECLARE
155         migration_schema ALIAS FOR $1;
156         field_map TEXT;
157         circ_table ALIAS FOR $2;
158         item_table ALIAS FOR $3;
159         patron_table ALIAS FOR $4;
160         sql TEXT;
161         sql_update TEXT;
162         sql_where1 TEXT := '';
163         sql_where2 TEXT := '';
164         sql_where3 TEXT := '';
165         sql_where4 TEXT := '';
166         output RECORD;
167     BEGIN
168         SELECT migration_tools.base_circ_field_map(migration_schema) INTO STRICT field_map;
169         FOR output IN 
170             EXECUTE 'SELECT * FROM ' || field_map || E' ORDER BY id;'
171         LOOP
172             sql_update := 'UPDATE ' || circ_table || ' AS c SET duration = ' || quote_literal(output.loan_period) || '::INTERVAL, renewal_remaining = ' || quote_literal(output.max_renewals) || '::INTEGER, recuring_fine = ' || quote_literal(output.fine_amount) || '::NUMERIC(6,2), fine_interval = ' || quote_literal(output.fine_interval) || '::INTERVAL, max_fine = ' || quote_literal(output.max_fine) || '::NUMERIC(6,2) FROM ' || field_map || ' AS m, ' || item_table || ' AS i, ' || patron_table || ' AS u WHERE c.usr = u.id AND c.target_copy = i.id AND ';
173             sql_where1 := NULLIF(output.item_field1,'') || ' = ' || quote_literal( output.item_value1 ) || ' AND item_field1 = ' || quote_literal(output.item_field1) || ' AND item_value1 = ' || quote_literal(output.item_value1);
174             sql_where2 := NULLIF(output.item_field2,'') || ' = ' || quote_literal( output.item_value2 ) || ' AND item_field2 = ' || quote_literal(output.item_field2) || ' AND item_value2 = ' || quote_literal(output.item_value2);
175             sql_where3 := NULLIF(output.patron_field1,'') || ' = ' || quote_literal( output.patron_value1 ) || ' AND patron_field1 = ' || quote_literal(output.patron_field1) || ' AND patron_value1 = ' || quote_literal(output.patron_value1);
176             sql_where4 := NULLIF(output.patron_field2,'') || ' = ' || quote_literal( output.patron_value2 ) || ' AND patron_field2 = ' || quote_literal(output.patron_field2) || ' AND patron_value2 = ' || quote_literal(output.patron_value2);
177             sql := sql_update || COALESCE(sql_where1,'') || CASE WHEN sql_where1 <> '' AND sql_where2<> ''  THEN ' AND ' ELSE '' END || COALESCE(sql_where2,'') || CASE WHEN sql_where2 <> '' AND sql_where3 <> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where3,'') || CASE WHEN sql_where3 <> '' AND sql_where4 <> '' THEN ' AND ' ELSE '' END || COALESCE(sql_where4,'') || ';';
178             --RAISE INFO 'sql = %', sql;
179             PERFORM migration_tools.exec( $1, sql );
180         END LOOP;
181         BEGIN
182             PERFORM migration_tools.exec( $1, 'INSERT INTO ' || migration_schema || '.config (key,value) VALUES ( ''last_base_circ_field_mapping'', now() );' );
183         EXCEPTION
184             WHEN OTHERS THEN PERFORM migration_tools.exec( $1, 'UPDATE ' || migration_schema || '.config SET value = now() WHERE key = ''last_base_circ_field_mapping'';' );
185         END;
186     END;
187 $$ LANGUAGE PLPGSQL STRICT VOLATILE;
188
189 CREATE OR REPLACE FUNCTION migration_tools.apply_circ_matrix_before_20( tablename TEXT ) RETURNS VOID AS $$
190
191 -- Usage:
192 --
193 --   First make sure the circ matrix is loaded and the circulations
194 --   have been staged to the extent possible (but at the very least
195 --   circ_lib, target_copy, usr, and *_renewal).  User profiles and
196 --   circ modifiers must also be in place.
197 --
198 --   SELECT migration_tools.apply_circ_matrix('m_pioneer.action_circulation');
199 --
200
201 DECLARE
202   circ_lib             INT;
203   target_copy          INT;
204   usr                  INT;
205   is_renewal           BOOLEAN;
206   this_duration_rule   INT;
207   this_fine_rule       INT;
208   this_max_fine_rule   INT;
209   rcd                  config.rule_circ_duration%ROWTYPE;
210   rrf                  config.rule_recurring_fine%ROWTYPE;
211   rmf                  config.rule_max_fine%ROWTYPE;
212   circ                 INT;
213   n                    INT := 0;
214   n_circs              INT;
215   
216 BEGIN
217
218   EXECUTE 'SELECT COUNT(*) FROM ' || tablename || ';' INTO n_circs;
219
220   FOR circ IN EXECUTE ('SELECT id FROM ' || tablename) LOOP
221
222     -- Fetch the correct rules for this circulation
223     EXECUTE ('
224       SELECT
225         circ_lib,
226         target_copy,
227         usr,
228         CASE
229           WHEN phone_renewal OR desk_renewal OR opac_renewal THEN TRUE
230           ELSE FALSE
231         END
232       FROM ' || tablename || ' WHERE id = ' || circ || ';')
233       INTO circ_lib, target_copy, usr, is_renewal ;
234     SELECT
235       INTO this_duration_rule,
236            this_fine_rule,
237            this_max_fine_rule
238       duration_rule,
239       recuring_fine_rule,
240       max_fine_rule
241       FROM action.find_circ_matrix_matchpoint(
242         circ_lib,
243         target_copy,
244         usr,
245         is_renewal
246         );
247     SELECT INTO rcd * FROM config.rule_circ_duration
248       WHERE id = this_duration_rule;
249     SELECT INTO rrf * FROM config.rule_recurring_fine
250       WHERE id = this_fine_rule;
251     SELECT INTO rmf * FROM config.rule_max_fine
252       WHERE id = this_max_fine_rule;
253
254     -- Apply the rules to this circulation
255     EXECUTE ('UPDATE ' || tablename || ' c
256     SET
257       duration_rule = rcd.name,
258       recuring_fine_rule = rrf.name,
259       max_fine_rule = rmf.name,
260       duration = rcd.normal,
261       recuring_fine = rrf.normal,
262       max_fine =
263         CASE rmf.is_percent
264           WHEN TRUE THEN (rmf.amount / 100.0) * ac.price
265           ELSE rmf.amount
266         END,
267       renewal_remaining = rcd.max_renewals
268     FROM
269       config.rule_circ_duration rcd,
270       config.rule_recuring_fine rrf,
271       config.rule_max_fine rmf,
272                         asset.copy ac
273     WHERE
274       rcd.id = ' || this_duration_rule || ' AND
275       rrf.id = ' || this_fine_rule || ' AND
276       rmf.id = ' || this_max_fine_rule || ' AND
277                         ac.id = c.target_copy AND
278       c.id = ' || circ || ';');
279
280     -- Keep track of where we are in the process
281     n := n + 1;
282     IF (n % 100 = 0) THEN
283       RAISE INFO '%', n || ' of ' || n_circs
284         || ' (' || (100*n/n_circs) || '%) circs updated.';
285     END IF;
286
287   END LOOP;
288
289   RETURN;
290 END;
291
292 $$ LANGUAGE plpgsql;
293
294 CREATE OR REPLACE FUNCTION migration_tools.apply_circ_matrix_after_20( tablename TEXT ) RETURNS VOID AS $$
295
296 -- Usage:
297 --
298 --   First make sure the circ matrix is loaded and the circulations
299 --   have been staged to the extent possible (but at the very least
300 --   circ_lib, target_copy, usr, and *_renewal).  User profiles and
301 --   circ modifiers must also be in place.
302 --
303 --   SELECT migration_tools.apply_circ_matrix('m_pioneer.action_circulation');
304 --
305
306 DECLARE
307   circ_lib             INT;
308   target_copy          INT;
309   usr                  INT;
310   is_renewal           BOOLEAN;
311   this_duration_rule   INT;
312   this_fine_rule       INT;
313   this_max_fine_rule   INT;
314   rcd                  config.rule_circ_duration%ROWTYPE;
315   rrf                  config.rule_recurring_fine%ROWTYPE;
316   rmf                  config.rule_max_fine%ROWTYPE;
317   circ                 INT;
318   n                    INT := 0;
319   n_circs              INT;
320   
321 BEGIN
322
323   EXECUTE 'SELECT COUNT(*) FROM ' || tablename || ';' INTO n_circs;
324
325   FOR circ IN EXECUTE ('SELECT id FROM ' || tablename) LOOP
326
327     -- Fetch the correct rules for this circulation
328     EXECUTE ('
329       SELECT
330         circ_lib,
331         target_copy,
332         usr,
333         CASE
334           WHEN phone_renewal OR desk_renewal OR opac_renewal THEN TRUE
335           ELSE FALSE
336         END
337       FROM ' || tablename || ' WHERE id = ' || circ || ';')
338       INTO circ_lib, target_copy, usr, is_renewal ;
339     SELECT
340       INTO this_duration_rule,
341            this_fine_rule,
342            this_max_fine_rule
343       (matchpoint).duration_rule,
344       (matchpoint).recurring_fine_rule,
345       (matchpoint).max_fine_rule
346       FROM action.find_circ_matrix_matchpoint(
347         circ_lib,
348         target_copy,
349         usr,
350         is_renewal
351         );
352     SELECT INTO rcd * FROM config.rule_circ_duration
353       WHERE id = this_duration_rule;
354     SELECT INTO rrf * FROM config.rule_recurring_fine
355       WHERE id = this_fine_rule;
356     SELECT INTO rmf * FROM config.rule_max_fine
357       WHERE id = this_max_fine_rule;
358
359     -- Apply the rules to this circulation
360     EXECUTE ('UPDATE ' || tablename || ' c
361     SET
362       duration_rule = rcd.name,
363       recurring_fine_rule = rrf.name,
364       max_fine_rule = rmf.name,
365       duration = rcd.normal,
366       recurring_fine = rrf.normal,
367       max_fine =
368         CASE rmf.is_percent
369           WHEN TRUE THEN (rmf.amount / 100.0) * ac.price
370           ELSE rmf.amount
371         END,
372       renewal_remaining = rcd.max_renewals,
373       grace_period = rrf.grace_period
374     FROM
375       config.rule_circ_duration rcd,
376       config.rule_recurring_fine rrf,
377       config.rule_max_fine rmf,
378                         asset.copy ac
379     WHERE
380       rcd.id = ' || this_duration_rule || ' AND
381       rrf.id = ' || this_fine_rule || ' AND
382       rmf.id = ' || this_max_fine_rule || ' AND
383                         ac.id = c.target_copy AND
384       c.id = ' || circ || ';');
385
386     -- Keep track of where we are in the process
387     n := n + 1;
388     IF (n % 100 = 0) THEN
389       RAISE INFO '%', n || ' of ' || n_circs
390         || ' (' || (100*n/n_circs) || '%) circs updated.';
391     END IF;
392
393   END LOOP;
394
395   RETURN;
396 END;
397
398 $$ LANGUAGE plpgsql;
399
400 CREATE OR REPLACE FUNCTION migration_tools.insert_856_9_conditional (TEXT, TEXT) RETURNS TEXT AS $$
401
402   ## USAGE: UPDATE biblio.record_entry SET marc = migration_tools.insert_856_9(marc, 'ABC') WHERE [...];
403
404   my ($marcxml, $shortname) = @_;
405
406   use MARC::Record;
407   use MARC::File::XML;
408
409   my $xml = $marcxml;
410
411   eval {
412     my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
413
414     foreach my $field ( $marc->field('856') ) {
415       if ( scalar(grep( /(contentreserve|netlibrary|overdrive)\.com/i, $field->subfield('u'))) > 0 &&
416            ! ( $field->as_string('9') =~ m/$shortname/ ) ) {
417         $field->add_subfields( '9' => $shortname );
418                                 $field->update( ind2 => '0');
419       }
420     }
421
422     $xml = $marc->as_xml_record;
423     $xml =~ s/^<\?.+?\?>$//mo;
424     $xml =~ s/\n//sgo;
425     $xml =~ s/>\s+</></sgo;
426   };
427
428   return $xml;
429
430 $$ LANGUAGE PLPERLU STABLE;
431
432 CREATE OR REPLACE FUNCTION migration_tools.insert_856_9 (TEXT, TEXT) RETURNS TEXT AS $$
433
434   ## USAGE: UPDATE biblio.record_entry SET marc = migration_tools.insert_856_9(marc, 'ABC') WHERE [...];
435
436   my ($marcxml, $shortname) = @_;
437
438   use MARC::Record;
439   use MARC::File::XML;
440
441   my $xml = $marcxml;
442
443   eval {
444     my $marc = MARC::Record->new_from_xml($marcxml, 'UTF-8');
445
446     foreach my $field ( $marc->field('856') ) {
447       if ( ! $field->as_string('9') ) {
448         $field->add_subfields( '9' => $shortname );
449       }
450     }
451
452     $xml = $marc->as_xml_record;
453     $xml =~ s/^<\?.+?\?>$//mo;
454     $xml =~ s/\n//sgo;
455     $xml =~ s/>\s+</></sgo;
456   };
457
458   return $xml;
459
460 $$ LANGUAGE PLPERLU STABLE;
461
462 CREATE OR REPLACE FUNCTION migration_tools.refresh_opac_visible_copies ( ) RETURNS VOID AS $$
463
464 BEGIN   
465
466         DELETE FROM asset.opac_visible_copies;
467
468         INSERT INTO asset.opac_visible_copies (id, circ_lib, record)
469                 SELECT DISTINCT
470                         cp.id, cp.circ_lib, cn.record
471                 FROM
472                         asset.copy cp
473                         JOIN asset.call_number cn ON (cn.id = cp.call_number)
474                         JOIN actor.org_unit a ON (cp.circ_lib = a.id)
475                         JOIN asset.copy_location cl ON (cp.location = cl.id)
476                         JOIN config.copy_status cs ON (cp.status = cs.id)
477                         JOIN biblio.record_entry b ON (cn.record = b.id)
478                 WHERE 
479                         NOT cp.deleted AND
480                         NOT cn.deleted AND
481                         NOT b.deleted AND
482                         cs.opac_visible AND
483                         cl.opac_visible AND
484                         cp.opac_visible AND
485                         a.opac_visible AND
486                         cp.id NOT IN (SELECT id FROM asset.opac_visible_copies);
487
488 END;
489
490 $$ LANGUAGE plpgsql;