]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Cat.pm
adding MARC cn extraction
[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 __PACKAGE__->register_method(
421         method  => "biblio_record_marc_cn",
422         api_name        => "open-ils.cat.biblio.record.marc_cn.retrieve",
423         argc            => 1, #(bib id ) 
424 );
425
426 sub biblio_record_marc_cn {
427         my( $self, $client, $id ) = @_;
428
429         my $session = OpenSRF::AppSession->create("open-ils.storage");
430         my $marc = $session
431                 ->request("open-ils.storage.direct.biblio.record_entry.retrieve", $id )
432                 ->gather(1)
433                 ->marc;
434
435         my $doc = XML::LibXML->new->parse_string($marc);
436         $doc->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
437         
438         my @res;
439         for my $tag ( qw/050 055 060 070 080 082 086 088 090 092 096 098 099/ ) {
440                 my @node = $doc->findnodes("//marc:datafield[\@tag='$tag']");
441                 for my $x (@node) {
442                         my $cn = $x->findvalue("marc:subfield[\@code='a' or \@code='b']");
443                         push @res, {$tag => $cn} if ($cn);
444                 }
445         }
446
447         return \@res
448 }
449
450 # gets the username
451 sub _get_userid_by_id {
452
453         my @ids = @_;
454         my @users;
455
456         my $session = OpenSRF::AppSession->create( "open-ils.storage" );
457         my $request = $session->request( 
458                 "open-ils.storage.direct.actor.user.batch.retrieve.atomic", @ids );
459
460         $request->wait_complete;
461         my $response = $request->recv();
462         if(!$request->complete) { return undef; }
463
464         if($response->isa("Error")){
465                 throw $response ($response);
466         }
467
468         for my $u (@{$response->content}) {
469                 next unless ref($u);
470                 push @users, $u->usrname;
471         }
472
473         $request->finish;
474         $session->disconnect;
475         $session->kill_me();
476
477         return @users;
478 }
479
480 sub _get_id_by_userid {
481
482         my @users = @_;
483         my @ids;
484
485         my $session = OpenSRF::AppSession->create( "open-ils.storage" );
486         my $request = $session->request( 
487                 "open-ils.storage.direct.actor.user.search.usrname.atomic", @users );
488
489         $request->wait_complete;
490         my $response = $request->recv();
491         if(!$request->complete) { 
492                 throw OpenSRF::EX::ERROR ("no response from storage on user retrieve");
493         }
494
495         if(UNIVERSAL::isa( $response, "Error")){
496                 throw $response ($response);
497         }
498
499         for my $u (@{$response->content}) {
500                 next unless ref($u);
501                 push @ids, $u->id();
502         }
503
504         $request->finish;
505         $session->disconnect;
506         $session->kill_me();
507
508         return @ids;
509 }
510
511
512 # commits metadata objects to the db
513 sub _update_record_metadata {
514
515         my ($session, @docs ) = @_;
516
517         for my $doc (@docs) {
518
519                 my $user_obj = $doc->{user};
520                 my $docid = $doc->{docid};
521
522                 warn "Updating metata for doc $docid\n";
523
524                 my $request = $session->request( 
525                         "open-ils.storage.direct.biblio.record_entry.retrieve", $docid );
526                 my $record = $request->gather(1);
527
528                 warn "retrieved record\n";
529                 my ($id) = _get_id_by_userid($user_obj->usrname);
530
531                 warn "got $id from _get_id_by_userid\n";
532                 $record->editor($id);
533                 
534                 warn "Grabbed the record, updating and moving on\n";
535
536                 $request = $session->request( 
537                         "open-ils.storage.direct.biblio.record_entry.update", $record );
538                 $request->gather(1);
539         }
540
541         warn "committing metarecord update\n";
542
543         return 1;
544 }
545
546
547
548 __PACKAGE__->register_method(
549         method  => "orgs_for_title",
550         api_name        => "open-ils.cat.actor.org_unit.retrieve_by_title"
551 );
552
553 sub orgs_for_title {
554         my( $self, $client, $record_id ) = @_;
555
556         my $vols = $apputils->simple_scalar_request(
557                 "open-ils.storage",
558                 "open-ils.storage.direct.asset.call_number.search.record.atomic",
559                 $record_id );
560
561         my $orgs = { map {$_->owning_lib => 1 } @$vols };
562         return [ keys %$orgs ];
563 }
564
565
566
567 __PACKAGE__->register_method(
568         method  => "retrieve_copies",
569         api_name        => "open-ils.cat.asset.copy_tree.retrieve");
570
571 __PACKAGE__->register_method(
572         method  => "retrieve_copies",
573         api_name        => "open-ils.cat.asset.copy_tree.global.retrieve");
574
575 # user_session may be null/undef
576 sub retrieve_copies {
577
578         my( $self, $client, $user_session, $docid, @org_ids ) = @_;
579
580         if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
581
582         $docid = "$docid";
583
584         warn " $$ retrieving copy tree for orgs @org_ids and doc $docid at " . time() . "\n";
585
586         # grabbing copy trees should be available for everyone..
587         if(!@org_ids and $user_session) {
588                 my $user_obj = 
589                         OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
590                         @org_ids = ($user_obj->home_ou);
591         }
592
593         if( $self->api_name =~ /global/ ) {
594                 warn "performing global copy_tree search for $docid\n";
595                 return _build_volume_list( { record => $docid } );
596
597         } else {
598
599                 my @all_vols;
600                 for my $orgid (@org_ids) {
601                         my $vols = _build_volume_list( 
602                                         { record => $docid, owning_lib => $orgid } );
603                         warn "Volumes built for org $orgid\n";
604                         push( @all_vols, @$vols );
605                 }
606                 
607                 warn " $$ Finished copy_tree at " . time() . "\n";
608                 return \@all_vols;
609         }
610
611         return undef;
612 }
613
614
615 sub _build_volume_list {
616         my $search_hash = shift;
617
618         my      $session = OpenSRF::AppSession->create( "open-ils.storage" );
619         
620
621         my $request = $session->request( 
622                         "open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
623
624         my $vols = $request->gather(1);
625         my @volumes;
626
627         for my $volume (@$vols) {
628
629                 warn "Grabbing copies for volume: " . $volume->id . "\n";
630                 my $creq = $session->request(
631                         "open-ils.storage.direct.asset.copy.search.call_number.atomic", $volume->id );
632
633                 my $copies = $creq->gather(1);
634
635                 $copies = [ sort { $a->barcode cmp $b->barcode } @$copies  ];
636
637                 $volume->copies($copies);
638
639                 push( @volumes, $volume );
640         }
641
642
643         $session->disconnect();
644         return \@volumes;
645
646 }
647
648
649 # -----------------------------------------------------------------
650 # Fleshed volume tree batch add/update.  This does everything a 
651 # volume tree could want, add, update, delete
652 # -----------------------------------------------------------------
653 __PACKAGE__->register_method(
654         method  => "volume_tree_fleshed_update",
655         api_name        => "open-ils.cat.asset.volume_tree.fleshed.batch.update",
656 );
657 sub volume_tree_fleshed_update {
658
659         my( $self, $client, $user_session, $volumes ) = @_;
660         return undef unless $volumes;
661
662         my $user_obj = $apputils->check_user_session($user_session);
663
664
665         my $session = $apputils->start_db_session();
666         warn "Looping on volumes in fleshed volume tree update\n";
667
668         # cycle through the volumes provided and update/create/delete where necessary
669         for my $volume (@$volumes) {
670
671                 warn "updating volume " . $volume->id . "\n";
672
673                 my $update_copy_list = $volume->copies;
674
675
676                 if( $volume->isdeleted) {
677                         my $status = _delete_volume($session, $volume, $user_obj);
678                         if(!$status) {
679                                 throw OpenSRF::EX::ERROR
680                                         ("Volume delete failed for volume " . $volume->id);
681                         }
682                         if(UNIVERSAL::isa($status, "Fieldmapper::perm_ex")) { return $status; }
683
684                 } elsif( $volume->isnew ) {
685
686                         $volume->clear_id;
687                         $volume->editor($user_obj->id);
688                         $volume->creator($user_obj->id);
689                         $volume = _add_volume($session, $volume, $user_obj);
690                         use Data::Dumper;
691                         warn Dumper $volume;
692                         if($volume and UNIVERSAL::isa($volume, "Fieldmapper::perm_ex")) { return $volume; }
693
694                 } elsif( $volume->ischanged ) {
695
696                         $volume->editor($user_obj->id);
697                         my $stat = _update_volume($session, $volume, $user_obj);
698                         if($stat and UNIVERSAL::isa($stat, "Fieldmapper::perm_ex")) { return $stat; }
699                 }
700
701
702                 if( ! $volume->isdeleted ) {
703                         for my $copy (@{$update_copy_list}) {
704         
705                                 $copy->editor($user_obj->id);
706                                 warn "updating copy for volume " . $volume->id . "\n";
707         
708                                 if( $copy->isnew ) {
709         
710                                         $copy->clear_id;
711                                         $copy->call_number($volume->id);
712                                         $copy->creator($user_obj->id);
713                                         $copy = _fleshed_copy_update($session,$copy,$user_obj);
714         
715                                 } elsif( $copy->ischanged ) {
716                                         $copy->call_number($volume->id);
717                                         $copy = _fleshed_copy_update($session, $copy, $user_obj);
718         
719                                 } elsif( $copy->isdeleted ) {
720                                         warn "Deleting copy " . $copy->id . " for volume " . $volume->id . "\n";
721                                         my $status = _fleshed_copy_update($session, $copy, $user_obj);
722                                         warn "Copy delete returned a status of $status\n";
723                                 }
724                         }
725                 }
726         }
727
728         $apputils->commit_db_session($session);
729         return scalar(@$volumes);
730 }
731
732
733 sub _delete_volume {
734         my( $session, $volume, $user_obj ) = @_;
735
736         if($apputils->check_user_perms(
737                         $user_obj->id, $user_obj->home_ou, "DELETE_VOLUME")) {
738                 return OpenILS::Perm->new("DELETE_VOLUME"); }
739
740         #$volume = _find_volume($session, $volume);
741         warn "Deleting volume " . $volume->id . "\n";
742
743         my $copies = $session->request(
744                 "open-ils.storage.direct.asset.copy.search.call_number.atomic",
745                 $volume->id )->gather(1);
746         if(@$copies) {
747                 throw OpenSRF::EX::ERROR 
748                         ("Cannot remove volume with copies attached");
749         }
750
751         my $req = $session->request(
752                 "open-ils.storage.direct.asset.call_number.delete",
753                 $volume );
754         return $req->gather(1);
755 }
756
757
758 sub _update_volume {
759         my($session, $volume, $user_obj) = @_;
760         if($apputils->check_user_perms(
761                         $user_obj->id, $user_obj->home_ou, "UPDATE_VOLUME")) {
762                 return OpenILS::Perm->new("UPDATE_VOLUME"); }
763
764         my $req = $session->request(
765                 "open-ils.storage.direct.asset.call_number.update",
766                 $volume );
767         my $status = $req->gather(1);
768 }
769
770 sub _add_volume {
771
772         my($session, $volume, $user_obj) = @_;
773
774         if($apputils->check_user_perms(
775                         $user_obj->id, $user_obj->home_ou, "CREATE_VOLUME")) {
776                 warn "User does not have priveleges to create new volumes\n";
777                 return OpenILS::Perm->new("CREATE_VOLUME"); 
778         }
779
780         my $request = $session->request( 
781                 "open-ils.storage.direct.asset.call_number.create", $volume );
782
783         my $id = $request->gather(1);
784
785         if( $id == 0 ) {
786                 OpenILS::Application::AppUtils->rollback_db_session($session);
787                 throw OpenSRF::EX::ERROR (" * -> Error creating new volume");
788         }
789
790         $volume->id($id);
791         warn "received new volume id: $id\n";
792         return $volume;
793
794 }
795
796
797
798
799 __PACKAGE__->register_method(
800         method  => "fleshed_copy_update",
801         api_name        => "open-ils.cat.asset.copy.fleshed.batch.update",
802 );
803
804 sub fleshed_copy_update {
805         my($self, $client, $user_session, $copies) = @_;
806
807         my $user_obj = $apputils->check_user_session($user_session); 
808         my $session = $apputils->start_db_session();
809
810         for my $copy (@$copies) {
811                 _fleshed_copy_update($session, $copy, $user_obj);
812         }
813
814         $apputils->commit_db_session($session);
815         return 1;
816 }
817
818
819
820 sub _delete_copy {
821         my($session, $copy, $user_obj) = @_;
822
823         if($apputils->check_user_perms(
824                         $user_obj->id, $user_obj->home_ou, "DELETE_COPY")) {
825                 return OpenILS::Perm->new("DELETE_COPY"); }
826
827         warn "Deleting copy " . $copy->id . "\n";
828         my $request = $session->request(
829                 "open-ils.storage.direct.asset.copy.delete",
830                 $copy );
831         return $request->gather(1);
832 }
833
834 sub _create_copy {
835         my($session, $copy, $user_obj) = @_;
836
837         if($apputils->check_user_perms(
838                         $user_obj->id, $user_obj->home_ou, "CREATE_COPY")) {
839                 return OpenILS::Perm->new("CREATE_COPY"); }
840
841         my $request = $session->request(
842                 "open-ils.storage.direct.asset.copy.create",
843                 $copy );
844         my $id = $request->gather(1);
845
846         if($id < 1) {
847                 throw OpenSRF::EX::ERROR
848                         ("Unable to create new copy " . Dumper($copy));
849         }
850         $copy->id($id);
851         warn "Created copy " . $copy->id . "\n";
852
853         return $copy;
854
855 }
856
857 sub _update_copy {
858         my($session, $copy, $user_obj) = @_;
859
860         my $evt = $apputils->check_perms($user_obj->id, $copy->circ_lib, 'UPDATE_COPY');
861         return $evt if $evt; #XXX NOT YET HANDLED BY CALLER
862
863         my $status = $apputils->simplereq(      
864                 'open-ils.storage',
865                 "open-ils.storage.direct.asset.copy.update", $copy );
866         $logger->debug("Successfully updated copy " . $copy->id );
867         return $status;
868 }
869
870
871 # -----------------------------------------------------------------
872 # Creates/Updates/Deletes a fleshed asset.copy.  
873 # adds/deletes copy stat_cat maps where necessary
874 # -----------------------------------------------------------------
875 sub _fleshed_copy_update {
876         my($session, $copy, $editor) = @_;
877
878         my $stat_cat_entries = $copy->stat_cat_entries;
879         $copy->editor($editor->id);
880         
881         # in case we're fleshed
882         if(ref($copy->status))          {$copy->status( $copy->status->id ); }
883         if(ref($copy->location))        {$copy->location( $copy->location->id ); }
884         if(ref($copy->circ_lib))        {$copy->circ_lib( $copy->circ_lib->id ); }
885
886         warn "Updating copy " . Dumper($copy) . "\n";
887
888         if( $copy->isdeleted ) { 
889                 return _delete_copy($session, $copy, $editor);
890         } elsif( $copy->isnew ) {
891                 $copy = _create_copy($session, $copy, $editor);
892         } elsif( $copy->ischanged ) {
893                 _update_copy($session, $copy, $editor);
894         }
895
896         
897         return 1 unless ( $stat_cat_entries and @$stat_cat_entries );
898
899         my $stat_maps = $session->request(
900                 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.search.owning_copy.atomic",
901                 $copy->id )->gather(1);
902
903         if(!$copy->isnew) { _delete_stale_maps($session, $stat_maps, $copy); }
904         
905         # go through the stat cat update/create process
906         for my $stat_entry (@{$stat_cat_entries}){ 
907                 _copy_update_stat_cats( $session, $copy, $stat_maps, $stat_entry, $editor );
908         }
909         
910         return 1;
911 }
912
913
914 # -----------------------------------------------------------------
915 # Deletes stat maps attached to this copy in the database that
916 # are no longer attached to the current copy
917 # -----------------------------------------------------------------
918 sub _delete_stale_maps {
919         my( $session, $stat_maps, $copy) = @_;
920
921         warn "Deleting stale stat maps for copy " . $copy->id . "\n";
922         for my $map (@$stat_maps) {
923         # if there is no stat cat entry on the copy who's id matches the
924         # current map's id, remove the map from the database
925         if(! grep { $_->id == $map->stat_cat_entry } @{$copy->stat_cat_entries} ) {
926                 my $req = $session->request(
927                         "open-ils.storage.direct.asset.stat_cat_entry_copy_map.delete", $map );
928                 $req->gather(1);
929                 }
930         }
931
932         return $stat_maps;
933 }
934
935
936 # -----------------------------------------------------------------
937 # Searches the stat maps to see if '$entry' already exists on
938 # the given copy.  If it does not, a new stat map is created
939 # for the given entry and copy
940 # -----------------------------------------------------------------
941 sub _copy_update_stat_cats {
942         my ( $session, $copy, $stat_maps, $entry, $editor ) = @_;
943
944         warn "Updating stat maps for copy " . $copy->id . "\n";
945
946         # see if this map already exists
947         for my $map (@$stat_maps) {
948                 if( $map->stat_cat_entry == $entry->id ) {return;}
949         }
950
951         warn "Creating new stat map for stat  " . 
952                 $entry->stat_cat . " and copy " . $copy->id . "\n";
953
954         # if not, create it
955         my $new_map = Fieldmapper::asset::stat_cat_entry_copy_map->new();
956
957         $new_map->stat_cat( $entry->stat_cat );
958         $new_map->stat_cat_entry( $entry->id );
959         $new_map->owning_copy( $copy->id );
960
961         warn "New map is " . Dumper($new_map) . "\n";
962
963         my $request = $session->request(
964                 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.create",
965                 $new_map );
966         my $status = $request->gather(1);
967         warn "created new map with id $status\n";
968
969 }
970
971
972
973
974 1;