]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI.pm
1719823ca74112c137c629a71615dc505aded770
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Storage / CDBI.pm
1 package OpenILS::Application::Storage::CDBI;
2 use base qw/Class::DBI/;
3 use Class::DBI;
4 use Class::DBI::AbstractSearch;
5
6 use OpenILS::Application::Storage::CDBI::actor;
7 use OpenILS::Application::Storage::CDBI::action;
8 use OpenILS::Application::Storage::CDBI::asset;
9 use OpenILS::Application::Storage::CDBI::authority;
10 use OpenILS::Application::Storage::CDBI::biblio;
11 use OpenILS::Application::Storage::CDBI::config;
12 use OpenILS::Application::Storage::CDBI::metabib;
13 use OpenILS::Application::Storage::CDBI::money;
14 use OpenILS::Application::Storage::CDBI::permission;
15 use OpenILS::Application::Storage::CDBI::container;
16
17 use JSON;
18 use OpenSRF::Utils::Logger qw(:level);
19 use OpenSRF::EX qw/:try/;
20
21 our $VERSION = 1;
22 my $log = 'OpenSRF::Utils::Logger';
23
24 sub child_init {
25         my $self = shift;
26
27         $log->debug("Creating ImaDBI Querys", DEBUG);
28         __PACKAGE__->set_sql( 'OILSFastSearch', <<"     SQL", 'Main');
29                 SELECT  %s
30                   FROM  %s
31                   WHERE %s = ?
32         SQL
33
34         __PACKAGE__->set_sql( 'OILSFastOrderedSearchLike', <<"  SQL", 'Main');
35                 SELECT  %s
36                   FROM  %s
37                   WHERE %s LIKE ?
38                   ORDER BY %s
39         SQL
40
41         __PACKAGE__->set_sql( 'OILSFastOrderedSearch', <<"      SQL", 'Main');
42                 SELECT  %s
43                   FROM  %s
44                   WHERE %s = ?
45                   ORDER BY %s
46         SQL
47
48         $log->debug("Calling Driver child_init", DEBUG);
49         $self->SUPER::child_init(@_);
50
51 }
52
53 sub fast_flesh_sth {
54         my $class = shift;
55         $class = ref($class) || $class;
56
57         my $field = shift;
58         my $value = shift;
59         my $order = shift;
60         my $like = shift;
61
62
63         if (!(defined($order) and ref($order) and ref($order) eq 'HASH')) {
64                 if (defined($value) and ref($value) and ref($value) eq 'HASH') {
65                         $order = $value;
66                         $value = undef;
67                 } else {
68                         $order = { order_by => $class->columns('Primary') }
69                 }
70         }
71
72         unless (defined $value) {
73                 $value = $field;
74                 ($field) = $class->columns('Primary');
75         }
76
77         unless (defined $field) {
78                 ($field) = $class->columns('Primary');
79         }
80
81         unless ($order->{order_by}) {
82                 $order = { order_by => $class->columns('Primary') }
83         }
84
85         my $fm_class = 'Fieldmapper::'.$class;
86         my $field_list = join ',', $class->columns('All');
87         
88         my $sth;
89         if (!$like) {
90                 $sth = $class->sql_OILSFastOrderedSearch( $field_list, $class->table, $field, $order->{order_by});
91         } else {
92                 $sth = $class->sql_OILSFastOrderedSearchLike( $field_list, $class->table, $field, $order->{order_by});
93         }
94         $sth->execute($value);
95         return $sth;
96 }
97
98 sub fast_flesh {
99         my $self = shift;
100         return map $class->construct($_), $self->fast_flesh_sth(@_)->fetchall_hash;
101 }
102
103 sub fast_fieldmapper {
104         my $self = shift;
105         my $id = shift;
106         my $col = shift;
107         my $like = shift;
108         my $options = shift;
109         my $class = ref($self) || $self;
110         my $fm_class = 'Fieldmapper::'.$class;
111         my @fms;
112         $log->debug("fast_fieldmapper() ==> Retrieving $fm_class", INTERNAL);
113         if ($like < 2) {
114                 for my $hash ($self->fast_flesh_sth( $col, "$id", { order_by => $col }, $like )->fetchall_hash) {
115                         my $fm = $fm_class->new;
116                         for my $field ( $fm_class->real_fields ) {
117                                 $fm->$field( $$hash{$field} );
118                         }
119                         push @fms, $fm;
120                 }
121         } else {
122                 my $search_type = 'search';
123                 if ($like == 2) {
124                         $search_type = 'search_fts'
125                 } elsif ($like == 3) {
126                         $search_type = 'search_regex'
127                 }
128
129                 for my $obj ($class->$search_type({ $col => $id}, $options)) {
130                         push @fms, $obj->to_fieldmapper;
131                 }
132         }
133         return @fms;
134 }
135
136 sub retrieve {
137         my $self = shift;
138         my $arg = shift;
139         if (ref($arg) &&
140                 (UNIVERSAL::isa($arg => 'Fieldmapper') ||
141                  UNIVERSAL::isa($arg => 'Class::DBI')) ) {
142                 my ($col) = $self->primary_column;
143                 $log->debug("Using field $col as the primary key", INTERNAL);
144                 $arg = $arg->$col;
145         } elsif (ref $arg) {
146                 my ($col) = $self->primary_column;
147                 $log->debug("Using field $col as the primary key", INTERNAL);
148                 $arg = $arg->{$col};
149         }
150                 
151         $log->debug("Retrieving $self with $arg", INTERNAL);
152         my $rec;
153         try {
154                 $rec = $self->SUPER::retrieve("$arg");
155         } catch Error with {
156                 $log->debug("Could not retrieve $self with $arg! -- ".shift(), DEBUG);
157                 return undef;
158         };
159         return $rec;
160 }
161
162 sub to_fieldmapper {
163         my $obj = shift;
164         my $class = ref($obj) || $obj;
165
166         my $fm_class = 'Fieldmapper::'.$class;
167         my $fm = $fm_class->new;
168
169         if (ref($obj)) {
170                 for my $field ( $fm->real_fields ) {
171                         $fm->$field( $obj->$field );
172                 }
173         }
174
175         return $fm;
176 }
177
178 sub merge {
179         my $self = shift;
180         my $search = shift;
181         my $arg = shift;
182
183         delete $$arg{$_} for (keys %$search);
184
185         $log->debug("CDBI->merge: \$search is $search (".ref($search)." : ".join(',',map{"$_ => $$search{$_}"}keys(%$search)).")",DEBUG);
186         $log->debug("CDBI->merge: \$arg is $arg (".ref($arg)." : ".join(',',map{"$_ => $$arg{$_}"}keys(%$arg)).")",DEBUG);
187
188         my @objs = ($self);
189         @objs = $self->search_where($search) unless (ref $self);
190
191         if (@objs == 1) {
192                 $objs[0]->update($arg);
193                 return $objs[0];
194         } elsif (@objs == 0) {
195                 return $self->create({%$search,%$arg});
196         } else {
197                 throw OpenSRF::EX::WARN ("Non-unique search key for merge.  Perhaps you meant to use remote_update?");
198         }
199 }
200
201 sub remote_update {
202         my $self = shift;
203         my $search = shift;
204         my $arg = shift;
205
206         delete $$arg{$_} for (keys %$search);
207
208         $log->debug("CDBI->remote_update: \$search is $search (".ref($search)." : ".join(',',map{"$_ => $$search{$_}"}keys(%$search)).")",DEBUG);
209         $log->debug("CDBI->remote_update: \$arg is $arg (".ref($arg)." : ".join(',',map{"$_ => $$arg{$_}"}keys(%$arg)).")",DEBUG);
210
211 #       my @objs = $self->search_where($search);
212 #       throw OpenSRF::EX::WARN ("No objects found for remote_update.  Perhaps you meant to use merge?")
213 #               if (@objs == 0);
214
215 #       $_->update($arg) for (@objs);
216 #       return scalar(@objs);
217
218         my @finds = sort keys %$search;
219         my @sets = sort keys %$arg;
220
221         my @find_vals = @$search{@finds};
222         my @set_vals = @$arg{@sets};
223
224         my $sql = 'UPDATE %s SET %s WHERE %s';
225
226         my $table = $self->table;
227         my $set = join(', ', map { "$_=?" } @sets);
228         my $where = join(', ', map { "$_=?" } @finds);
229
230         my $sth = $self->db_Main->prepare(sprintf($sql, $table, $set, $where));
231         $sth->execute(@set_vals,@find_vals);
232         return $sth->rows;
233
234 }
235
236 sub create {
237         my $self = shift;
238         my $arg = shift;
239
240         $log->debug("CDBI->create: \$arg is $arg (".ref($arg)." : ".JSON->perl2JSON($arg).")",DEBUG);
241
242         if (ref($arg) && UNIVERSAL::isa($arg => 'Fieldmapper')) {
243                 return $self->create_from_fieldmapper($arg,@_);
244         }
245
246         return $self->SUPER::create($arg,@_);
247 }
248
249 sub create_from_fieldmapper {
250         my $obj = shift;
251         my $fm = shift;
252         my @params = @_;
253
254         $log->debug("Creating node of type ".ref($fm), DEBUG);
255
256         my $class = ref($obj) || $obj;
257         my ($primary) = $class->columns('Primary');
258
259         if (ref($fm) &&UNIVERSAL::isa($fm => 'Fieldmapper')) {
260                 my %hash = map { defined $fm->$_ ?
261                                         ($_ => $fm->$_) :
262                                         ()
263                                 } grep { $_ ne $primary } $class->columns('All');
264
265                 if ($class->find_column( 'last_xact_id' )) {
266                         my $xact_id = $class->current_xact_id;
267                         throw Error unless ($xact_id);
268                         $hash{last_xact_id} = $xact_id;
269                 }
270
271                 return $class->create( \%hash, @params );
272         } else {
273                 return undef;
274         }
275 }
276
277 sub delete {
278         my $self = shift;
279         my $arg = shift;
280         my $orig = $self;
281
282         my $class = ref($self) || $self;
283
284         $self = $self->retrieve($arg) if (!ref($self));
285         unless (defined $self) {
286                 $log->debug("ARG! Couldn't retrieve record ".$arg->id, DEBUG);
287                 throw OpenSRF::EX::WARN ("ARG! Couldn't retrieve record ");
288         }
289
290         if ($class->find_column( 'last_xact_id' )) {
291                 my $xact_id = $self->current_xact_id;
292                 
293                 throw Error ("Deleting from $class requires a transaction be established")
294                         unless ($xact_id);
295                 
296                 throw Error ("The row you are attempting to delete has been changed since you read it")
297                         unless ( $orig->last_xact_id eq $self->last_xact_id);
298
299                 $self->last_xact_id( $class->current_xact_id );
300                 $self->SUPER::update;
301         }
302
303         $self->SUPER::delete;
304
305         return 1;
306 }
307
308 sub debug_object {
309         my $obj = shift;
310         my $string = '';
311
312         $string .= "Object type:\t".ref($obj)."\n";
313         $string .= "Object string:\t$obj\n";
314
315         if (ref($obj) && UNIVERSAL::isa($obj => 'Fieldmapper')) {
316                 $string .= "Object fields:\n";
317                 for my $col ($obj->real_fields()) {
318                         $string .= "\t$col\t=> ".$obj->$col."\n";
319                 }
320         } elsif (ref($obj) && UNIVERSAL::isa($obj => 'Class::DBI')) {
321                 $string .= "Object cols:\n";
322                 for my $col ($obj->columns('All')) {
323                         $string .= "\t$col\t=> ".$obj->$col."\n";
324                 }
325         } elsif (ref($obj) && UNIVERSAL::isa($obj => 'HASH')) {
326                 $string .= "Object keys and vals:\n";
327                 for my $col (keys %$obj) {
328                         $string .= "\t$col\t=> $$obj{$col}\n";
329                 }
330         }
331
332         $string .= "\n";
333         
334         $log->debug($string,DEBUG);
335 }
336
337
338 sub update {
339         my $self = shift;
340         my $arg = shift;
341
342         $log->debug("Attempting to update using $arg", DEBUG) if ($arg);
343
344         if (ref($arg)) {
345                 $self = $self->modify_from_fieldmapper($arg);
346                 $log->debug("Modification of $self seems to have failed....", DEBUG);
347                 return undef unless (defined $self);
348         }
349
350         $log->debug("Calling Class::DBI->update on modified object $self", DEBUG);
351
352         debug_object($self);
353
354         return $self->SUPER::update if ($self->is_changed);
355         return 0;
356 }
357
358 sub modify_from_fieldmapper {
359         my $obj = shift;
360         my $fm = shift;
361         my $orig = $obj;
362
363         debug_object($obj);
364         debug_object($fm);
365
366         $log->debug("Modifying object using fieldmapper", DEBUG);
367
368         my $class = ref($obj) || $obj;
369         my ($primary) = $class->columns('Primary');
370
371
372         if (!ref($obj)) {
373                 $obj = $class->retrieve($fm);
374                 debug_object($obj);
375                 unless ($obj) {
376                         $log->debug("Retrieve of $class using $fm (".$fm->id.") failed! -- ".shift(), ERROR);
377                         throw OpenSRF::EX::WARN ("No $class with id of ".$fm->id."!!");
378                 }
379         }
380
381         my %hash;
382         
383         if (ref($fm) and UNIVERSAL::isa($fm => 'Fieldmapper')) {
384                 %hash = map { defined $fm->$_ ?
385                                 ($_ => ''.$fm->$_) :
386                                 ()
387                         } grep { $_ ne $primary } $class->columns('All');
388         } else {
389                 %hash = %{$fm};
390         }
391
392         my $au = $obj->autoupdate;
393         $obj->autoupdate(0);
394         
395         debug_object($obj);
396
397         for my $field ( keys %hash ) {
398                 $obj->$field( $hash{$field} ) if ($obj->$field ne $hash{$field});
399                 $log->debug("Setting field $field on $obj to $hash{$field}",INTERNAL);
400         }
401
402         if ($class->find_column( 'last_xact_id' ) and $obj->is_changed) {
403                 my ($xact_id) = OpenILS::Application::Storage->method_lookup('open-ils.storage.transaction.current')->run();
404                 throw Error ("Updating $class requires a transaction be established")
405                         unless ($xact_id);
406                 throw Error ("The row you are attempting to delete has been changed since you read it")
407                         unless ( $fm->last_xact_id eq $obj->last_xact_id);
408                 $obj->last_xact_id( $xact_id );
409         } else {
410                 $obj->autoupdate($au)
411         }
412
413         return $obj;
414 }
415
416
417
418         #-------------------------------------------------------------------------------
419         actor::user->has_a( home_ou => 'actor::org_unit' );
420         actor::user->has_a( card => 'actor::card' );
421         actor::user->has_a( standing => 'config::standing' );
422         actor::user->has_a( profile => 'actor::profile' );
423         actor::user->has_a( mailing_address => 'actor::user_address' );
424         actor::user->has_a( billing_address => 'actor::user_address' );
425         actor::user->has_a( ident_type => 'config::identification_type' );
426         actor::user->has_a( ident_type2 => 'config::identification_type' );
427         actor::user->has_a( net_access_level => 'config::net_access_level' );
428
429         actor::user_address->has_a( usr => 'actor::user' );
430         
431         actor::card->has_a( usr => 'actor::user' );
432         
433         actor::org_unit->has_a( parent_ou => 'actor::org_unit' );
434         actor::org_unit->has_a( ou_type => 'actor::org_unit_type' );
435         #actor::org_unit->has_a( address => 'actor::org_address' );
436
437         actor::stat_cat_entry->has_a( stat_cat => 'actor::stat_cat' );
438         actor::stat_cat->has_many( entries => 'actor::stat_cat_entry' );
439         actor::stat_cat_entry_user_map->has_a( stat_cat => 'actor::stat_cat' );
440         actor::stat_cat_entry_user_map->has_a( stat_cat_entry => 'actor::stat_cat_entry' );
441         actor::stat_cat_entry_user_map->has_a( target_usr => 'actor::user' );
442
443         asset::stat_cat_entry->has_a( stat_cat => 'asset::stat_cat' );
444         asset::stat_cat->has_many( entries => 'asset::stat_cat_entry' );
445         asset::stat_cat_entry_copy_map->has_a( stat_cat => 'asset::stat_cat' );
446         asset::stat_cat_entry_copy_map->has_a( stat_cat_entry => 'asset::stat_cat_entry' );
447         asset::stat_cat_entry_copy_map->has_a( owning_copy => 'asset::copy' );
448
449         action::survey_response->has_a( usr => 'actor::user' );
450         action::survey_response->has_a( survey => 'action::survey' );
451         action::survey_response->has_a( question => 'action::survey_question' );
452         action::survey_response->has_a( answer => 'action::survey_answer' );
453
454         action::survey_question->has_a( survey => 'action::survey' );
455
456         action::survey_answer->has_a( question => 'action::survey' );
457
458         asset::copy_note->has_a( owning_copy => 'asset::copy' );
459
460         actor::user->has_many( stat_cat_entries => [ 'actor::stat_cat_entry_user_map' => 'stat_cat_entry' ] );
461         actor::user->has_many( stat_cat_entry_user_maps => 'actor::stat_cat_entry_user_map' );
462
463         asset::copy->has_many( stat_cat_entries => [ 'asset::stat_cat_entry_copy_map' => 'stat_cat_entry' ] );
464         asset::copy->has_many( stat_cat_entry_copy_maps => 'asset::stat_cat_entry_copy_map' );
465
466         asset::copy->has_a( call_number => 'asset::call_number' );
467         asset::copy->has_a( creator => 'actor::user' );
468         asset::copy->has_a( editor => 'actor::user' );
469         asset::copy->has_a( status => 'config::copy_status' );
470         asset::copy->has_a( location => 'asset::copy_location' );
471         asset::copy->has_a( circ_lib => 'actor::org_unit' );
472
473         asset::call_number_note->has_a( call_number => 'asset::call_number' );
474
475         asset::call_number->has_a( record => 'biblio::record_entry' );
476         asset::call_number->has_a( creator => 'actor::user' );
477         asset::call_number->has_a( editor => 'actor::user' );
478
479         authority::record_note->has_a( record => 'authority::record_entry' );
480         biblio::record_note->has_a( record => 'biblio::record_entry' );
481         
482         authority::record_entry->has_a( creator => 'actor::user' );
483         authority::record_entry->has_a( editor => 'actor::user' );
484         biblio::record_entry->has_a( creator => 'actor::user' );
485         biblio::record_entry->has_a( editor => 'actor::user' );
486         
487         metabib::metarecord->has_a( master_record => 'biblio::record_entry' );
488         
489         authority::record_descriptor->has_a( record => 'authority::record_entry' );
490         metabib::record_descriptor->has_a( record => 'biblio::record_entry' );
491         
492         authority::full_rec->has_a( record => 'authority::record_entry' );
493         metabib::full_rec->has_a( record => 'biblio::record_entry' );
494         
495         metabib::title_field_entry->has_a( source => 'biblio::record_entry' );
496         metabib::title_field_entry->has_a( field => 'config::metabib_field' );
497         
498         metabib::author_field_entry->has_a( source => 'biblio::record_entry' );
499         metabib::author_field_entry->has_a( field => 'config::metabib_field' );
500         
501         metabib::subject_field_entry->has_a( source => 'biblio::record_entry' );
502         metabib::subject_field_entry->has_a( field => 'config::metabib_field' );
503         
504         metabib::keyword_field_entry->has_a( source => 'biblio::record_entry' );
505         metabib::keyword_field_entry->has_a( field => 'config::metabib_field' );
506         
507         metabib::series_field_entry->has_a( source => 'biblio::record_entry' );
508         metabib::series_field_entry->has_a( field => 'config::metabib_field' );
509         
510         metabib::metarecord_source_map->has_a( metarecord => 'metabib::metarecord' );
511         metabib::metarecord_source_map->has_a( source => 'biblio::record_entry' );
512
513         action::circulation->has_a( usr => 'actor::user' );
514         action::circulation->has_a( target_copy => 'asset::copy' );
515         action::circulation->has_a( circ_lib => 'actor::org_unit' );
516
517         money::billable_transaction->has_a( usr => 'actor::user' );
518         
519         
520         #-------------------------------------------------------------------------------
521         actor::user->has_many( survey_responses => 'action::survey_response' );
522         actor::user->has_many( addresses => 'actor::user_address' );
523         actor::user->has_many( cards => 'actor::card' );
524
525         actor::org_unit->has_many( users => 'actor::user' );
526         actor::profile->has_many( users => 'actor::user' );
527
528         action::survey->has_many( questions => 'action::survey_question' );
529         action::survey->has_many( responses => 'action::survey_response' );
530         
531         action::survey_question->has_many( answers => 'action::survey_answer' );
532         action::survey_question->has_many( responses => 'action::survey_response' );
533
534         action::survey_answer->has_many( responses => 'action::survey_response' );
535
536         asset::copy->has_many( notes => 'asset::copy_note' );
537         asset::call_number->has_many( copies => 'asset::copy' );
538         asset::call_number->has_many( notes => 'asset::call_number_note' );
539
540         authority::record_entry->has_many( record_descriptor => 'authority::record_descriptor' );
541         authority::record_entry->has_many( notes => 'authority::record_note' );
542
543         biblio::record_entry->has_many( record_descriptor => 'metabib::record_descriptor' );
544         biblio::record_entry->has_many( notes => 'biblio::record_note' );
545         biblio::record_entry->has_many( call_numbers => 'asset::call_number' );
546         biblio::record_entry->has_many( full_record_entries => 'metabib::full_rec' );
547         biblio::record_entry->has_many( title_field_entries => 'metabib::title_field_entry' );
548         biblio::record_entry->has_many( author_field_entries => 'metabib::author_field_entry' );
549         biblio::record_entry->has_many( subject_field_entries => 'metabib::subject_field_entry' );
550         biblio::record_entry->has_many( keyword_field_entries => 'metabib::keyword_field_entry' );
551         biblio::record_entry->has_many( series_field_entries => 'metabib::series_field_entry' );
552
553         metabib::metarecord->has_many( source_records => [ 'metabib::metarecord_source_map' => 'source'] );
554
555         money::billable_transaction->has_many( billings => 'money::billing' );
556         money::billable_transaction->has_many( payments => 'money::payment' );
557
558         money::grocery->has_many( billings => 'money::billing' );
559         money::grocery->has_many( payments => 'money::payment' );
560
561         money::billing->has_a( xact => 'money::billable_transaction' );
562         money::payment->has_a( xact => 'money::billable_transaction' );
563
564         money::cash_payment->has_a( xact => 'money::billable_transaction' );
565         money::cash_payment->has_a( accepting_usr => 'actor::user' );
566
567         money::check_payment->has_a( xact => 'money::billable_transaction' );
568         money::check_payment->has_a( accepting_usr => 'actor::user' );
569
570         money::credit_card_payment->has_a( xact => 'money::billable_transaction' );
571         money::credit_card_payment->has_a( accepting_usr => 'actor::user' );
572
573         money::forgive_payment->has_a( xact => 'money::billable_transaction' );
574         money::forgive_payment->has_a( accepting_usr => 'actor::user' );
575
576         money::work_payment->has_a( xact => 'money::billable_transaction' );
577         money::work_payment->has_a( accepting_usr => 'actor::user' );
578
579         money::credit_payment->has_a( xact => 'money::billable_transaction' );
580         money::credit_payment->has_a( accepting_usr => 'actor::user' );
581
582         permission::grp_tree->has_a( parent => 'permission::grp_tree' );
583
584         permission::grp_perm_map->has_a( grp => 'permission::grp_tree' );
585         permission::grp_perm_map->has_a(  perm => 'permission::perm_list' );
586         permission::grp_perm_map->has_a(  depth => 'actor::org_unit_type' );
587         
588         permission::usr_perm_map->has_a( usr => 'actor::user' );
589         permission::usr_perm_map->has_a(  perm => 'permission::perm_list' );
590         permission::usr_perm_map->has_a(  depth => 'actor::org_unit_type' );
591         
592         permission::usr_grp_map->has_a(  usr => 'actor::user' );
593         permission::usr_grp_map->has_a(  grp => 'permission::grp_tree' );
594
595         action::hold_notification->has_a(  hold => 'action::hold_request' );
596         
597         action::hold_copy_map->has_a(  hold => 'action::hold_request' );
598         action::hold_copy_map->has_a(  target_copy => 'asset::copy' );
599
600         action::hold_request->has_a(  current_copy => 'asset::copy' );
601         action::hold_request->has_a(  requestor => 'actor::user' );
602         action::hold_request->has_a(  usr => 'actor::user' );
603         action::hold_request->has_a(  pickup_lib => 'actor::org_unit' );
604
605         action::hold_request->has_many(  notifications => 'action::hold_notification' );
606         action::hold_request->has_many(  copy_maps => 'action::hold_copy_map' );
607
608         asset::copy->has_many(  hold_maps => 'action::hold_copy_map' );
609
610 1;