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