checking for deleted on call_number searches
[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_where.atomic",
208                 { tcn_value => $tcn, deleted => 'f' } );
209                 #"open-ils.storage.direct.biblio.record_entry.search.tcn_value.atomic",
210
211         my $recs = $req->gather(1);
212
213         if($recs and $recs->[0]) {
214                 $logger->debug("_tcn_exists is true for tcn : $tcn");
215                 return 1;
216         }
217
218         $logger->debug("_tcn_exists is false for tcn : $tcn");
219         return 0;
220 }
221
222
223
224 __PACKAGE__->register_method(
225         method  => "biblio_record_tree_retrieve",
226         api_name        => "open-ils.cat.biblio.record.tree.retrieve",
227 );
228
229 sub biblio_record_tree_retrieve {
230
231         my( $self, $client, $recordid ) = @_;
232
233         my $name = "open-ils.storage.direct.biblio.record_entry.retrieve";
234         my $session = OpenSRF::AppSession->create( "open-ils.storage" );
235         my $request = $session->request( $name, $recordid );
236         my $marcxml = $request->gather(1);
237
238         if(!$marcxml) {
239                 throw OpenSRF::EX::ERROR 
240                         ("No record in database with id $recordid");
241         }
242
243         $session->disconnect();
244         $session->kill_me();
245
246         warn "turning into nodeset\n";
247         my $nodes = OpenILS::Utils::FlatXML->new()->xml_to_nodeset( $marcxml->marc ); 
248         warn "turning nodeset into tree\n";
249         my $tree = $utils->nodeset2tree( $nodes->nodeset );
250
251         $tree->owner_doc( $marcxml->id() );
252
253         warn "returning tree\n";
254
255         return $tree;
256 }
257
258 __PACKAGE__->register_method(
259         method  => "biblio_record_tree_commit",
260         api_name        => "open-ils.cat.biblio.record.tree.commit",
261         argc            => 3, #(session_id, biblio_tree ) 
262         notes           => <<"  NOTES");
263         Walks the tree and commits any changed nodes 
264         adds any new nodes, and deletes any deleted nodes
265         The record to commit must already exist or this
266         method will fail
267         NOTES
268
269 sub biblio_record_tree_commit {
270
271         my( $self, $client, $user_session,  $tree ) = @_;
272
273         throw OpenSRF::EX::InvalidArg 
274                 ("Not enough args to to open-ils.cat.biblio.record.tree.commit")
275                 unless ( $user_session and $tree );
276
277         my $user_obj = $apputils->check_user_session($user_session); 
278
279         if($apputils->check_user_perms(
280                         $user_obj->id, $user_obj->home_ou, "UPDATE_MARC")) {
281                 return OpenILS::Perm->new("UPDATE_MARC"); 
282         }
283
284
285         # capture the doc id
286         my $docid = $tree->owner_doc();
287         my $session = OpenILS::Application::AppUtils->start_db_session();
288
289         warn "Retrieving biblio record from storage for update\n";
290
291         my $req1 = $session->request(
292                         "open-ils.storage.direct.biblio.record_entry.batch.retrieve", $docid );
293         my $biblio = $req1->gather(1);
294
295         warn "retrieved doc $docid\n";
296
297
298         # turn the tree into a nodeset
299         my $nodeset = $utils->tree2nodeset($tree);
300         $nodeset = $utils->clean_nodeset($nodeset);
301
302         if(!defined($docid)) { # be sure
303                 for my $node (@$nodeset) {
304                         $docid = $node->owner_doc();
305                         last if defined($docid);
306                 }
307         }
308
309         # turn the nodeset into a doc
310         my $marcxml = OpenILS::Utils::FlatXML->new()->nodeset_to_xml( $nodeset );
311
312         $biblio->marc( $marcxml->toString() );
313
314         warn "Starting db session\n";
315
316         my $x = _update_record_metadata( $session, { user => $user_obj, docid => $docid } );
317         OpenILS::Application::AppUtils->rollback_db_session($session) unless $x;
318
319         warn "Sending updated doc $docid to db\n";
320         my $req = $session->request( "open-ils.storage.direct.biblio.record_entry.update", $biblio );
321
322         $req->wait_complete;
323         my $status = $req->recv();
324         if( !$status || $status->isa("Error") || ! $status->content) {
325                 OpenILS::Application::AppUtils->rollback_db_session($session);
326                 if($status->isa("Error")) { throw $status ($status); }
327                 throw OpenSRF::EX::ERROR ("Error updating biblio record");
328         }
329         $req->finish();
330
331         # Send the doc to the wormer for wormizing
332         warn "Starting worm session\n";
333
334         my $success = 0;
335         my $wresp;
336
337         my $wreq = $session->request( "open-ils.worm.wormize.biblio", $docid );
338
339         try {
340                 $wreq->gather(1);
341
342         } catch Error with {
343                 my $e = shift;
344                 warn "wormizing failed, rolling back\n";
345                 OpenILS::Application::AppUtils->rollback_db_session($session);
346
347                 if($e) { throw $e ($e); }
348                 throw OpenSRF::EX::ERROR ("Wormizing Failed for $docid" );
349         };
350
351         warn "Committing db session...\n";
352         OpenILS::Application::AppUtils->commit_db_session( $session );
353
354         $nodeset = OpenILS::Utils::FlatXML->new()->xmldoc_to_nodeset($marcxml);
355         $tree = $utils->nodeset2tree($nodeset->nodeset);
356         $tree->owner_doc($docid);
357
358 #       $client->respond_complete($tree);
359
360         warn "Done wormizing\n";
361
362         #use Data::Dumper;
363         #warn "Returning tree:\n";
364         #warn Dumper $tree;
365
366         return $tree;
367
368 }
369
370
371
372 __PACKAGE__->register_method(
373         method  => "biblio_record_record_metadata",
374         api_name        => "open-ils.cat.biblio.record.metadata.retrieve",
375         argc            => 1, #(session_id, biblio_tree ) 
376         notes           => "Walks the tree and commits any changed nodes " .
377                                         "adds any new nodes, and deletes any deleted nodes",
378 );
379
380 sub biblio_record_record_metadata {
381         my( $self, $client, @ids ) = @_;
382
383         if(!@ids){return undef;}
384
385         my $session = OpenSRF::AppSession->create("open-ils.storage");
386         my $request = $session->request( 
387                         "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
388
389         my $results = [];
390
391         while( my $response = $request->recv() ) {
392
393                 if(!$response) {
394                         throw OpenSRF::EX::ERROR ("No Response from Storage");
395                 }
396                 if($response->isa("Error")) {
397                         throw $response ($response->stringify);
398                 }
399
400                 my $record_entry = $response->content;
401
402                 my $creator = $record_entry->creator;
403                 my $editor      = $record_entry->editor;
404
405                 ($creator, $editor) = _get_userid_by_id($creator, $editor);
406
407                 $record_entry->creator($creator);
408                 $record_entry->editor($editor);
409
410                 push @$results, $record_entry;
411
412         }
413
414         $request->finish;
415         $session->disconnect();
416         $session->finish();
417
418         return $results;
419
420 }
421
422 __PACKAGE__->register_method(
423         method  => "biblio_record_marc_cn",
424         api_name        => "open-ils.cat.biblio.record.marc_cn.retrieve",
425         argc            => 1, #(bib id ) 
426 );
427
428 sub biblio_record_marc_cn {
429         my( $self, $client, $id ) = @_;
430
431         my $session = OpenSRF::AppSession->create("open-ils.storage");
432         my $marc = $session
433                 ->request("open-ils.storage.direct.biblio.record_entry.retrieve", $id )
434                 ->gather(1)
435                 ->marc;
436
437         my $doc = XML::LibXML->new->parse_string($marc);
438         $doc->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
439         
440         my @res;
441         for my $tag ( qw/050 055 060 070 080 082 086 088 090 092 096 098 099/ ) {
442                 my @node = $doc->findnodes("//marc:datafield[\@tag='$tag']");
443                 for my $x (@node) {
444                         my $cn = $x->findvalue("marc:subfield[\@code='a' or \@code='b']");
445                         push @res, {$tag => $cn} if ($cn);
446                 }
447         }
448
449         return \@res
450 }
451
452 # gets the username
453 sub _get_userid_by_id {
454
455         my @ids = @_;
456         my @users;
457
458         my $session = OpenSRF::AppSession->create( "open-ils.storage" );
459         my $request = $session->request( 
460                 "open-ils.storage.direct.actor.user.batch.retrieve.atomic", @ids );
461
462         $request->wait_complete;
463         my $response = $request->recv();
464         if(!$request->complete) { return undef; }
465
466         if($response->isa("Error")){
467                 throw $response ($response);
468         }
469
470         for my $u (@{$response->content}) {
471                 next unless ref($u);
472                 push @users, $u->usrname;
473         }
474
475         $request->finish;
476         $session->disconnect;
477         $session->kill_me();
478
479         return @users;
480 }
481
482 sub _get_id_by_userid {
483
484         my @users = @_;
485         my @ids;
486
487         my $session = OpenSRF::AppSession->create( "open-ils.storage" );
488         my $request = $session->request( 
489                 "open-ils.storage.direct.actor.user.search.usrname.atomic", @users );
490
491         $request->wait_complete;
492         my $response = $request->recv();
493         if(!$request->complete) { 
494                 throw OpenSRF::EX::ERROR ("no response from storage on user retrieve");
495         }
496
497         if(UNIVERSAL::isa( $response, "Error")){
498                 throw $response ($response);
499         }
500
501         for my $u (@{$response->content}) {
502                 next unless ref($u);
503                 push @ids, $u->id();
504         }
505
506         $request->finish;
507         $session->disconnect;
508         $session->kill_me();
509
510         return @ids;
511 }
512
513
514 # commits metadata objects to the db
515 sub _update_record_metadata {
516
517         my ($session, @docs ) = @_;
518
519         for my $doc (@docs) {
520
521                 my $user_obj = $doc->{user};
522                 my $docid = $doc->{docid};
523
524                 warn "Updating metata for doc $docid\n";
525
526                 my $request = $session->request( 
527                         "open-ils.storage.direct.biblio.record_entry.retrieve", $docid );
528                 my $record = $request->gather(1);
529
530                 warn "retrieved record\n";
531                 my ($id) = _get_id_by_userid($user_obj->usrname);
532
533                 warn "got $id from _get_id_by_userid\n";
534                 $record->editor($id);
535                 
536                 warn "Grabbed the record, updating and moving on\n";
537
538                 $request = $session->request( 
539                         "open-ils.storage.direct.biblio.record_entry.update", $record );
540                 $request->gather(1);
541         }
542
543         warn "committing metarecord update\n";
544
545         return 1;
546 }
547
548
549
550 __PACKAGE__->register_method(
551         method  => "orgs_for_title",
552         api_name        => "open-ils.cat.actor.org_unit.retrieve_by_title"
553 );
554
555 sub orgs_for_title {
556         my( $self, $client, $record_id ) = @_;
557
558         my $vols = $apputils->simple_scalar_request(
559                 "open-ils.storage",
560                 "open-ils.storage.direct.asset.call_number.search_where.atomic",
561                 { record => $record_id, deleted => 'f' });
562                 #"open-ils.storage.direct.asset.call_number.search.record.atomic",
563
564         my $orgs = { map {$_->owning_lib => 1 } @$vols };
565         return [ keys %$orgs ];
566 }
567
568
569 __PACKAGE__->register_method(
570         method  => "retrieve_copies_",
571         api_name        => "open-ils.cat.asset.copy_tree.retrieve_");
572
573 sub retrieve_copies_ {
574         my( $self, $conn, $authtoken, $docid, $orgs ) = @_;
575 }
576
577
578 __PACKAGE__->register_method(
579         method  => "retrieve_copies",
580         api_name        => "open-ils.cat.asset.copy_tree.retrieve");
581
582 __PACKAGE__->register_method(
583         method  => "retrieve_copies",
584         api_name        => "open-ils.cat.asset.copy_tree.global.retrieve");
585
586 # user_session may be null/undef
587 sub retrieve_copies {
588
589         my( $self, $client, $user_session, $docid, @org_ids ) = @_;
590
591         if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
592
593         $docid = "$docid";
594
595         warn " $$ retrieving copy tree for orgs @org_ids and doc $docid at " . time() . "\n";
596
597         # grabbing copy trees should be available for everyone..
598         if(!@org_ids and $user_session) {
599                 my $user_obj = 
600                         OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
601                         @org_ids = ($user_obj->home_ou);
602         }
603
604         if( $self->api_name =~ /global/ ) {
605                 warn "performing global copy_tree search for $docid\n";
606                 return _build_volume_list( { record => $docid } );
607
608         } else {
609
610                 my @all_vols;
611                 for my $orgid (@org_ids) {
612                         my $vols = _build_volume_list( 
613                                         { record => $docid, owning_lib => $orgid } );
614                         warn "Volumes built for org $orgid\n";
615                         push( @all_vols, @$vols );
616                 }
617                 
618                 warn " $$ Finished copy_tree at " . time() . "\n";
619                 return \@all_vols;
620         }
621
622         return undef;
623 }
624
625
626 sub _build_volume_list {
627         my $search_hash = shift;
628
629         $search_hash->{deleted} = 'f';
630
631         my      $session = OpenSRF::AppSession->create( "open-ils.storage" );
632         
633
634         my $request = $session->request( 
635                         "open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
636                         #"open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
637
638         my $vols = $request->gather(1);
639         my @volumes;
640
641         for my $volume (@$vols) {
642
643                 warn "Grabbing copies for volume: " . $volume->id . "\n";
644                 my $creq = $session->request(
645                         "open-ils.storage.direct.asset.copy.search_where.atomic", 
646                         { call_number => $volume->id , deleted => 'f' });
647                         #"open-ils.storage.direct.asset.copy.search.call_number.atomic", $volume->id );
648
649                 my $copies = $creq->gather(1);
650
651                 $copies = [ sort { $a->barcode cmp $b->barcode } @$copies  ];
652
653                 $volume->copies($copies);
654
655                 push( @volumes, $volume );
656         }
657
658
659         $session->disconnect();
660         return \@volumes;
661
662 }
663
664
665 # -----------------------------------------------------------------
666 # Fleshed volume tree batch add/update.  This does everything a 
667 # volume tree could want, add, update, delete
668 # -----------------------------------------------------------------
669 __PACKAGE__->register_method(
670         method  => "volume_tree_fleshed_update",
671         api_name        => "open-ils.cat.asset.volume_tree.fleshed.batch.update",
672 );
673 sub volume_tree_fleshed_update {
674
675         my( $self, $client, $user_session, $volumes ) = @_;
676         return undef unless $volumes;
677
678         my $user_obj = $apputils->check_user_session($user_session);
679
680
681         my $session = $apputils->start_db_session();
682         warn "Looping on volumes in fleshed volume tree update\n";
683
684         # cycle through the volumes provided and update/create/delete where necessary
685         for my $volume (@$volumes) {
686
687                 warn "updating volume " . $volume->id . "\n";
688
689                 my $update_copy_list = $volume->copies;
690
691
692                 if( $volume->isdeleted) {
693                         my $status = _delete_volume($session, $volume, $user_obj);
694                         #if(!$status) {
695                                 #throw OpenSRF::EX::ERROR
696                                         #("Volume delete failed for volume " . $volume->id);
697                         #}
698                         if(UNIVERSAL::isa($status, "Fieldmapper::perm_ex")) { return $status; }
699
700                 } elsif( $volume->isnew ) {
701
702                         $volume->clear_id;
703                         $volume->editor($user_obj->id);
704                         $volume->creator($user_obj->id);
705                         $volume = _add_volume($session, $volume, $user_obj);
706                         use Data::Dumper;
707                         warn Dumper $volume;
708                         if($volume and UNIVERSAL::isa($volume, "Fieldmapper::perm_ex")) { return $volume; }
709
710                 } elsif( $volume->ischanged ) {
711
712                         $volume->editor($user_obj->id);
713                         my $stat = _update_volume($session, $volume, $user_obj);
714                         if($stat and UNIVERSAL::isa($stat, "Fieldmapper::perm_ex")) { return $stat; }
715                 }
716
717
718                 if( ! $volume->isdeleted ) {
719                         for my $copy (@{$update_copy_list}) {
720         
721                                 $copy->editor($user_obj->id);
722                                 warn "updating copy for volume " . $volume->id . "\n";
723         
724                                 if( $copy->isnew ) {
725         
726                                         $copy->clear_id;
727                                         $copy->call_number($volume->id);
728                                         $copy->creator($user_obj->id);
729                                         $copy = _fleshed_copy_update($session,$copy,$user_obj);
730         
731                                 } elsif( $copy->ischanged ) {
732                                         $copy->call_number($volume->id);
733                                         $copy = _fleshed_copy_update($session, $copy, $user_obj);
734         
735                                 } elsif( $copy->isdeleted ) {
736                                         warn "Deleting copy " . $copy->id . " for volume " . $volume->id . "\n";
737                                         my $status = _fleshed_copy_update($session, $copy, $user_obj);
738                                         warn "Copy delete returned a status of $status\n";
739                                 }
740                         }
741                 }
742         }
743
744         $apputils->commit_db_session($session);
745         return scalar(@$volumes);
746 }
747
748
749 sub _delete_volume {
750         my( $session, $volume, $user_obj ) = @_;
751
752         if($apputils->check_user_perms(
753                         $user_obj->id, $user_obj->home_ou, "DELETE_VOLUME")) {
754                 return OpenILS::Perm->new("DELETE_VOLUME"); }
755
756         #$volume = _find_volume($session, $volume);
757         warn "Deleting volume " . $volume->id . "\n";
758
759         my $copies = $session->request(
760                 "open-ils.storage.direct.asset.copy.search_where.atomic", 
761                 { call_number => $volume->id, deleted => 'f' } )->gather(1);
762                 #"open-ils.storage.direct.asset.copy.search.call_number.atomic",
763
764         if(@$copies) {
765                 throw OpenSRF::EX::ERROR 
766                         ("Cannot remove volume with copies attached");
767         }
768
769         my $req = $session->request(
770                 "open-ils.storage.direct.asset.call_number.delete",
771                 $volume );
772         return $req->gather(1);
773 }
774
775
776 sub _update_volume {
777         my($session, $volume, $user_obj) = @_;
778         if($apputils->check_user_perms(
779                         $user_obj->id, $user_obj->home_ou, "UPDATE_VOLUME")) {
780                 return OpenILS::Perm->new("UPDATE_VOLUME"); }
781
782         my $req = $session->request(
783                 "open-ils.storage.direct.asset.call_number.update",
784                 $volume );
785         my $status = $req->gather(1);
786 }
787
788 sub _add_volume {
789
790         my($session, $volume, $user_obj) = @_;
791
792         if($apputils->check_user_perms(
793                         $user_obj->id, $user_obj->home_ou, "CREATE_VOLUME")) {
794                 warn "User does not have priveleges to create new volumes\n";
795                 return OpenILS::Perm->new("CREATE_VOLUME"); 
796         }
797
798         my $request = $session->request( 
799                 "open-ils.storage.direct.asset.call_number.create", $volume );
800
801         my $id = $request->gather(1);
802
803         if( $id == 0 ) {
804                 OpenILS::Application::AppUtils->rollback_db_session($session);
805                 throw OpenSRF::EX::ERROR (" * -> Error creating new volume");
806         }
807
808         $volume->id($id);
809         warn "received new volume id: $id\n";
810         return $volume;
811
812 }
813
814
815
816
817 __PACKAGE__->register_method(
818         method  => "fleshed_copy_update",
819         api_name        => "open-ils.cat.asset.copy.fleshed.batch.update",
820 );
821
822 sub fleshed_copy_update {
823         my($self, $client, $user_session, $copies) = @_;
824
825         my $user_obj = $apputils->check_user_session($user_session); 
826         my $session = $apputils->start_db_session();
827
828         for my $copy (@$copies) {
829                 _fleshed_copy_update($session, $copy, $user_obj);
830         }
831
832         $apputils->commit_db_session($session);
833         return 1;
834 }
835
836
837
838 sub _delete_copy {
839         my($session, $copy, $user_obj) = @_;
840
841         if($apputils->check_user_perms(
842                         $user_obj->id, $user_obj->home_ou, "DELETE_COPY")) {
843                 return OpenILS::Perm->new("DELETE_COPY"); }
844
845         warn "Deleting copy " . $copy->id . "\n";
846         my $request = $session->request(
847                 "open-ils.storage.direct.asset.copy.delete",
848                 $copy );
849         return $request->gather(1);
850 }
851
852 sub _create_copy {
853         my($session, $copy, $user_obj) = @_;
854
855         if($apputils->check_user_perms(
856                         $user_obj->id, $user_obj->home_ou, "CREATE_COPY")) {
857                 return OpenILS::Perm->new("CREATE_COPY"); }
858
859         my $request = $session->request(
860                 "open-ils.storage.direct.asset.copy.create",
861                 $copy );
862         my $id = $request->gather(1);
863
864         if($id < 1) {
865                 throw OpenSRF::EX::ERROR
866                         ("Unable to create new copy " . Dumper($copy));
867         }
868         $copy->id($id);
869         warn "Created copy " . $copy->id . "\n";
870
871         return $copy;
872
873 }
874
875 sub _update_copy {
876         my($session, $copy, $user_obj) = @_;
877
878         my $evt = $apputils->check_perms($user_obj->id, $copy->circ_lib, 'UPDATE_COPY');
879         return $evt if $evt; #XXX NOT YET HANDLED BY CALLER
880
881         my $status = $apputils->simplereq(      
882                 'open-ils.storage',
883                 "open-ils.storage.direct.asset.copy.update", $copy );
884         $logger->debug("Successfully updated copy " . $copy->id );
885         return $status;
886 }
887
888
889 # -----------------------------------------------------------------
890 # Creates/Updates/Deletes a fleshed asset.copy.  
891 # adds/deletes copy stat_cat maps where necessary
892 # -----------------------------------------------------------------
893 sub _fleshed_copy_update {
894         my($session, $copy, $editor) = @_;
895
896         my $stat_cat_entries = $copy->stat_cat_entries;
897         $copy->editor($editor->id);
898         
899         # in case we're fleshed
900         if(ref($copy->status))          {$copy->status( $copy->status->id ); }
901         if(ref($copy->location))        {$copy->location( $copy->location->id ); }
902         if(ref($copy->circ_lib))        {$copy->circ_lib( $copy->circ_lib->id ); }
903
904         warn "Updating copy " . Dumper($copy) . "\n";
905
906         if( $copy->isdeleted ) { 
907                 return _delete_copy($session, $copy, $editor);
908         } elsif( $copy->isnew ) {
909                 $copy = _create_copy($session, $copy, $editor);
910         } elsif( $copy->ischanged ) {
911                 _update_copy($session, $copy, $editor);
912         }
913
914         
915         return 1 unless ( $stat_cat_entries and @$stat_cat_entries );
916
917         my $stat_maps = $session->request(
918                 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.search.owning_copy.atomic",
919                 $copy->id )->gather(1);
920
921         if(!$copy->isnew) { _delete_stale_maps($session, $stat_maps, $copy); }
922         
923         # go through the stat cat update/create process
924         for my $stat_entry (@{$stat_cat_entries}){ 
925                 _copy_update_stat_cats( $session, $copy, $stat_maps, $stat_entry, $editor );
926         }
927         
928         return 1;
929 }
930
931
932 # -----------------------------------------------------------------
933 # Deletes stat maps attached to this copy in the database that
934 # are no longer attached to the current copy
935 # -----------------------------------------------------------------
936 sub _delete_stale_maps {
937         my( $session, $stat_maps, $copy) = @_;
938
939         warn "Deleting stale stat maps for copy " . $copy->id . "\n";
940         for my $map (@$stat_maps) {
941         # if there is no stat cat entry on the copy who's id matches the
942         # current map's id, remove the map from the database
943         if(! grep { $_->id == $map->stat_cat_entry } @{$copy->stat_cat_entries} ) {
944                 my $req = $session->request(
945                         "open-ils.storage.direct.asset.stat_cat_entry_copy_map.delete", $map );
946                 $req->gather(1);
947                 }
948         }
949
950         return $stat_maps;
951 }
952
953
954 # -----------------------------------------------------------------
955 # Searches the stat maps to see if '$entry' already exists on
956 # the given copy.  If it does not, a new stat map is created
957 # for the given entry and copy
958 # -----------------------------------------------------------------
959 sub _copy_update_stat_cats {
960         my ( $session, $copy, $stat_maps, $entry, $editor ) = @_;
961
962         warn "Updating stat maps for copy " . $copy->id . "\n";
963
964         # see if this map already exists
965         for my $map (@$stat_maps) {
966                 if( $map->stat_cat_entry == $entry->id ) {return;}
967         }
968
969         warn "Creating new stat map for stat  " . 
970                 $entry->stat_cat . " and copy " . $copy->id . "\n";
971
972         # if not, create it
973         my $new_map = Fieldmapper::asset::stat_cat_entry_copy_map->new();
974
975         $new_map->stat_cat( $entry->stat_cat );
976         $new_map->stat_cat_entry( $entry->id );
977         $new_map->owning_copy( $copy->id );
978
979         warn "New map is " . Dumper($new_map) . "\n";
980
981         my $request = $session->request(
982                 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.create",
983                 $new_map );
984         my $status = $request->gather(1);
985         warn "created new map with id $status\n";
986
987 }
988
989
990
991
992 1;