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