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