1 use strict; use warnings;
2 package OpenILS::Application::Cat;
3 use OpenILS::Application::AppUtils;
4 use OpenILS::Application;
5 use OpenILS::Application::Cat::Merge;
6 use OpenILS::Application::Cat::Authority;
7 use OpenILS::Application::Cat::BibCommon;
8 use OpenILS::Application::Cat::AssetCommon;
9 use base qw/OpenILS::Application/;
10 use Time::HiRes qw(time);
11 use OpenSRF::EX qw(:try);
12 use OpenSRF::Utils::JSON;
13 use OpenILS::Utils::Fieldmapper;
15 use OpenILS::Const qw/:const/;
18 use Unicode::Normalize;
20 use OpenILS::Utils::CStoreEditor q/:funcs/;
22 use OpenSRF::Utils::SettingsClient;
23 use OpenSRF::Utils::Logger qw($logger);
24 use OpenSRF::AppSession;
26 my $U = "OpenILS::Application::AppUtils";
29 my $assetcom = 'OpenILS::Application::Cat::AssetCommon';
31 __PACKAGE__->register_method(
32 method => "retrieve_marc_template",
33 api_name => "open-ils.cat.biblio.marc_template.retrieve",
35 Returns a MARC 'record tree' based on a set of pre-defined templates.
36 Templates include : book
39 sub retrieve_marc_template {
40 my( $self, $client, $type ) = @_;
41 return $marctemplates{$type} if defined($marctemplates{$type});
42 $marctemplates{$type} = _load_marc_template($type);
43 return $marctemplates{$type};
46 __PACKAGE__->register_method(
47 method => 'fetch_marc_template_types',
48 api_name => 'open-ils.cat.marc_template.types.retrieve'
51 my $marc_template_files;
53 sub fetch_marc_template_types {
54 my( $self, $conn ) = @_;
55 __load_marc_templates();
56 return [ keys %$marc_template_files ];
59 sub __load_marc_templates {
60 return if $marc_template_files;
61 if(!$conf) { $conf = OpenSRF::Utils::SettingsClient->new; }
63 $marc_template_files = $conf->config_value(
64 "apps", "open-ils.cat","app_settings", "marctemplates" );
66 $logger->info("Loaded marc templates: " . Dumper($marc_template_files));
69 sub _load_marc_template {
72 __load_marc_templates();
74 my $template = $$marc_template_files{$type};
75 open( F, $template ) or
76 throw OpenSRF::EX::ERROR ("Unable to open MARC template file: $template : $@");
80 my $xml = join('', @xml);
82 return XML::LibXML->new->parse_string($xml)->documentElement->toString;
87 __PACKAGE__->register_method(
88 method => 'fetch_bib_sources',
89 api_name => 'open-ils.cat.bib_sources.retrieve.all');
91 sub fetch_bib_sources {
92 return OpenILS::Application::Cat::BibCommon->fetch_bib_sources();
95 __PACKAGE__->register_method(
96 method => "create_record_xml",
97 api_name => "open-ils.cat.biblio.record.xml.create.override",
98 signature => q/@see open-ils.cat.biblio.record.xml.create/);
100 __PACKAGE__->register_method(
101 method => "create_record_xml",
102 api_name => "open-ils.cat.biblio.record.xml.create",
104 Inserts a new biblio with the given XML
108 sub create_record_xml {
109 my( $self, $client, $login, $xml, $source, $oargs, $strip_grps ) = @_;
111 my $override = 1 if $self->api_name =~ /override/;
112 $oargs = { all => 1 } unless defined $oargs;
114 my( $user_obj, $evt ) = $U->checksesperm($login, 'CREATE_MARC');
117 $logger->activity("user ".$user_obj->id." creating new MARC record");
119 my $meth = $self->method_lookup("open-ils.cat.biblio.record.xml.import");
121 $meth = $self->method_lookup(
122 "open-ils.cat.biblio.record.xml.import.override") if $override;
124 my ($s) = $meth->run($login, $xml, $source, $oargs, $strip_grps);
130 __PACKAGE__->register_method(
131 method => "biblio_record_replace_marc",
132 api_name => "open-ils.cat.biblio.record.xml.update",
135 Updates the XML for a given biblio record.
136 This does not change any other aspect of the record entry
137 exception the XML, the editor, and the edit date.
138 @return The update record object
142 __PACKAGE__->register_method(
143 method => 'biblio_record_replace_marc',
144 api_name => 'open-ils.cat.biblio.record.marc.replace',
146 @param auth The authtoken
147 @param recid The record whose MARC we're replacing
148 @param newxml The new xml to use
152 __PACKAGE__->register_method(
153 method => 'biblio_record_replace_marc',
154 api_name => 'open-ils.cat.biblio.record.marc.replace.override',
155 signature => q/@see open-ils.cat.biblio.record.marc.replace/
158 sub biblio_record_replace_marc {
159 my( $self, $conn, $auth, $recid, $newxml, $source, $oargs, $strip_grps ) = @_;
160 my $e = new_editor(authtoken=>$auth, xact=>1);
161 return $e->die_event unless $e->checkauth;
162 return $e->die_event unless $e->allowed('UPDATE_MARC', $e->requestor->ws_ou);
164 my $fix_tcn = $self->api_name =~ /replace/o;
165 if($self->api_name =~ /override/o) {
166 $oargs = { all => 1 } unless defined $oargs;
171 my $res = OpenILS::Application::Cat::BibCommon->biblio_record_replace_marc(
172 $e, $recid, $newxml, $source, $fix_tcn, $oargs, $strip_grps);
174 $e->commit unless $U->event_code($res);
175 $U->create_events_for_hook('bre.edit', $res, $e->requestor->ws_ou) unless $U->event_code($res);;
180 __PACKAGE__->register_method(
181 method => "template_overlay_biblio_record_entry",
182 api_name => "open-ils.cat.biblio.record_entry.template_overlay",
185 Overlays biblio.record_entry MARC values
186 @param auth The authtoken
187 @param records The record ids to be updated by the template
188 @param template The overlay template
189 @return Stream of hashes record id in the key "record" and t or f for the success of the overlay operation in key "success"
193 sub template_overlay_biblio_record_entry {
194 my($self, $conn, $auth, $records, $template) = @_;
195 my $e = new_editor(authtoken=>$auth, xact=>1);
196 return $e->die_event unless $e->checkauth;
198 $records = [$records] if (!ref($records));
200 for my $rid ( @$records ) {
201 my $rec = $e->retrieve_biblio_record_entry($rid);
204 unless ($e->allowed('UPDATE_RECORD', $rec->owner, $rec)) {
205 $conn->respond({ record => $rid, success => 'f' });
209 my $success = $e->json_query(
210 { from => [ 'vandelay.template_overlay_bib_record', $template, $rid ] }
211 )->[0]->{'vandelay.template_overlay_bib_record'};
212 $U->create_events_for_hook('bre.edit', $rec, $e->requestor->ws_ou);
214 $conn->respond({ record => $rid, success => $success });
221 __PACKAGE__->register_method(
222 method => "template_overlay_container",
223 api_name => "open-ils.cat.container.template_overlay",
226 Overlays biblio.record_entry MARC values
227 @param auth The authtoken
228 @param container The container, um, containing the records to be updated by the template
229 @param template The overlay template, or nothing and the method will look for a negative bib id in the container
230 @return Stream of hashes record id in the key "record" and t or f for the success of the overlay operation in key "success"
234 __PACKAGE__->register_method(
235 method => "template_overlay_container",
236 api_name => "open-ils.cat.container.template_overlay.background",
239 Overlays biblio.record_entry MARC values
240 @param auth The authtoken
241 @param container The container, um, containing the records to be updated by the template
242 @param template The overlay template, or nothing and the method will look for a negative bib id in the container
243 @param options Hash of options; currently supports:
244 xact_per_record: Apply updates to each bib record within its own transaction.
245 @return Cache key to check for status of the container overlay
249 sub template_overlay_container {
250 my($self, $conn, $auth, $container, $template, $options) = @_;
252 my $xact_per_rec = $options->{xact_per_record};
254 my $e = new_editor(authtoken=>$auth, xact=>1);
255 return $e->die_event unless $e->checkauth;
257 my $actor = OpenSRF::AppSession->create('open-ils.actor') if ($self->api_name =~ /background$/);
259 my $items = $e->search_container_biblio_record_entry_bucket_item({ bucket => $container });
263 ($titem) = grep { $_->target_biblio_record_entry < 0 } @$items;
268 $items = [grep { $_->target_biblio_record_entry > 0 } @$items];
270 $template = $e->retrieve_biblio_record_entry( $titem->target_biblio_record_entry )->marc;
273 my $num_total = scalar(@$items);
275 my $num_succeeded = 0;
277 $conn->respond_complete(
278 $actor->request('open-ils.actor.anon_cache.set_value', $auth,
279 batch_edit_progress => {total => $num_total})->gather(1)
282 # read-only up to here.
283 $e->rollback if $xact_per_rec;
285 for my $item ( @$items ) {
286 $e->xact_begin if $xact_per_rec;
287 my $rec = $e->retrieve_biblio_record_entry($item->target_biblio_record_entry);
291 if ($e->allowed('UPDATE_RECORD', $rec->owner, $rec)) {
292 $success = $e->json_query(
293 { from => [ 'vandelay.template_overlay_bib_record', $template, $rec->id ] }
294 )->[0]->{'vandelay.template_overlay_bib_record'};
297 if ($success eq 'f') {
300 $U->create_events_for_hook('bre.edit', $rec, $e->requestor->ws_ou);
306 'open-ils.actor.anon_cache.set_value', $auth,
307 batch_edit_progress => {
309 succeeded => $num_succeeded,
310 failed => $num_failed
314 $conn->respond({ record => $rec->id, success => $success });
317 if ($success eq 't') {
318 unless ($e->delete_container_biblio_record_entry_bucket_item($item)) {
322 'open-ils.actor.anon_cache.set_value', $auth,
323 batch_edit_progress => {
327 succeeded => $num_succeeded,
328 failed => $num_failed,
333 return { complete => 1, success => 'f' };
337 $e->xact_commit if $xact_per_rec;
340 if ($titem && !$num_failed) {
341 $e->xact_begin if $xact_per_rec;
342 return $e->die_event unless ($e->delete_container_biblio_record_entry_bucket_item($titem));
343 $e->xact_commit if $xact_per_rec;
346 if ($xact_per_rec || $e->commit) {
349 'open-ils.actor.anon_cache.set_value', $auth,
350 batch_edit_progress => {
354 succeeded => $num_succeeded,
355 failed => $num_failed,
359 return { complete => 1, success => 't' };
364 'open-ils.actor.anon_cache.set_value', $auth,
365 batch_edit_progress => {
369 succeeded => $num_succeeded,
370 failed => $num_failed,
374 return { complete => 1, success => 'f' };
380 __PACKAGE__->register_method(
381 method => "update_biblio_record_entry",
382 api_name => "open-ils.cat.biblio.record_entry.update",
384 Updates a biblio.record_entry
385 @param auth The authtoken
386 @param record The record with updated values
387 @return 1 on success, Event on error.
391 sub update_biblio_record_entry {
392 my($self, $conn, $auth, $record) = @_;
393 my $e = new_editor(authtoken=>$auth, xact=>1);
394 return $e->die_event unless $e->checkauth;
395 return $e->die_event unless $e->allowed('UPDATE_RECORD');
396 $e->update_biblio_record_entry($record) or return $e->die_event;
398 $U->create_events_for_hook('bre.edit', $record, $e->requestor->ws_ou);
402 __PACKAGE__->register_method(
403 method => "undelete_biblio_record_entry",
404 api_name => "open-ils.cat.biblio.record_entry.undelete",
406 Un-deletes a record and sets active=true
407 @param auth The authtoken
408 @param record The record_id to ressurect
409 @return 1 on success, Event on error.
412 sub undelete_biblio_record_entry {
413 my($self, $conn, $auth, $record_id) = @_;
414 my $e = new_editor(authtoken=>$auth, xact=>1);
415 return $e->die_event unless $e->checkauth;
416 return $e->die_event unless $e->allowed('UPDATE_RECORD');
418 my $record = $e->retrieve_biblio_record_entry($record_id)
419 or return $e->die_event;
420 $record->deleted('f');
421 $record->active('t');
423 # Set the leader/05 to indicate that the record has been corrected/revised
424 my $marc = $record->marc();
425 $marc =~ s{(<leader>.{5}).}{$1c};
426 $record->marc($marc);
428 # no 2 non-deleted records can have the same tcn_value
429 my $existing = $e->search_biblio_record_entry(
431 tcn_value => $record->tcn_value,
432 id => {'!=' => $record_id}
434 return OpenILS::Event->new('TCN_EXISTS') if @$existing;
436 $e->update_biblio_record_entry($record) or return $e->die_event;
438 $U->create_events_for_hook('bre.edit', $record, $e->requestor->ws_ou);
443 __PACKAGE__->register_method(
444 method => "biblio_record_xml_import",
445 api_name => "open-ils.cat.biblio.record.xml.import.override",
446 signature => q/@see open-ils.cat.biblio.record.xml.import/);
448 __PACKAGE__->register_method(
449 method => "biblio_record_xml_import",
450 api_name => "open-ils.cat.biblio.record.xml.import",
451 notes => <<" NOTES");
452 Takes a marcxml record and imports the record into the database. In this
453 case, the marcxml record is assumed to be a complete record (i.e. valid
454 MARC). The title control number is taken from (whichever comes first)
455 tags 001, 039[ab], 020a, 022a, 010, 035a and whichever does not already exist
457 user_session must have IMPORT_MARC permissions
461 sub biblio_record_xml_import {
462 my( $self, $client, $authtoken, $xml, $source, $auto_tcn, $oargs, $strip_grps) = @_;
463 my $e = new_editor(xact=>1, authtoken=>$authtoken);
464 return $e->die_event unless $e->checkauth;
465 return $e->die_event unless $e->allowed('IMPORT_MARC', $e->requestor->ws_ou);
467 if ($self->api_name =~ /override/) {
468 $oargs = { all => 1 } unless defined $oargs;
472 my $record = OpenILS::Application::Cat::BibCommon->biblio_record_xml_import(
473 $e, $xml, $source, $auto_tcn, $oargs, $strip_grps);
475 return $record if $U->event_code($record);
482 __PACKAGE__->register_method(
483 method => "biblio_record_record_metadata",
484 api_name => "open-ils.cat.biblio.record.metadata.retrieve",
486 argc => 2, #(session_id, list of bre ids )
487 notes => "Returns a list of slim-downed bre objects based on the " .
491 sub biblio_record_record_metadata {
492 my( $self, $client, $authtoken, $ids ) = @_;
494 return [] unless $ids and @$ids;
496 my $editor = new_editor(authtoken => $authtoken);
497 return $editor->event unless $editor->checkauth;
498 return $editor->event unless $editor->allowed('VIEW_USER');
503 return $editor->event unless
504 my $rec = $editor->retrieve_biblio_record_entry($_);
505 $rec->creator($editor->retrieve_actor_user($rec->creator));
506 $rec->editor($editor->retrieve_actor_user($rec->editor));
507 $rec->attrs($U->get_bre_attrs([$rec->id], $editor)->{$rec->id});
508 $rec->clear_marc; # slim the record down
509 push( @results, $rec );
517 __PACKAGE__->register_method(
518 method => "biblio_record_marc_cn",
519 api_name => "open-ils.cat.biblio.record.marc_cn.retrieve",
520 argc => 1, #(bib id )
522 desc => 'Extracts call number candidates from a bibliographic record',
524 {desc => 'Record ID', type => 'number'},
525 {desc => '(Optional) Classification scheme ID', type => 'number'},
526 {desc => '(Optional) Context org unit ID for default classification lookup', type => 'number'},
529 return => {desc => 'Hash of candidate call numbers identified by tag' }
532 sub biblio_record_marc_cn {
533 my( $self, $client, $id, $class, $ctx_org_id ) = @_;
535 my $e = new_editor();
536 my $bre = $e->retrieve_biblio_record_entry($id);
537 my $marc = $bre->marc;
539 my $doc = XML::LibXML->new->parse_string($marc);
540 $doc->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
543 my $ctx_org = $ctx_org_id || $bre->owner || $U->get_org_tree->id; # root org
544 $class = $U->ou_ancestor_setting_value(
545 $ctx_org, 'cat.default_classification_scheme', $e);
551 # be sure the class ID provided exists.
552 my $cn_class = $e->retrieve_asset_call_number_class($class) or return $e->event;
553 @fields = split(/,/, $cn_class->field);
555 @fields = qw/050ab 055ab 060ab 070ab 080ab 082ab 086ab 088ab 090 092 096 098 099/;
558 # Get field/subfield combos based on acnc value; for example "050ab,055ab"
560 foreach my $field (@fields) {
561 my $tag = substr($field, 0, 3);
562 $logger->debug("Tag = $tag");
563 my @node = $doc->findnodes("//marc:datafield[\@tag='$tag']");
566 # Now parse the subfields and build up the subfield XPath
567 my @subfields = split(//, substr($field, 3));
569 # If they give us no subfields to parse, default to just the 'a'
573 my $xpath = 'marc:subfield[' . join(' or ', map { "\@code='$_'" } @subfields) . ']';
574 $logger->debug("xpath = $xpath");
576 # Find the contents of the specified subfields
577 foreach my $x (@node) {
578 # We can't use find($xpath)->to_literal_delimited here because older 2.x
579 # versions of the XML::LibXML module don't have to_literal_delimited().
582 map { $_->textContent } $x->findnodes($xpath)
584 push @res, {$tag => $cn} if ($cn);
591 __PACKAGE__->register_method(
592 method => 'autogen_barcodes',
593 api_name => "open-ils.cat.item.barcode.autogen",
595 desc => 'Returns N generated barcodes following a specified barcode.',
597 {desc => 'Authentication token', type => 'string'},
598 {desc => 'Barcode which the sequence should follow from', type => 'string'},
599 {desc => 'Number of barcodes to generate', type => 'number'},
600 {desc => 'Options hash. Currently you can pass in checkdigit : false to disable the use of checkdigits.'}
602 return => {desc => 'Array of generated barcodes'}
606 sub autogen_barcodes {
607 my( $self, $client, $auth, $barcode, $num_of_barcodes, $options ) = @_;
608 my $e = new_editor(authtoken => $auth);
609 return $e->event unless $e->checkauth;
610 return $e->event unless $e->allowed('UPDATE_COPY', $e->requestor->ws_ou);
613 my $barcode_text = '';
614 my $barcode_number = 0;
616 if ($barcode =~ /^(\D+)/) { $barcode_text = $1; }
617 if ($barcode =~ /(\d+)$/) { $barcode_number = $1; }
620 for (my $i = 1; $i <= $num_of_barcodes; $i++) {
621 my $calculated_barcode;
623 # default is to use checkdigits, so looking for an explicit false here
624 if (defined $$options{'checkdigit'} && ! $$options{'checkdigit'}) {
625 $calculated_barcode = $barcode_number + $i;
627 if ($barcode_number =~ /^\d{8}$/) {
628 $calculated_barcode = add_codabar_checkdigit($barcode_number + $i, 0);
629 } elsif ($barcode_number =~ /^\d{9}$/) {
630 $calculated_barcode = add_codabar_checkdigit($barcode_number + $i*10, 1); # strip last digit
631 } elsif ($barcode_number =~ /^\d{13}$/) {
632 $calculated_barcode = add_codabar_checkdigit($barcode_number + $i, 0);
633 } elsif ($barcode_number =~ /^\d{14}$/) {
634 $calculated_barcode = add_codabar_checkdigit($barcode_number + $i*10, 1); # strip last digit
636 $calculated_barcode = $barcode_number + $i;
639 push @res, $barcode_text . $calculated_barcode;
644 # Codabar doesn't define a checkdigit algorithm, but this one is typically used by libraries. gmcharlt++
645 sub add_codabar_checkdigit {
647 my $strip_last_digit = shift;
649 return $barcode if $barcode =~ /\D/;
650 $barcode = substr($barcode, 0, length($barcode)-1) if $strip_last_digit;
651 my @digits = split //, $barcode;
653 for (my $i = 1; $i < length($barcode); $i+=2) { # for a 13/14 digit barcode, would expect 1,3,5,7,9,11
654 $total += $digits[$i];
656 for (my $i = 0; $i < length($barcode); $i+=2) { # for a 13/14 digit barcode, would expect 0,2,4,6,8,10,12
657 $total += (2 * $digits[$i] >= 10) ? (2 * $digits[$i] - 9) : (2 * $digits[$i]);
659 my $remainder = $total % 10;
660 my $checkdigit = ($remainder == 0) ? $remainder : 10 - $remainder;
661 return $barcode . $checkdigit;
664 __PACKAGE__->register_method(
665 method => "orgs_for_title",
667 api_name => "open-ils.cat.actor.org_unit.retrieve_by_title"
671 my( $self, $client, $record_id ) = @_;
673 my $vols = $U->simple_scalar_request(
675 "open-ils.cstore.direct.asset.call_number.search.atomic",
676 { record => $record_id, deleted => 'f' });
678 my $orgs = { map {$_->owning_lib => 1 } @$vols };
679 return [ keys %$orgs ];
683 __PACKAGE__->register_method(
684 method => "retrieve_copies",
686 api_name => "open-ils.cat.asset.copy_tree.retrieve");
688 __PACKAGE__->register_method(
689 method => "retrieve_copies",
690 api_name => "open-ils.cat.asset.copy_tree.global.retrieve");
692 # user_session may be null/undef
693 sub retrieve_copies {
695 my( $self, $client, $user_session, $docid, @org_ids ) = @_;
697 if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
701 # grabbing copy trees should be available for everyone..
702 if(!@org_ids and $user_session) {
703 my($user_obj, $evt) = OpenILS::Application::AppUtils->checkses($user_session);
705 @org_ids = ($user_obj->home_ou);
708 # Create an editor that can be shared across all iterations of
709 # _build_volume_list(). Otherwise, .authoritative calls can result
710 # in creating too many cstore connections.
711 my $e = new_editor();
713 if( $self->api_name =~ /global/ ) {
714 return _build_volume_list($e, { record => $docid, deleted => 'f', label => { '<>' => '##URI##' } } );
719 for my $orgid (@org_ids) {
720 my $vols = _build_volume_list($e,
721 { record => $docid, owning_lib => $orgid, deleted => 'f', label => { '<>' => '##URI##' } } );
722 push( @all_vols, @$vols );
732 sub _build_volume_list {
734 my $search_hash = shift;
738 $search_hash->{deleted} = 'f';
740 my $vols = $e->search_asset_call_number([
744 flesh_fields => { acn => ['prefix','suffix','label_class'] },
745 'order_by' => { 'acn' => 'oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib' }
751 for my $volume (@$vols) {
753 my $copies = $e->search_asset_copy([
754 { call_number => $volume->id , deleted => 'f' },
760 bmp => { type => 'left' }
765 flesh_fields => { acp => ['stat_cat_entries','parts'] },
767 {'class' => 'bmp', 'field' => 'label_sortkey', 'transform' => 'oils_text_as_bytea'},
768 {'class' => 'bmp', 'field' => 'label', 'transform' => 'oils_text_as_bytea'},
769 {'class' => 'acp', 'field' => 'barcode'}
774 for my $c (@$copies) {
775 if( $c->status == OILS_COPY_STATUS_CHECKED_OUT ) {
777 $e->search_action_circulation(
779 { target_copy => $c->id },
781 order_by => { circ => 'xact_start desc' },
790 $volume->copies($copies);
791 push( @volumes, $volume );
794 #$session->disconnect();
800 __PACKAGE__->register_method(
801 method => "fleshed_copy_update",
802 api_name => "open-ils.cat.asset.copy.fleshed.batch.update",);
804 __PACKAGE__->register_method(
805 method => "fleshed_copy_update",
806 api_name => "open-ils.cat.asset.copy.fleshed.batch.update.override",);
809 sub fleshed_copy_update {
810 my( $self, $conn, $auth, $copies, $delete_stats, $oargs, $create_parts ) = @_;
811 return 1 unless ref $copies;
812 my( $reqr, $evt ) = $U->checkses($auth);
814 my $editor = new_editor(requestor => $reqr, xact => 1);
815 if ($self->api_name =~ /override/) {
816 $oargs = { all => 1 } unless defined $oargs;
820 my $retarget_holds = [];
821 $evt = OpenILS::Application::Cat::AssetCommon->update_fleshed_copies(
822 $editor, $oargs, undef, $copies, $delete_stats, $retarget_holds, undef, $create_parts);
825 $logger->info("fleshed copy update failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
831 $logger->info("fleshed copy update successfully updated ".scalar(@$copies)." copies");
832 reset_hold_list($auth, $retarget_holds);
837 sub reset_hold_list {
838 my($auth, $hold_ids) = @_;
839 return unless @$hold_ids;
840 $logger->info("reseting holds after copy status change: @$hold_ids");
841 my $ses = OpenSRF::AppSession->create('open-ils.circ');
842 $ses->request('open-ils.circ.hold.reset.batch', $auth, $hold_ids);
845 __PACKAGE__->register_method(
846 method => "transfer_copies_to_volume",
847 api_name => "open-ils.cat.transfer_copies_to_volume",
850 desc => 'Transfers specified copies to the specified call number, and changes Circ Lib to match the new Owning Lib.',
852 {desc => 'Authtoken', type => 'string'},
853 {desc => 'Call Number ID', type => 'number'},
854 {desc => 'Array of Copy IDs', type => 'array'},
857 return => {desc => '1 on success, Event on error'}
860 __PACKAGE__->register_method(
861 method => "transfer_copies_to_volume",
862 api_name => "open-ils.cat.transfer_copies_to_volume.override",);
864 sub transfer_copies_to_volume {
865 my( $self, $conn, $auth, $volume, $copies, $oargs ) = @_;
866 my $delete_stats = 1;
867 my $force_delete_empty_bib = undef;
868 my $create_parts = undef;
872 return 1 unless ref $copies;
873 my( $reqr, $evt ) = $U->checkses($auth);
875 my $editor = new_editor(requestor => $reqr, xact => 1);
876 if ($self->api_name =~ /override/) {
877 $oargs = { all => 1 } unless defined $oargs;
882 # does the volume exist? good, we also need its owning_lib later
883 my( $cn, $cn_evt ) = $U->fetch_callnumber( $volume, 0, $editor );
884 return $cn_evt if $cn_evt;
886 # flesh and munge the copies
887 my $fleshed_copies = [];
889 foreach my $copy_id ( @{ $copies } ) {
890 $copy = $editor->search_asset_copy([
891 { id => $copy_id , deleted => 'f' },
894 flesh_fields => { acp => ['parts', 'stat_cat_entries'] }
897 return OpenILS::Event->new('ASSET_COPY_NOT_FOUND') if !$copy;
898 $copy->call_number( $volume );
899 $copy->circ_lib( $cn->owning_lib() );
900 $copy->ischanged( 't' );
901 push @$fleshed_copies, $copy;
905 my $retarget_holds = [];
906 $evt = OpenILS::Application::Cat::AssetCommon->update_fleshed_copies(
907 $editor, $oargs, undef, $fleshed_copies, $delete_stats, $retarget_holds, $force_delete_empty_bib, $create_parts);
910 $logger->info("copy to volume transfer failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
915 # take care of the parts
916 for my $copy (@$fleshed_copies) {
917 my $parts = $copy->parts;
920 foreach my $part (@$parts) {
921 my $part_label = $part->label;
922 my $part_obj = $editor->search_biblio_monograph_part(
930 $part_obj = Fieldmapper::biblio::monograph_part->new();
931 $part_obj->label( $part_label );
932 $part_obj->record( $cn->record );
933 unless($editor->create_biblio_monograph_part($part_obj)) {
934 return $editor->die_event if $editor->die_event;
937 push @$part_objs, $part_obj;
939 $copy->parts( $part_objs );
941 $evt = OpenILS::Application::Cat::AssetCommon->update_copy_parts($editor, $copy, 1); #delete_parts=1
946 $logger->info("copy to volume transfer successfully updated ".scalar(@$copies)." copies");
947 reset_hold_list($auth, $retarget_holds);
952 __PACKAGE__->register_method(
953 method => 'in_db_merge',
954 api_name => 'open-ils.cat.biblio.records.merge',
956 Merges a group of records
957 @param auth The login session key
958 @param master The id of the record all other records should be merged into
959 @param records Array of records to be merged into the master record
960 @return 1 on success, Event on error.
965 my( $self, $conn, $auth, $master, $records ) = @_;
967 my $editor = new_editor( authtoken => $auth, xact => 1 );
968 return $editor->die_event unless $editor->checkauth;
969 return $editor->die_event unless $editor->allowed('MERGE_BIB_RECORDS'); # TODO see below about record ownership
972 for my $source ( @$records ) {
973 #XXX we actually /will/ want to check perms for master and sources after record ownership exists
975 # This stored proc (asset.merge_record_assets(target,source)) has the side effects of
976 # moving call_number, title-type (and some volume-type) hold_request and uri-mapping
977 # objects from the source record to the target record, so must be called from within
980 $count += $editor->json_query({
984 transform => 'asset.merge_record_assets',
990 where => { id => $master }
991 })->[0]->{count}; # count of objects moved, of all types
999 __PACKAGE__->register_method(
1000 method => 'in_db_auth_merge',
1001 api_name => 'open-ils.cat.authority.records.merge',
1003 Merges a group of authority records
1004 @param auth The login session key
1005 @param master The id of the record all other records should be merged into
1006 @param records Array of records to be merged into the master record
1007 @return 1 on success, Event on error.
1011 sub in_db_auth_merge {
1012 my( $self, $conn, $auth, $master, $records ) = @_;
1014 my $editor = new_editor( authtoken => $auth, xact => 1 );
1015 return $editor->die_event unless $editor->checkauth;
1016 return $editor->die_event unless $editor->allowed('MERGE_AUTH_RECORDS'); # TODO see below about record ownership
1019 for my $source ( @$records ) {
1020 $count += $editor->json_query({
1024 transform => 'authority.merge_records',
1030 where => { id => $master }
1031 })->[0]->{count}; # count of objects moved, of all types
1038 __PACKAGE__->register_method(
1039 method => 'calculate_marc_merge',
1040 api_name => 'open-ils.cat.merge.marc.per_profile',
1042 Calculate the result of merging one or more MARC records
1043 per the specified merge profile
1044 @param auth The login session key
1045 @param merge_profile ID of the record merge profile
1046 @param records Array of two or more MARCXML records to be
1047 merged. If two are supplied, the first
1048 is treated as the record to be overlaid,
1049 and the the incoming record that will
1050 overlay the first. If more than two are
1051 supplied, the first is treated as the
1052 record to be overlaid, and each following
1053 record in turn will be merged into that
1055 @return MARCXML string of the results of the merge
1058 __PACKAGE__->register_method(
1059 method => 'calculate_bib_marc_merge',
1060 api_name => 'open-ils.cat.merge.biblio.per_profile',
1062 Calculate the result of merging one or more bib records
1063 per the specified merge profile
1064 @param auth The login session key
1065 @param merge_profile ID of the record merge profile
1066 @param records Array of two or more bib record IDs of
1067 the bibs to be merged.
1068 @return MARCXML string of the results of the merge
1071 __PACKAGE__->register_method(
1072 method => 'calculate_authority_marc_merge',
1073 api_name => 'open-ils.cat.merge.authority.per_profile',
1075 Calculate the result of merging one or more authority records
1076 per the specified merge profile
1077 @param auth The login session key
1078 @param merge_profile ID of the record merge profile
1079 @param records Array of two or more bib record IDs of
1080 the bibs to be merged.
1081 @return MARCXML string of the results of the merge
1085 sub _handle_marc_merge {
1086 my ($e, $merge_profile_id, $records) = @_;
1088 my $result = shift @$records;
1089 foreach my $incoming (@$records) {
1090 my $response = $e->json_query({
1092 'vandelay.merge_record_xml_using_profile',
1097 return unless ref($response);
1098 $result = $response->[0]->{'vandelay.merge_record_xml_using_profile'};
1103 sub calculate_marc_merge {
1104 my( $self, $conn, $auth, $merge_profile_id, $records ) = @_;
1106 my $e = new_editor(authtoken=>$auth, xact=>1);
1107 return $e->die_event unless $e->checkauth;
1109 my $merge_profile = $e->retrieve_vandelay_merge_profile($merge_profile_id)
1110 or return $e->die_event;
1111 return $e->die_event unless ref($records) && @$records >= 2;
1113 return _handle_marc_merge($e, $merge_profile_id, $records)
1116 sub calculate_bib_marc_merge {
1117 my( $self, $conn, $auth, $merge_profile_id, $bib_ids ) = @_;
1119 my $e = new_editor(authtoken=>$auth, xact=>1);
1120 return $e->die_event unless $e->checkauth;
1122 my $merge_profile = $e->retrieve_vandelay_merge_profile($merge_profile_id)
1123 or return $e->die_event;
1124 return $e->die_event unless ref($bib_ids) && @$bib_ids >= 2;
1127 foreach my $id (@$bib_ids) {
1128 my $bre = $e->retrieve_biblio_record_entry($id) or return $e->die_event;
1129 push @$records, $bre->marc();
1132 return _handle_marc_merge($e, $merge_profile_id, $records)
1135 sub calculate_authority_marc_merge {
1136 my( $self, $conn, $auth, $merge_profile_id, $authority_ids ) = @_;
1138 my $e = new_editor(authtoken=>$auth, xact=>1);
1139 return $e->die_event unless $e->checkauth;
1141 my $merge_profile = $e->retrieve_vandelay_merge_profile($merge_profile_id)
1142 or return $e->die_event;
1143 return $e->die_event unless ref($authority_ids) && @$authority_ids >= 2;
1146 foreach my $id (@$authority_ids) {
1147 my $are = $e->retrieve_authority_record_entry($id) or return $e->die_event;
1148 push @$records, $are->marc();
1151 return _handle_marc_merge($e, $merge_profile_id, $records)
1154 __PACKAGE__->register_method(
1155 method => "fleshed_volume_update",
1156 api_name => "open-ils.cat.asset.volume.fleshed.batch.update",);
1158 __PACKAGE__->register_method(
1159 method => "fleshed_volume_update",
1160 api_name => "open-ils.cat.asset.volume.fleshed.batch.update.override",);
1162 sub fleshed_volume_update {
1163 my( $self, $conn, $auth, $volumes, $delete_stats, $options, $oargs ) = @_;
1164 my( $reqr, $evt ) = $U->checkses($auth);
1165 return $evt if $evt;
1168 if ($self->api_name =~ /override/) {
1169 $oargs = { all => 1 } unless defined $oargs;
1173 my $editor = new_editor( requestor => $reqr, xact => 1 );
1174 my $retarget_holds = [];
1175 my $auto_merge_vols = $options->{auto_merge_vols};
1176 my $create_parts = $options->{create_parts};
1179 for my $vol (@$volumes) {
1180 $logger->info("vol-update: investigating volume ".$vol->id);
1182 $vol->editor($reqr->id);
1183 $vol->edit_date('now');
1185 my $copies = $vol->copies;
1188 $vol->editor($editor->requestor->id);
1189 $vol->edit_date('now');
1191 if( $vol->isdeleted ) {
1193 $logger->info("vol-update: deleting volume");
1194 return $editor->die_event unless
1195 $editor->allowed('UPDATE_VOLUME', $vol->owning_lib);
1197 if(my $evt = $assetcom->delete_volume($editor, $vol, $oargs, $$options{force_delete_copies})) {
1202 return $editor->die_event unless
1203 $editor->update_asset_call_number($vol);
1205 } elsif( $vol->isnew ) {
1206 $logger->info("vol-update: creating volume");
1207 ($vol,$evt) = $assetcom->create_volume( $auto_merge_vols ? { all => 1} : $oargs, $editor, $vol );
1208 return $evt if $evt;
1210 } elsif( $vol->ischanged ) {
1211 $logger->info("vol-update: update volume");
1214 # 1) We're editing a volume, and not its copies.
1215 # 2) We're editing a volume, and a subset of its copies.
1216 # 3) We're editing a volume, and all of its copies.
1218 # For 1) and 3), we definitely want to edit the volume
1219 # itself (and possibly auto-merge), but for 2), we want
1220 # to create a new volume (and possibly auto-merge).
1222 if (scalar(@$copies) == 0) { # case 1
1224 my $resp = update_volume($vol, $editor, ($oargs->{all} or grep { $_ eq 'VOLUME_LABEL_EXISTS' } @{$oargs->{events}} or $auto_merge_vols));
1225 return $resp->{evt} if $resp->{evt};
1226 $vol = $resp->{merge_vol} if $resp->{merge_vol};
1230 my $resp = $editor->json_query({
1233 {transform => 'count', aggregate => 1, column => 'id', alias => 'count'}
1238 call_number => $vol->id,
1240 id => {'not in' => [ map { $_->id } @$copies ]}
1243 if ($resp->[0]->{count} && $resp->[0]->{count} > 0) { # case 2
1245 ($vol,$evt) = $assetcom->create_volume( $auto_merge_vols ? { all => 1} : $oargs, $editor, $vol );
1246 return $evt if $evt;
1250 my $resp = update_volume($vol, $editor, ($oargs->{all} or grep { $_ eq 'VOLUME_LABEL_EXISTS' } @{$oargs->{events}} or $auto_merge_vols));
1251 return $resp->{evt} if $resp->{evt};
1252 $vol = $resp->{merge_vol} if $resp->{merge_vol};
1258 # now update any attached copies
1259 if( $copies and @$copies and !$vol->isdeleted ) {
1260 $_->call_number($vol->id) for @$copies;
1261 $evt = $assetcom->update_fleshed_copies(
1262 $editor, $oargs, $vol, $copies, $delete_stats, $retarget_holds, undef, $create_parts);
1263 return $evt if $evt;
1264 push( @$copy_ids, $_->id ) for @$copies;
1269 reset_hold_list($auth, $retarget_holds);
1270 if ($options->{return_copy_ids}) {
1273 return scalar(@$volumes);
1281 my $auto_merge = shift;
1285 return {evt => $editor->event} unless
1286 $editor->allowed('UPDATE_VOLUME', $vol->owning_lib);
1288 return {evt => $evt}
1289 if ( $evt = OpenILS::Application::Cat::AssetCommon->org_cannot_have_vols($editor, $vol->owning_lib) );
1291 my $vols = $editor->search_asset_call_number({
1292 owning_lib => $vol->owning_lib,
1293 record => $vol->record,
1294 label => $vol->label,
1295 prefix => $vol->prefix,
1296 suffix => $vol->suffix,
1298 id => {'!=' => $vol->id}
1305 # If the auto-merge option is on, merge our updated volume into the existing
1306 # volume with the same record + owner + label.
1307 ($merge_vol, $evt) = OpenILS::Application::Cat::Merge::merge_volumes($editor, [$vol], $vols->[0]);
1308 return {evt => $evt, merge_vol => $merge_vol};
1311 return {evt => OpenILS::Event->new('VOLUME_LABEL_EXISTS', payload => $vol->id)};
1315 return {evt => $editor->die_event} unless $editor->update_asset_call_number($vol);
1321 __PACKAGE__->register_method (
1322 method => 'delete_bib_record',
1323 api_name => 'open-ils.cat.biblio.record_entry.delete');
1325 sub delete_bib_record {
1326 my($self, $conn, $auth, $rec_id) = @_;
1327 my $e = new_editor(xact=>1, authtoken=>$auth);
1328 return $e->die_event unless $e->checkauth;
1329 return $e->die_event unless $e->allowed('DELETE_RECORD', $e->requestor->ws_ou);
1330 my $vols = $e->search_asset_call_number({record=>$rec_id, deleted=>'f'});
1331 return OpenILS::Event->new('RECORD_NOT_EMPTY', payload=>$rec_id) if @$vols;
1332 my $acq_li_count = $e->json_query({
1333 select => {jub => [{column => 'id', transform => 'count'}]},
1337 eg_bib_id => $rec_id,
1338 state => ['new','pending-order','on-order']
1342 return OpenILS::Event->new('RECORD_REFERENCED_BY_LINEITEM', payload => $rec_id) if ($acq_li_count->{id} > 0);
1343 my $evt = OpenILS::Application::Cat::BibCommon->delete_rec($e, $rec_id);
1344 if($evt) { $e->rollback; return $evt; }
1351 __PACKAGE__->register_method (
1352 method => 'batch_volume_transfer',
1353 api_name => 'open-ils.cat.asset.volume.batch.transfer',
1356 __PACKAGE__->register_method (
1357 method => 'batch_volume_transfer',
1358 api_name => 'open-ils.cat.asset.volume.batch.transfer.override',
1362 sub batch_volume_transfer {
1363 my( $self, $conn, $auth, $args, $oargs ) = @_;
1366 my $rec = $$args{docid};
1367 my $o_lib = $$args{lib};
1368 my $vol_ids = $$args{volumes};
1370 my $override = 1 if $self->api_name =~ /override/;
1371 $oargs = { all => 1 } unless defined $oargs;
1373 $logger->info("merge: transferring volumes to lib=$o_lib and record=$rec");
1375 my $e = new_editor(authtoken => $auth, xact =>1);
1376 return $e->event unless $e->checkauth;
1377 return $e->event unless $e->allowed('UPDATE_VOLUME', $o_lib);
1379 my $dorg = $e->retrieve_actor_org_unit($o_lib)
1380 or return $e->event;
1382 my $ou_type = $e->retrieve_actor_org_unit_type($dorg->ou_type)
1383 or return $e->event;
1385 return $evt if ( $evt = OpenILS::Application::Cat::AssetCommon->org_cannot_have_vols($e, $o_lib) );
1387 my $vols = $e->batch_retrieve_asset_call_number($vol_ids);
1392 for my $vol (@$vols) {
1394 # if we've already looked at this volume, go to the next
1395 next if !$vol or grep { $vol->id == $_ } @seen;
1397 # grab all of the volumes in the list that have
1398 # the same label so they can be merged
1399 my @all = grep { $_->label eq $vol->label } @$vols;
1401 # take note of the fact that we've looked at this set of volumes
1402 push( @seen, $_->id ) for @all;
1403 push( @rec_ids, $_->record ) for @all;
1405 # for each volume, see if there are any copies that have a
1406 # remote circ_lib (circ_lib != vol->owning_lib and != $o_lib ).
1408 unless( $override && ($oargs->{all} || grep { $_ eq 'COPY_REMOTE_CIRC_LIB' } @{$oargs->{events}}) ) {
1411 $logger->debug("merge: searching for copies with remote circ_lib for volume ".$v->id);
1413 call_number => $v->id,
1414 circ_lib => { "not in" => [ $o_lib, $v->owning_lib ] },
1418 my $copies = $e->search_asset_copy($args, {idlist=>1});
1420 # if the copy's circ_lib matches the destination lib,
1422 return OpenILS::Event->new('COPY_REMOTE_CIRC_LIB') if @$copies;
1426 # record the difference between the destination bib and the present bib
1427 my $same_bib = $vol->record == $rec;
1429 # see if there is a volume at the destination lib that
1430 # already has the requested label
1431 my $existing_vol = $e->search_asset_call_number(
1433 label => $vol->label,
1434 prefix => $vol->prefix,
1435 suffix => $vol->suffix,
1437 owning_lib => $o_lib,
1442 if( $existing_vol ) {
1444 if( grep { $_->id == $existing_vol->id } @all ) {
1445 # this volume is already accounted for in our list of volumes to merge
1446 $existing_vol = undef;
1449 # this volume exists on the destination record/owning_lib and must
1450 # be used as the destination for merging
1451 $logger->debug("merge: volume already exists at destination record: ".
1452 $existing_vol->id.' : '.$existing_vol->label) if $existing_vol;
1456 if( @all > 1 || $existing_vol ) {
1457 $logger->info("merge: found collisions in volume transfer");
1458 my @args = ($e, \@all);
1459 @args = ($e, \@all, $existing_vol) if $existing_vol;
1460 ($vol, $evt) = OpenILS::Application::Cat::Merge::merge_volumes(@args);
1461 return $evt if $evt;
1464 if( !$existing_vol ) {
1466 $vol->owning_lib($o_lib);
1468 $vol->editor($e->requestor->id);
1469 $vol->edit_date('now');
1471 $logger->info("merge: updating volume ".$vol->id);
1472 $e->update_asset_call_number($vol) or return $e->event;
1475 $logger->info("merge: bypassing volume update because existing volume used as target");
1478 # regardless of what volume was used as the destination,
1479 # update any copies that have moved over to the new lib
1480 my $copies = $e->search_asset_copy([
1481 { call_number => $vol->id , deleted => 'f' },
1484 flesh_fields => { acp => ['parts'] }
1488 # update circ lib on the copies - make this a method flag?
1489 for my $copy (@$copies) {
1490 next if $copy->circ_lib == $o_lib;
1491 $logger->info("merge: transfer moving circ lib on copy ".$copy->id);
1492 $copy->circ_lib($o_lib);
1493 $copy->editor($e->requestor->id);
1494 $copy->edit_date('now');
1495 $e->update_asset_copy($copy) or return $e->event;
1498 # update parts if volume is moving bib records
1500 for my $copy (@$copies) {
1501 my $parts = $copy->parts;
1504 foreach my $part (@$parts) {
1505 my $part_label = $part->label;
1506 my $part_obj = $e->search_biblio_monograph_part(
1515 $part_obj = Fieldmapper::biblio::monograph_part->new();
1516 $part_obj->label( $part_label );
1517 $part_obj->record( $rec );
1518 unless($e->create_biblio_monograph_part($part_obj)) {
1519 return $e->die_event if $e->die_event;
1522 push @$part_objs, $part_obj;
1525 $copy->parts( $part_objs );
1526 $copy->ischanged(1);
1527 $evt = OpenILS::Application::Cat::AssetCommon->update_copy_parts($e, $copy, 1); #delete_parts=1
1528 return $evt if $evt;
1532 # Now see if any empty records need to be deleted after all of this
1533 my $keep_on_empty = $U->ou_ancestor_setting_value($e->requestor->ws_ou, 'cat.bib.keep_on_empty', $e);
1534 unless ($U->is_true($keep_on_empty)) {
1537 $logger->debug("merge: seeing if we should delete record $_...");
1538 if (OpenILS::Application::Cat::BibCommon->title_is_empty($e, $_)) {
1539 # check for any title holds on the bib, bail if so
1540 my $has_holds = OpenILS::Application::Cat::BibCommon->title_has_holds($e, $_);
1541 return OpenILS::Event->new('TITLE_HAS_HOLDS', payload => $_) if $has_holds;
1542 # we're good, delete the record
1543 $evt = OpenILS::Application::Cat::BibCommon->delete_rec($e, $_);
1544 return $evt if $evt;
1550 $logger->info("merge: transfer succeeded");
1558 __PACKAGE__->register_method(
1559 api_name => 'open-ils.cat.call_number.find_or_create',
1560 method => 'find_or_create_volume',
1563 sub find_or_create_volume {
1564 my( $self, $conn, $auth, $label, $record_id, $org_id, $prefix, $suffix, $label_class ) = @_;
1565 my $e = new_editor(authtoken=>$auth, xact=>1);
1566 return $e->die_event unless $e->checkauth;
1567 my ($vol, $evt, $exists) =
1568 OpenILS::Application::Cat::AssetCommon->find_or_create_volume($e, $label, $record_id, $org_id, $prefix, $suffix, $label_class);
1569 return $evt if $evt;
1570 $e->rollback if $exists;
1572 return { 'acn_id' => $vol->id, 'existed' => $exists };
1576 __PACKAGE__->register_method(
1577 method => "create_serial_record_xml",
1578 api_name => "open-ils.cat.serial.record.xml.create.override",
1579 signature => q/@see open-ils.cat.serial.record.xml.create/);
1581 __PACKAGE__->register_method(
1582 method => "create_serial_record_xml",
1583 api_name => "open-ils.cat.serial.record.xml.create",
1585 Inserts a new serial record with the given XML
1589 sub create_serial_record_xml {
1590 my( $self, $client, $login, $source, $owning_lib, $record_id, $xml, $oargs ) = @_;
1592 my $override = 1 if $self->api_name =~ /override/; # not currently used
1593 $oargs = { all => 1 } unless defined $oargs; # Not currently used, but here for consistency.
1595 my $e = new_editor(xact=>1, authtoken=>$login);
1596 return $e->die_event unless $e->checkauth;
1597 return $e->die_event unless $e->allowed('CREATE_MFHD_RECORD', $owning_lib);
1599 # Auto-populate the location field of a placeholder MFHD record with the library name
1600 my $aou = $e->retrieve_actor_org_unit($owning_lib) or return $e->die_event;
1602 my $mfhd = Fieldmapper::serial::record_entry->new;
1604 $mfhd->source($source) if $source;
1605 $mfhd->record($record_id);
1606 $mfhd->creator($e->requestor->id);
1607 $mfhd->editor($e->requestor->id);
1608 $mfhd->create_date('now');
1609 $mfhd->edit_date('now');
1610 $mfhd->owning_lib($owning_lib);
1612 # If the caller did not pass in MFHD XML, create a placeholder record.
1613 # The placeholder will only contain the name of the owning library.
1614 # The goal is to generate common patterns for the caller in the UI that
1615 # then get passed in here.
1617 my $aou_name = $aou->name;
1620 xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
1621 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1622 xmlns="http://www.loc.gov/MARC21/slim">
1623 <leader>00307ny a22001094 4500</leader>
1624 <controlfield tag="001">42153</controlfield>
1625 <controlfield tag="005">20090601182414.0</controlfield>
1626 <controlfield tag="004">$record_id</controlfield>
1627 <controlfield tag="008"> 4u####8###l# 4 uueng1 </controlfield>
1628 <datafield tag="852" ind1=" " ind2=" "> <subfield code="b">$aou_name</subfield></datafield>
1632 my $marcxml = XML::LibXML->new->parse_string($xml);
1633 $marcxml->documentElement->setNamespace("http://www.loc.gov/MARC21/slim", "marc", 1 );
1634 $marcxml->documentElement->setNamespace("http://www.loc.gov/MARC21/slim");
1636 $mfhd->marc($U->entityize($marcxml->documentElement->toString));
1638 $e->create_serial_record_entry($mfhd) or return $e->die_event;
1644 __PACKAGE__->register_method(
1645 method => "create_update_asset_copy_template",
1646 api_name => "open-ils.cat.asset.copy_template.create_or_update"
1649 sub create_update_asset_copy_template {
1650 my ($self, $client, $authtoken, $act) = @_;
1652 my $e = new_editor("xact" => 1, "authtoken" => $authtoken);
1653 return $e->die_event unless $e->checkauth;
1654 return $e->die_event unless $e->allowed(
1655 "ADMIN_ASSET_COPY_TEMPLATE", $act->owning_lib
1658 $act->editor($e->requestor->id);
1659 $act->edit_date("now");
1663 $act->creator($e->requestor->id);
1664 $act->create_date("now");
1666 $e->create_asset_copy_template($act) or return $e->die_event;
1669 $e->update_asset_copy_template($act) or return $e->die_event;
1670 $retval = $e->retrieve_asset_copy_template($e->data);
1672 $e->commit and return $retval;
1675 __PACKAGE__->register_method(
1676 method => "acn_sms_msg",
1677 api_name => "open-ils.cat.acn.send_sms_text",
1679 Send an SMS text from an A/T template for specified call numbers.
1681 First parameter is null or an auth token (whether a null is allowed
1682 depends on the sms.disable_authentication_requirement.callnumbers OU
1685 Second parameter is the id of the context org.
1687 Third parameter is the code of the SMS carrier from the
1688 config.sms_carrier table.
1690 Fourth parameter is the SMS number.
1692 Fifth parameter is the ACN id's to target, though currently only the
1693 first ACN is used by the template (and the UI is only sending one).
1698 my($self, $conn, $auth, $org_id, $carrier, $number, $target_ids) = @_;
1700 my $sms_enable = $U->ou_ancestor_setting_value(
1701 $org_id || $U->get_org_tree->id,
1704 # We could maybe make a Validator for this on the templates
1705 if (! $U->is_true($sms_enable)) {
1709 my $disable_auth = $U->ou_ancestor_setting_value(
1710 $org_id || $U->get_org_tree->id,
1711 'sms.disable_authentication_requirement.callnumbers'
1716 ? (authtoken => $auth, xact => 1)
1719 return $e->event unless $disable_auth || $e->checkauth;
1721 my $targets = $e->batch_retrieve_asset_call_number($target_ids);
1723 $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
1724 # simply making this method authoritative because of weirdness
1725 # with transaction handling in A/T code that causes rollback
1726 # failure down the line if handling many targets
1728 return undef unless @$targets;
1729 return $U->fire_object_event(
1731 'acn.format.sms_text', # hook
1734 undef, # granularity
1736 sms_carrier => $carrier,
1737 sms_notify => $number
1744 __PACKAGE__->register_method(
1745 method => "fixed_field_values_by_rec_type",
1746 api_name => "open-ils.cat.biblio.fixed_field_values.by_rec_type",
1749 desc => 'Given a record type (as in cmfpm.rec_type), return fixed fields and their possible values as known to the DB',
1751 {desc => 'Record Type', type => 'string'},
1752 {desc => '(Optional) Fixed field', type => 'string'},
1755 return => {desc => 'an object in which the keys are fixed fields and the values are arrays representing the set of all unique values for that fixed field in that record type', type => 'object' }
1759 sub fixed_field_values_by_rec_type {
1760 my ($self, $conn, $rec_type, $fixed_field) = @_;
1763 my $values = $e->json_query({
1765 crad => ["fixed_field"],
1766 ccvm => [qw/code value/],
1767 cmfpm => [qw/length default_val/],
1775 fkey => "fixed_field",
1776 field => "fixed_field"
1783 "+cmfpm" => {rec_type => $rec_type},
1784 defined $fixed_field ?
1785 ("+crad" => {fixed_field => $fixed_field}) : ()
1788 {class => "crad", field => "fixed_field"},
1789 {class => "ccvm", field => "code"}
1791 }) or return $e->die_event;
1794 for my $row (@$values) {
1795 $result->{$row->{fixed_field}} ||= [];
1796 push @{$result->{$row->{fixed_field}}}, [@$row{qw/code value length default_val/}];
1802 __PACKAGE__->register_method(
1803 method => "retrieve_tag_table",
1804 api_name => "open-ils.cat.tag_table.all.retrieve.local",
1808 desc => "Retrieve set of MARC tags, subfields, and indicator values for the user's OU",
1810 {desc => 'Authtoken', type => 'string'},
1811 {desc => 'MARC Format', type => 'string'},
1812 {desc => 'MARC Record Type', type => 'string'},
1815 return => {desc => 'Structure representing the tag table available to that user', type => 'object' }
1817 __PACKAGE__->register_method(
1818 method => "retrieve_tag_table",
1819 api_name => "open-ils.cat.tag_table.all.retrieve.stock",
1823 desc => 'Retrieve set of MARC tags, subfields, and indicator values for stock MARC standard',
1825 {desc => 'Authtoken', type => 'string'},
1826 {desc => 'MARC Format', type => 'string'},
1827 {desc => 'MARC Record Type', type => 'string'},
1830 return => {desc => 'Structure representing the stock tag table', type => 'object' }
1832 __PACKAGE__->register_method(
1833 method => "retrieve_tag_table",
1834 api_name => "open-ils.cat.tag_table.field_list.retrieve.local",
1838 desc => "Retrieve set of MARC tags for available to the user's OU",
1840 {desc => 'Authtoken', type => 'string'},
1841 {desc => 'MARC Format', type => 'string'},
1842 {desc => 'MARC Record Type', type => 'string'},
1845 return => {desc => 'Structure representing the tags available to that user', type => 'object' }
1847 __PACKAGE__->register_method(
1848 method => "retrieve_tag_table",
1849 api_name => "open-ils.cat.tag_table.field_list.retrieve.stock",
1853 desc => 'Retrieve set of MARC tags for stock MARC standard',
1855 {desc => 'Authtoken', type => 'string'},
1856 {desc => 'MARC Format', type => 'string'},
1857 {desc => 'MARC Record Type', type => 'string'},
1860 return => {desc => 'Structure representing the stock MARC tags', type => 'object' }
1863 sub retrieve_tag_table {
1864 my( $self, $conn, $auth, $marc_format, $marc_record_type ) = @_;
1865 my $e = new_editor( authtoken=>$auth, xact=>1 );
1866 return $e->die_event unless $e->checkauth;
1868 my $field_list_only = ($self->api_name =~ /\.field_list\./) ? 1 : 0;
1870 if ($self->api_name =~ /\.local$/) {
1871 $context_ou = $e->requestor->ws_ou;
1875 unless ($field_list_only) {
1876 my $subfields = $e->json_query(
1877 { from => [ 'config.ou_marc_subfields', 1, $marc_record_type, $context_ou ] }
1879 foreach my $sf (@$subfields) {
1881 code => $sf->{code},
1882 description => $sf->{description},
1883 mandatory => $sf->{mandatory},
1884 repeatable => $sf->{repeatable},
1886 if ($sf->{value_ctype}) {
1887 $sf_data->{value_list} = $e->json_query({
1888 select => { ccvm => [
1890 { column => 'value', alias => 'description' }
1894 where => { ctype => $sf->{value_ctype} },
1895 order_by => { ccvm => { code => {} } },
1898 push @{ $sf_by_tag{$sf->{tag}} }, $sf_data;
1902 my $fields = $e->json_query(
1903 { from => [ 'config.ou_marc_fields', 1, $marc_record_type, $context_ou ] }
1906 foreach my $field (@$fields) {
1907 next if $field->{hidden} eq 't';
1908 unless ($field_list_only) {
1909 my $tag = $field->{tag};
1910 if ($tag ge '010') {
1911 for my $pos (1..2) {
1912 my $ind_ccvm_key = "${marc_format}_${marc_record_type}_${tag}_ind_${pos}";
1913 my $indvals = $e->json_query({
1914 select => { ccvm => [
1916 { column => 'value', alias => 'description' }
1920 where => { ctype => $ind_ccvm_key }
1922 next unless defined($indvals);
1923 $field->{"ind$pos"} = $indvals;
1925 $field->{subfields} = exists($sf_by_tag{$tag}) ? $sf_by_tag{$tag} : [];
1928 $conn->respond($field);