]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Cat.pm
adding new record methods
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Cat.pm
1 use strict; use warnings;
2 package OpenILS::Application::Cat;
3 use OpenILS::Application::AppUtils;
4 use OpenSRF::Application;
5 use OpenILS::Application::Cat::Utils;
6 use base qw/OpenSRF::Application/;
7 use Time::HiRes qw(time);
8 use OpenSRF::EX qw(:try);
9 use JSON;
10 use OpenILS::Utils::Fieldmapper;
11
12 use XML::LibXML;
13 use Unicode::Normalize;
14 use Data::Dumper;
15 use OpenILS::Utils::FlatXML;
16 use OpenILS::Perm;
17 use OpenSRF::Utils::SettingsClient;
18 use OpenSRF::Utils::Logger qw($logger);
19
20 my $apputils = "OpenILS::Application::AppUtils";
21
22 my $utils = "OpenILS::Application::Cat::Utils";
23
24 my $conf;
25
26 my %marctemplates;
27
28 sub entityize { 
29         my $stuff = shift;
30         my $form = shift;
31
32         if ($form eq 'D') {
33                 $stuff = NFD($stuff);
34         } else {
35                 $stuff = NFC($stuff);
36         }
37
38         $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
39         return $stuff;
40 }
41
42 __PACKAGE__->register_method(
43         method  => "retrieve_marc_template",
44         api_name        => "open-ils.cat.biblio.marc_template.retrieve",
45         notes           => <<"  NOTES");
46         Returns a MARC 'record tree' based on a set of pre-defined templates.
47         Templates include : book
48         NOTES
49
50 sub retrieve_marc_template {
51         my( $self, $client, $type ) = @_;
52
53         return $marctemplates{$type} if defined($marctemplates{$type});
54         $marctemplates{$type} = _load_marc_template($type);
55         return $marctemplates{$type};
56 }
57
58 sub _load_marc_template {
59         my $type = shift;
60
61         if(!$conf) { $conf = OpenSRF::Utils::SettingsClient->new; }
62
63         my $template = $conf->config_value(                                     
64                 "apps", "open-ils.cat","app_settings", "marctemplates", $type );
65         warn "Opening template file $template\n";
66
67         open( F, $template ) or 
68                 throw OpenSRF::EX::ERROR ("Unable to open MARC template file: $template : $@");
69
70         my @xml = <F>;
71         close(F);
72         my $xml = join('', @xml);
73
74         return XML::LibXML->new->parse_string($xml)->documentElement->toString;
75 }
76
77
78
79 __PACKAGE__->register_method(
80         method  => "create_record_xml",
81         api_name        => "open-ils.cat.biblio.record.xml.create",
82         notes           => <<"  NOTES");
83         Inserts a new MARC 'record tree' into the system
84         NOTES
85
86 sub create_record_xml {
87         my( $self, $client, $login, $tree, $source ) = @_;
88         $source ||= 2;
89
90         my $user_obj = $apputils->check_user_session($login);
91
92         if($apputils->check_user_perms(
93                         $user_obj->id, $user_obj->home_ou, "CREATE_MARC")) {
94                 return OpenILS::Perm->new("CREATE_MARC"); 
95         }
96
97         warn "Creating a new record entry...";
98         my $meth = $self->method_lookup("open-ils.cat.biblio.record.xml.import");
99         my ($s) = $meth->run($login, $tree, 2);
100         return $s;
101 }
102
103
104
105
106 __PACKAGE__->register_method(
107         method  => "biblio_record_xml_import",
108         api_name        => "open-ils.cat.biblio.record.xml.import",
109         notes           => <<"  NOTES");
110         Takes a marcxml record and imports the record into the database.  In this
111         case, the marcxml record is assumed to be a complete record (i.e. valid
112         MARC).  The title control number is taken from (whichever comes first)
113         tags 001, 039[ab], 020a, 022a, 010, 035a and whichever does not already exist
114         in the database.
115         user_session must have IMPORT_MARC permissions
116         NOTES
117
118
119 sub biblio_record_xml_import {
120         my( $self, $client, $user_session, $xml, $source) = @_;
121         my $user_obj = $apputils->check_user_session($user_session);
122
123         if($apputils->check_user_perms(
124                         $user_obj->id, $user_obj->home_ou, "IMPORT_MARC")) {
125                 return OpenILS::Perm->new("IMPORT_MARC"); 
126         }
127
128         # copy the doc so that we can mangle the namespace.  
129         my $marcxml = XML::LibXML->new->parse_string( $xml );
130
131         $marcxml->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
132         my ($tcn, $tcn_source);
133
134         #warn "Importing MARC Doc:\n".$marcxml->toString(1)."\n";
135         #warn "Namespace: " . $marcxml->documentElement->firstChild->namespaceURI . "\n";
136         #return 1;
137
138         warn "Starting db session in import\n";
139         my $session = $apputils->start_db_session();
140
141         my $xpath = '//controlfield[@tag="001"]';
142         $tcn = $marcxml->documentElement->findvalue($xpath);
143
144         $xpath = '//controlfield[@tag="003"]';
145         $tcn_source = $marcxml->documentElement->findvalue($xpath);
146
147         if(_tcn_exists($session, $tcn)) {$tcn = undef;}
148
149         my $add_039 = 0;
150         if(!$tcn) {
151                 $xpath = '//datafield[@tag="039"]/subfield[@code="a"]';
152                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
153
154                 $add_039++ if (!$tcn);
155
156                 $xpath = '//datafield[@tag="039"]/subfield[@code="b"]';
157                 $tcn_source = $marcxml->documentElement->findvalue($xpath) || "System Local";
158                 if(_tcn_exists($session, $tcn, $tcn_source)) {$tcn = undef;}
159         }
160
161         if(!$tcn) {
162                 $xpath = '//datafield[@tag="020"]/subfield[@code="a"]';
163                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
164                 $tcn_source = "ISBN";
165                 if(_tcn_exists($session, $tcn, $tcn_source)) {$tcn = undef;}
166         }
167
168         if(!$tcn) { 
169                 $xpath = '//datafield[@tag="022"]/subfield[@code="a"]';
170                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
171                 $tcn_source = "ISSN";
172                 if(_tcn_exists($session, $tcn, $tcn_source)) {$tcn = undef;}
173         }
174
175         if(!$tcn) {
176                 $xpath = '//datafield[@tag="010"]';
177                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
178                 $tcn_source = "LCCN";
179                 if(_tcn_exists($session, $tcn, $tcn_source)) {$tcn = undef;}
180         }
181
182         if(!$tcn) {
183                 $xpath = '//datafield[@tag="035"]/subfield[@code="a"]';
184                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
185                 $tcn_source = "System Legacy";
186                 if(_tcn_exists($session, $tcn, $tcn_source)) {$tcn = undef;}
187
188                 if($tcn) {
189                         $marcxml->documentElement->removeChild(
190                                 $marcxml->documentElement->findnodes( '//datafield[@tag="035"]' )
191                         );
192                 }
193         }
194
195         if ($add_039) {
196                 my $df = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'datafield');
197                 $df->setAttribute( tag => '039' );
198                 $df->setAttribute( ind1 => ' ' );
199                 $df->setAttribute( ind2 => ' ' );
200                 $marcxml->documentElement->appendChild( $df );
201
202                 my $sfa = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'subfield');
203                 $sfa->setAttribute( code => 'a' );
204                 $sfa->appendChild( $marcxml->createTextNode( $tcn ) );
205                 $df->appendChid( $sfa );
206
207                 my $sfb = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'subfield');
208                 $sfb->setAttribute( code => 'b' );
209                 $sfb->appendChild( $marcxml->createTextNode( $tcn_source ) );
210                 $df->appendChid( $sfb );
211         }
212
213         warn "Record import with tcn: $tcn and source $tcn_source\n";
214
215         my $record = Fieldmapper::biblio::record_entry->new;
216
217         $record->source($source) if ($source);
218         $record->tcn_source($tcn_source);
219         $record->tcn_value($tcn);
220         $record->creator($user_obj->id);
221         $record->editor($user_obj->id);
222         $record->marc( entityize( $marcxml->documentElement->toString ) );
223
224
225         my $req = $session->request(
226                 "open-ils.storage.direct.biblio.record_entry.create", $record );
227
228         my $id = $req->gather(1);
229         $record->id( $id );
230
231         if(!$id) { throw OpenSRF::EX::ERROR ("Unable to create new record_entry from import"); }
232         warn "received id: $id from record_entry create\n";
233
234         $apputils->commit_db_session($session);
235
236         $session = OpenSRF::AppSession->create("open-ils.storage");
237
238         my $wreq = $session->request("open-ils.worm.wormize.biblio", $id)->gather(1);
239         warn "Done worming record $id\n";
240
241         if(!$wreq) { throw OpenSRF::EX::ERROR ("Unable to wormize imported record"); }
242
243         return $record;
244
245 }
246
247 sub _tcn_exists {
248         my $session = shift;
249         my $tcn = shift;
250         my $source = shift;
251
252         if(!$tcn) {return 0;}
253
254         my $req = $session->request(      
255                 "open-ils.storage.id_list.biblio.record_entry.search_where.atomic",
256                 { tcn_value => $tcn, tcn_source => $source, deleted => 'f' } );
257
258         my $recs = $req->gather(1);
259
260         if($recs and $recs->[0]) {
261                 $logger->debug("_tcn_exists is true for tcn : $tcn ($source)");
262                 return $recs->[0];
263         }
264
265         $logger->debug("_tcn_exists is false for tcn : $tcn ($source)");
266         return 0;
267 }
268
269
270
271 __PACKAGE__->register_method(
272         method  => "biblio_record_tree_retrieve",
273         api_name        => "open-ils.cat.biblio.record.tree.retrieve",
274 );
275
276 sub biblio_record_tree_retrieve {
277
278         my( $self, $client, $recordid ) = @_;
279
280         my $name = "open-ils.storage.direct.biblio.record_entry.retrieve";
281         my $session = OpenSRF::AppSession->create( "open-ils.storage" );
282         my $request = $session->request( $name, $recordid );
283         my $marcxml = $request->gather(1);
284
285         if(!$marcxml) {
286                 throw OpenSRF::EX::ERROR 
287                         ("No record in database with id $recordid");
288         }
289
290         $session->disconnect();
291         $session->kill_me();
292
293         warn "turning into nodeset\n";
294         my $nodes = OpenILS::Utils::FlatXML->new()->xml_to_nodeset( $marcxml->marc ); 
295         warn "turning nodeset into tree\n";
296         my $tree = $utils->nodeset2tree( $nodes->nodeset );
297
298         $tree->owner_doc( $marcxml->id() );
299
300         warn "returning tree\n";
301
302         return $tree;
303 }
304
305 __PACKAGE__->register_method(
306         method  => "biblio_record_xml_update",
307         api_name        => "open-ils.cat.biblio.record.xml.update",
308         argc            => 3, #(session_id, biblio_tree ) 
309         notes           => <<'  NOTES');
310         Updates the XML of a biblio record entry
311         @param authtoken The session token for the staff updating the record
312         @param docID The record entry ID to update
313         @param xml The new MARCXML record
314         NOTES
315
316 sub biblio_record_xml_update {
317
318         my( $self, $client, $user_session,  $id, $xml ) = @_;
319
320         throw OpenSRF::EX::InvalidArg 
321                 ("Not enough args to to open-ils.cat.biblio.record.tree.commit")
322                 unless ( $user_session and $tree );
323
324         my $user_obj = $apputils->check_user_session($user_session); 
325
326         if($apputils->check_user_perms(
327                         $user_obj->id, $user_obj->home_ou, "UPDATE_MARC")) {
328                 return OpenILS::Perm->new("UPDATE_MARC"); 
329         }
330
331
332         warn "Starting db session\n";
333         my $session = OpenILS::Application::AppUtils->start_db_session();
334
335         warn "Retrieving biblio record from storage for update\n";
336
337         my $req1 = $session->request(
338                         "open-ils.storage.direct.biblio.record_entry.batch.retrieve", $id );
339         my $biblio = $req1->gather(1);
340
341         warn "retrieved doc $id\n";
342
343         $biblio->marc( entityize( $xml ) );
344         $biblio->editor( $user_obj->id );
345         $biblio->edit_date( 'now' );
346
347
348         warn "Sending updated doc $id to db\n";
349         my $req = $session->request( "open-ils.storage.direct.biblio.record_entry.update", $biblio );
350
351         $req->wait_complete;
352         my $status = $req->recv();
353         if( !$status || $status->isa("Error") || ! $status->content) {
354                 OpenILS::Application::AppUtils->rollback_db_session($session);
355                 if($status->isa("Error")) { throw $status ($status); }
356                 throw OpenSRF::EX::ERROR ("Error updating biblio record");
357         }
358         $req->finish();
359
360         # Send the doc to the wormer for wormizing
361         warn "Starting worm session\n";
362
363         my $success = 0;
364         my $wresp;
365
366         my $wreq = $session->request( "open-ils.worm.wormize.biblio", $docid );
367
368         my $w = 0;
369         try {
370                 $w = $wreq->gather(1);
371
372         } catch Error with {
373                 my $e = shift;
374                 warn "wormizing failed, rolling back\n";
375                 OpenILS::Application::AppUtils->rollback_db_session($session);
376
377                 if($e) { throw $e ($e); }
378                 throw OpenSRF::EX::ERROR ("Wormizing Failed for $docid" );
379         };
380
381         warn "Committing db session...\n";
382         OpenILS::Application::AppUtils->commit_db_session( $session );
383
384 #       $client->respond_complete($tree);
385
386         warn "Done wormizing\n";
387
388         #use Data::Dumper;
389         #warn "Returning tree:\n";
390         #warn Dumper $tree;
391
392         return $biblio;
393
394 }
395
396
397
398 __PACKAGE__->register_method(
399         method  => "biblio_record_record_metadata",
400         api_name        => "open-ils.cat.biblio.record.metadata.retrieve",
401         argc            => 1, #(session_id, biblio_tree ) 
402         notes           => "Walks the tree and commits any changed nodes " .
403                                         "adds any new nodes, and deletes any deleted nodes",
404 );
405
406 sub biblio_record_record_metadata {
407         my( $self, $client, @ids ) = @_;
408
409         if(!@ids){return undef;}
410
411         my $session = OpenSRF::AppSession->create("open-ils.storage");
412         my $request = $session->request( 
413                         "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
414
415         my $results = [];
416
417         while( my $response = $request->recv() ) {
418
419                 if(!$response) {
420                         throw OpenSRF::EX::ERROR ("No Response from Storage");
421                 }
422                 if($response->isa("Error")) {
423                         throw $response ($response->stringify);
424                 }
425
426                 my $record_entry = $response->content;
427
428                 my $creator = $record_entry->creator;
429                 my $editor      = $record_entry->editor;
430
431                 ($creator, $editor) = _get_userid_by_id($creator, $editor);
432
433                 $record_entry->creator($creator);
434                 $record_entry->editor($editor);
435
436                 push @$results, $record_entry;
437
438         }
439
440         $request->finish;
441         $session->disconnect();
442         $session->finish();
443
444         return $results;
445
446 }
447
448 __PACKAGE__->register_method(
449         method  => "biblio_record_marc_cn",
450         api_name        => "open-ils.cat.biblio.record.marc_cn.retrieve",
451         argc            => 1, #(bib id ) 
452 );
453
454 sub biblio_record_marc_cn {
455         my( $self, $client, $id ) = @_;
456
457         my $session = OpenSRF::AppSession->create("open-ils.storage");
458         my $marc = $session
459                 ->request("open-ils.storage.direct.biblio.record_entry.retrieve", $id )
460                 ->gather(1)
461                 ->marc;
462
463         my $doc = XML::LibXML->new->parse_string($marc);
464         $doc->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
465         
466         my @res;
467         for my $tag ( qw/050 055 060 070 080 082 086 088 090 092 096 098 099/ ) {
468                 my @node = $doc->findnodes("//marc:datafield[\@tag='$tag']");
469                 for my $x (@node) {
470                         my $cn = $x->findvalue("marc:subfield[\@code='a' or \@code='b']");
471                         push @res, {$tag => $cn} if ($cn);
472                 }
473         }
474
475         return \@res
476 }
477
478 # gets the username
479 sub _get_userid_by_id {
480
481         my @ids = @_;
482         my @users;
483
484         my $session = OpenSRF::AppSession->create( "open-ils.storage" );
485         my $request = $session->request( 
486                 "open-ils.storage.direct.actor.user.batch.retrieve.atomic", @ids );
487
488         $request->wait_complete;
489         my $response = $request->recv();
490         if(!$request->complete) { return undef; }
491
492         if($response->isa("Error")){
493                 throw $response ($response);
494         }
495
496         for my $u (@{$response->content}) {
497                 next unless ref($u);
498                 push @users, $u->usrname;
499         }
500
501         $request->finish;
502         $session->disconnect;
503         $session->kill_me();
504
505         return @users;
506 }
507
508 sub _get_id_by_userid {
509
510         my @users = @_;
511         my @ids;
512
513         my $session = OpenSRF::AppSession->create( "open-ils.storage" );
514         my $request = $session->request( 
515                 "open-ils.storage.direct.actor.user.search.usrname.atomic", @users );
516
517         $request->wait_complete;
518         my $response = $request->recv();
519         if(!$request->complete) { 
520                 throw OpenSRF::EX::ERROR ("no response from storage on user retrieve");
521         }
522
523         if(UNIVERSAL::isa( $response, "Error")){
524                 throw $response ($response);
525         }
526
527         for my $u (@{$response->content}) {
528                 next unless ref($u);
529                 push @ids, $u->id();
530         }
531
532         $request->finish;
533         $session->disconnect;
534         $session->kill_me();
535
536         return @ids;
537 }
538
539
540 # commits metadata objects to the db
541 sub _update_record_metadata {
542
543         my ($session, @docs ) = @_;
544
545         for my $doc (@docs) {
546
547                 my $user_obj = $doc->{user};
548                 my $docid = $doc->{docid};
549
550                 warn "Updating metata for doc $docid\n";
551
552                 my $request = $session->request( 
553                         "open-ils.storage.direct.biblio.record_entry.retrieve", $docid );
554                 my $record = $request->gather(1);
555
556                 warn "retrieved record\n";
557                 my ($id) = _get_id_by_userid($user_obj->usrname);
558
559                 warn "got $id from _get_id_by_userid\n";
560                 $record->editor($id);
561                 
562                 warn "Grabbed the record, updating and moving on\n";
563
564                 $request = $session->request( 
565                         "open-ils.storage.direct.biblio.record_entry.update", $record );
566                 $request->gather(1);
567         }
568
569         warn "committing metarecord update\n";
570
571         return 1;
572 }
573
574
575
576 __PACKAGE__->register_method(
577         method  => "orgs_for_title",
578         api_name        => "open-ils.cat.actor.org_unit.retrieve_by_title"
579 );
580
581 sub orgs_for_title {
582         my( $self, $client, $record_id ) = @_;
583
584         my $vols = $apputils->simple_scalar_request(
585                 "open-ils.storage",
586                 "open-ils.storage.direct.asset.call_number.search_where.atomic",
587                 { record => $record_id, deleted => 'f' });
588                 #"open-ils.storage.direct.asset.call_number.search.record.atomic",
589
590         my $orgs = { map {$_->owning_lib => 1 } @$vols };
591         return [ keys %$orgs ];
592 }
593
594
595 __PACKAGE__->register_method(
596         method  => "retrieve_copies",
597         api_name        => "open-ils.cat.asset.copy_tree.retrieve");
598
599 __PACKAGE__->register_method(
600         method  => "retrieve_copies",
601         api_name        => "open-ils.cat.asset.copy_tree.global.retrieve");
602
603 # user_session may be null/undef
604 sub retrieve_copies {
605
606         my( $self, $client, $user_session, $docid, @org_ids ) = @_;
607
608         if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
609
610         $docid = "$docid";
611
612         warn " $$ retrieving copy tree for orgs @org_ids and doc $docid at " . time() . "\n";
613
614         # grabbing copy trees should be available for everyone..
615         if(!@org_ids and $user_session) {
616                 my $user_obj = 
617                         OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
618                         @org_ids = ($user_obj->home_ou);
619         }
620
621         if( $self->api_name =~ /global/ ) {
622                 warn "performing global copy_tree search for $docid\n";
623                 return _build_volume_list( { record => $docid } );
624
625         } else {
626
627                 my @all_vols;
628                 for my $orgid (@org_ids) {
629                         my $vols = _build_volume_list( 
630                                         { record => $docid, owning_lib => $orgid } );
631                         warn "Volumes built for org $orgid\n";
632                         push( @all_vols, @$vols );
633                 }
634                 
635                 warn " $$ Finished copy_tree at " . time() . "\n";
636                 return \@all_vols;
637         }
638
639         return undef;
640 }
641
642
643 sub _build_volume_list {
644         my $search_hash = shift;
645
646         $search_hash->{deleted} = 'f';
647
648         my      $session = OpenSRF::AppSession->create( "open-ils.storage" );
649         
650
651         my $request = $session->request( 
652                         "open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
653                         #"open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
654
655         my $vols = $request->gather(1);
656         my @volumes;
657
658         for my $volume (@$vols) {
659
660                 warn "Grabbing copies for volume: " . $volume->id . "\n";
661                 my $creq = $session->request(
662                         "open-ils.storage.direct.asset.copy.search_where.atomic", 
663                         { call_number => $volume->id , deleted => 'f' });
664                         #"open-ils.storage.direct.asset.copy.search.call_number.atomic", $volume->id );
665
666                 my $copies = $creq->gather(1);
667
668                 $copies = [ sort { $a->barcode cmp $b->barcode } @$copies  ];
669
670                 $volume->copies($copies);
671
672                 push( @volumes, $volume );
673         }
674
675
676         $session->disconnect();
677         return \@volumes;
678
679 }
680
681
682 # -----------------------------------------------------------------
683 # Fleshed volume tree batch add/update.  This does everything a 
684 # volume tree could want, add, update, delete
685 # -----------------------------------------------------------------
686 __PACKAGE__->register_method(
687         method  => "volume_tree_fleshed_update",
688         api_name        => "open-ils.cat.asset.volume_tree.fleshed.batch.update",
689 );
690 sub volume_tree_fleshed_update {
691
692         my( $self, $client, $user_session, $volumes ) = @_;
693         return undef unless $volumes;
694
695         my $user_obj = $apputils->check_user_session($user_session);
696
697
698         my $session = $apputils->start_db_session();
699         warn "Looping on volumes in fleshed volume tree update\n";
700
701         # cycle through the volumes provided and update/create/delete where necessary
702         for my $volume (@$volumes) {
703
704                 warn "updating volume " . $volume->id . "\n";
705
706                 my $update_copy_list = $volume->copies;
707
708
709                 if( $volume->isdeleted) {
710                         my $status = _delete_volume($session, $volume, $user_obj);
711                         #if(!$status) {
712                                 #throw OpenSRF::EX::ERROR
713                                         #("Volume delete failed for volume " . $volume->id);
714                         #}
715                         if(UNIVERSAL::isa($status, "Fieldmapper::perm_ex")) { return $status; }
716
717                 } elsif( $volume->isnew ) {
718
719                         $volume->clear_id;
720                         $volume->editor($user_obj->id);
721                         $volume->creator($user_obj->id);
722                         $volume = _add_volume($session, $volume, $user_obj);
723                         use Data::Dumper;
724                         warn Dumper $volume;
725                         if($volume and UNIVERSAL::isa($volume, "Fieldmapper::perm_ex")) { return $volume; }
726
727                 } elsif( $volume->ischanged ) {
728
729                         $volume->editor($user_obj->id);
730                         my $stat = _update_volume($session, $volume, $user_obj);
731                         if($stat and UNIVERSAL::isa($stat, "Fieldmapper::perm_ex")) { return $stat; }
732                 }
733
734
735                 if( ! $volume->isdeleted ) {
736                         for my $copy (@{$update_copy_list}) {
737         
738                                 $copy->editor($user_obj->id);
739                                 warn "updating copy for volume " . $volume->id . "\n";
740         
741                                 if( $copy->isnew ) {
742         
743                                         $copy->clear_id;
744                                         $copy->call_number($volume->id);
745                                         $copy->creator($user_obj->id);
746                                         $copy = _fleshed_copy_update($session,$copy,$user_obj);
747         
748                                 } elsif( $copy->ischanged ) {
749                                         $copy->call_number($volume->id);
750                                         $copy = _fleshed_copy_update($session, $copy, $user_obj);
751         
752                                 } elsif( $copy->isdeleted ) {
753                                         warn "Deleting copy " . $copy->id . " for volume " . $volume->id . "\n";
754                                         my $status = _fleshed_copy_update($session, $copy, $user_obj);
755                                         warn "Copy delete returned a status of $status\n";
756                                 }
757                         }
758                 }
759         }
760
761         $apputils->commit_db_session($session);
762         return scalar(@$volumes);
763 }
764
765
766 sub _delete_volume {
767         my( $session, $volume, $user_obj ) = @_;
768
769         if($apputils->check_user_perms(
770                         $user_obj->id, $user_obj->home_ou, "DELETE_VOLUME")) {
771                 return OpenILS::Perm->new("DELETE_VOLUME"); }
772
773         #$volume = _find_volume($session, $volume);
774         warn "Deleting volume " . $volume->id . "\n";
775
776         my $copies = $session->request(
777                 "open-ils.storage.direct.asset.copy.search_where.atomic", 
778                 { call_number => $volume->id, deleted => 'f' } )->gather(1);
779                 #"open-ils.storage.direct.asset.copy.search.call_number.atomic",
780
781         if(@$copies) {
782                 throw OpenSRF::EX::ERROR 
783                         ("Cannot remove volume with copies attached");
784         }
785
786         my $req = $session->request(
787                 "open-ils.storage.direct.asset.call_number.delete",
788                 $volume );
789         return $req->gather(1);
790 }
791
792
793 sub _update_volume {
794         my($session, $volume, $user_obj) = @_;
795         if($apputils->check_user_perms(
796                         $user_obj->id, $user_obj->home_ou, "UPDATE_VOLUME")) {
797                 return OpenILS::Perm->new("UPDATE_VOLUME"); }
798
799         my $req = $session->request(
800                 "open-ils.storage.direct.asset.call_number.update",
801                 $volume );
802         my $status = $req->gather(1);
803 }
804
805 sub _add_volume {
806
807         my($session, $volume, $user_obj) = @_;
808
809         if($apputils->check_user_perms(
810                         $user_obj->id, $user_obj->home_ou, "CREATE_VOLUME")) {
811                 warn "User does not have priveleges to create new volumes\n";
812                 return OpenILS::Perm->new("CREATE_VOLUME"); 
813         }
814
815         my $request = $session->request( 
816                 "open-ils.storage.direct.asset.call_number.create", $volume );
817
818         my $id = $request->gather(1);
819
820         if( $id == 0 ) {
821                 OpenILS::Application::AppUtils->rollback_db_session($session);
822                 throw OpenSRF::EX::ERROR (" * -> Error creating new volume");
823         }
824
825         $volume->id($id);
826         warn "received new volume id: $id\n";
827         return $volume;
828
829 }
830
831
832
833
834 __PACKAGE__->register_method(
835         method  => "fleshed_copy_update",
836         api_name        => "open-ils.cat.asset.copy.fleshed.batch.update",
837 );
838
839 sub fleshed_copy_update {
840         my($self, $client, $user_session, $copies) = @_;
841
842         my $user_obj = $apputils->check_user_session($user_session); 
843         my $session = $apputils->start_db_session();
844
845         for my $copy (@$copies) {
846                 _fleshed_copy_update($session, $copy, $user_obj);
847         }
848
849         $apputils->commit_db_session($session);
850         return 1;
851 }
852
853
854
855 sub _delete_copy {
856         my($session, $copy, $user_obj) = @_;
857
858         if($apputils->check_user_perms(
859                         $user_obj->id, $user_obj->home_ou, "DELETE_COPY")) {
860                 return OpenILS::Perm->new("DELETE_COPY"); }
861
862         warn "Deleting copy " . $copy->id . "\n";
863         my $request = $session->request(
864                 "open-ils.storage.direct.asset.copy.delete",
865                 $copy );
866         return $request->gather(1);
867 }
868
869 sub _create_copy {
870         my($session, $copy, $user_obj) = @_;
871
872         if($apputils->check_user_perms(
873                         $user_obj->id, $user_obj->home_ou, "CREATE_COPY")) {
874                 return OpenILS::Perm->new("CREATE_COPY"); }
875
876         my $request = $session->request(
877                 "open-ils.storage.direct.asset.copy.create",
878                 $copy );
879         my $id = $request->gather(1);
880
881         if($id < 1) {
882                 throw OpenSRF::EX::ERROR
883                         ("Unable to create new copy " . Dumper($copy));
884         }
885         $copy->id($id);
886         warn "Created copy " . $copy->id . "\n";
887
888         return $copy;
889
890 }
891
892 sub _update_copy {
893         my($session, $copy, $user_obj) = @_;
894
895         my $evt = $apputils->check_perms($user_obj->id, $copy->circ_lib, 'UPDATE_COPY');
896         return $evt if $evt; #XXX NOT YET HANDLED BY CALLER
897
898         my $status = $apputils->simplereq(      
899                 'open-ils.storage',
900                 "open-ils.storage.direct.asset.copy.update", $copy );
901         $logger->debug("Successfully updated copy " . $copy->id );
902         return $status;
903 }
904
905
906 # -----------------------------------------------------------------
907 # Creates/Updates/Deletes a fleshed asset.copy.  
908 # adds/deletes copy stat_cat maps where necessary
909 # -----------------------------------------------------------------
910 sub _fleshed_copy_update {
911         my($session, $copy, $editor) = @_;
912
913         my $stat_cat_entries = $copy->stat_cat_entries;
914         $copy->editor($editor->id);
915         
916         # in case we're fleshed
917         if(ref($copy->status))          {$copy->status( $copy->status->id ); }
918         if(ref($copy->location))        {$copy->location( $copy->location->id ); }
919         if(ref($copy->circ_lib))        {$copy->circ_lib( $copy->circ_lib->id ); }
920
921         warn "Updating copy " . Dumper($copy) . "\n";
922
923         if( $copy->isdeleted ) { 
924                 return _delete_copy($session, $copy, $editor);
925         } elsif( $copy->isnew ) {
926                 $copy = _create_copy($session, $copy, $editor);
927         } elsif( $copy->ischanged ) {
928                 _update_copy($session, $copy, $editor);
929         }
930
931         
932         return 1 unless ( $stat_cat_entries and @$stat_cat_entries );
933
934         my $stat_maps = $session->request(
935                 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.search.owning_copy.atomic",
936                 $copy->id )->gather(1);
937
938         if(!$copy->isnew) { _delete_stale_maps($session, $stat_maps, $copy); }
939         
940         # go through the stat cat update/create process
941         for my $stat_entry (@{$stat_cat_entries}){ 
942                 _copy_update_stat_cats( $session, $copy, $stat_maps, $stat_entry, $editor );
943         }
944         
945         return 1;
946 }
947
948
949 # -----------------------------------------------------------------
950 # Deletes stat maps attached to this copy in the database that
951 # are no longer attached to the current copy
952 # -----------------------------------------------------------------
953 sub _delete_stale_maps {
954         my( $session, $stat_maps, $copy) = @_;
955
956         warn "Deleting stale stat maps for copy " . $copy->id . "\n";
957         for my $map (@$stat_maps) {
958         # if there is no stat cat entry on the copy who's id matches the
959         # current map's id, remove the map from the database
960         if(! grep { $_->id == $map->stat_cat_entry } @{$copy->stat_cat_entries} ) {
961                 my $req = $session->request(
962                         "open-ils.storage.direct.asset.stat_cat_entry_copy_map.delete", $map );
963                 $req->gather(1);
964                 }
965         }
966
967         return $stat_maps;
968 }
969
970
971 # -----------------------------------------------------------------
972 # Searches the stat maps to see if '$entry' already exists on
973 # the given copy.  If it does not, a new stat map is created
974 # for the given entry and copy
975 # -----------------------------------------------------------------
976 sub _copy_update_stat_cats {
977         my ( $session, $copy, $stat_maps, $entry, $editor ) = @_;
978
979         warn "Updating stat maps for copy " . $copy->id . "\n";
980
981         # see if this map already exists
982         for my $map (@$stat_maps) {
983                 if( $map->stat_cat_entry == $entry->id ) {return;}
984         }
985
986         warn "Creating new stat map for stat  " . 
987                 $entry->stat_cat . " and copy " . $copy->id . "\n";
988
989         # if not, create it
990         my $new_map = Fieldmapper::asset::stat_cat_entry_copy_map->new();
991
992         $new_map->stat_cat( $entry->stat_cat );
993         $new_map->stat_cat_entry( $entry->id );
994         $new_map->owning_copy( $copy->id );
995
996         warn "New map is " . Dumper($new_map) . "\n";
997
998         my $request = $session->request(
999                 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.create",
1000                 $new_map );
1001         my $status = $request->gather(1);
1002         warn "created new map with id $status\n";
1003
1004 }
1005
1006
1007
1008
1009 1;