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);
179 __PACKAGE__->register_method(
180 method => "template_overlay_biblio_record_entry",
181 api_name => "open-ils.cat.biblio.record_entry.template_overlay",
184 Overlays biblio.record_entry MARC values
185 @param auth The authtoken
186 @param records The record ids to be updated by the template
187 @param template The overlay template
188 @return Stream of hashes record id in the key "record" and t or f for the success of the overlay operation in key "success"
192 sub template_overlay_biblio_record_entry {
193 my($self, $conn, $auth, $records, $template) = @_;
194 my $e = new_editor(authtoken=>$auth, xact=>1);
195 return $e->die_event unless $e->checkauth;
197 $records = [$records] if (!ref($records));
199 for my $rid ( @$records ) {
200 my $rec = $e->retrieve_biblio_record_entry($rid);
203 unless ($e->allowed('UPDATE_RECORD', $rec->owner, $rec)) {
204 $conn->respond({ record => $rid, success => 'f' });
208 my $success = $e->json_query(
209 { from => [ 'vandelay.template_overlay_bib_record', $template, $rid ] }
210 )->[0]->{'vandelay.template_overlay_bib_record'};
212 $conn->respond({ record => $rid, success => $success });
219 __PACKAGE__->register_method(
220 method => "template_overlay_container",
221 api_name => "open-ils.cat.container.template_overlay",
224 Overlays biblio.record_entry MARC values
225 @param auth The authtoken
226 @param container The container, um, containing the records to be updated by the template
227 @param template The overlay template, or nothing and the method will look for a negative bib id in the container
228 @return Stream of hashes record id in the key "record" and t or f for the success of the overlay operation in key "success"
232 __PACKAGE__->register_method(
233 method => "template_overlay_container",
234 api_name => "open-ils.cat.container.template_overlay.background",
237 Overlays biblio.record_entry MARC values
238 @param auth The authtoken
239 @param container The container, um, containing the records to be updated by the template
240 @param template The overlay template, or nothing and the method will look for a negative bib id in the container
241 @return Cache key to check for status of the container overlay
245 sub template_overlay_container {
246 my($self, $conn, $auth, $container, $template) = @_;
247 my $e = new_editor(authtoken=>$auth, xact=>1);
248 return $e->die_event unless $e->checkauth;
250 my $actor = OpenSRF::AppSession->create('open-ils.actor') if ($self->api_name =~ /background$/);
252 my $items = $e->search_container_biblio_record_entry_bucket_item({ bucket => $container });
256 ($titem) = grep { $_->target_biblio_record_entry < 0 } @$items;
261 $items = [grep { $_->target_biblio_record_entry > 0 } @$items];
263 $template = $e->retrieve_biblio_record_entry( $titem->target_biblio_record_entry )->marc;
267 my $num_succeeded = 0;
269 $conn->respond_complete(
270 $actor->request('open-ils.actor.anon_cache.set_value', $auth, batch_edit_progress => {})->gather(1)
273 for my $item ( @$items ) {
274 my $rec = $e->retrieve_biblio_record_entry($item->target_biblio_record_entry);
278 if ($e->allowed('UPDATE_RECORD', $rec->owner, $rec)) {
279 $success = $e->json_query(
280 { from => [ 'vandelay.template_overlay_bib_record', $template, $rec->id ] }
281 )->[0]->{'vandelay.template_overlay_bib_record'};
284 if ($success eq 'f') {
292 'open-ils.actor.anon_cache.set_value', $auth,
293 batch_edit_progress => {
294 succeeded => $num_succeeded,
295 failed => $num_failed
299 $conn->respond({ record => $rec->id, success => $success });
302 if ($success eq 't') {
303 unless ($e->delete_container_biblio_record_entry_bucket_item($item)) {
307 'open-ils.actor.anon_cache.set_value', $auth,
308 batch_edit_progress => {
311 succeeded => $num_succeeded,
312 failed => $num_failed,
317 return { complete => 1, success => 'f' };
323 if ($titem && !$num_failed) {
324 return $e->die_event unless ($e->delete_container_biblio_record_entry_bucket_item($titem));
330 'open-ils.actor.anon_cache.set_value', $auth,
331 batch_edit_progress => {
334 succeeded => $num_succeeded,
335 failed => $num_failed,
339 return { complete => 1, success => 't' };
344 'open-ils.actor.anon_cache.set_value', $auth,
345 batch_edit_progress => {
348 succeeded => $num_succeeded,
349 failed => $num_failed,
353 return { complete => 1, success => 'f' };
359 __PACKAGE__->register_method(
360 method => "update_biblio_record_entry",
361 api_name => "open-ils.cat.biblio.record_entry.update",
363 Updates a biblio.record_entry
364 @param auth The authtoken
365 @param record The record with updated values
366 @return 1 on success, Event on error.
370 sub update_biblio_record_entry {
371 my($self, $conn, $auth, $record) = @_;
372 my $e = new_editor(authtoken=>$auth, xact=>1);
373 return $e->die_event unless $e->checkauth;
374 return $e->die_event unless $e->allowed('UPDATE_RECORD');
375 $e->update_biblio_record_entry($record) or return $e->die_event;
380 __PACKAGE__->register_method(
381 method => "undelete_biblio_record_entry",
382 api_name => "open-ils.cat.biblio.record_entry.undelete",
384 Un-deletes a record and sets active=true
385 @param auth The authtoken
386 @param record The record_id to ressurect
387 @return 1 on success, Event on error.
390 sub undelete_biblio_record_entry {
391 my($self, $conn, $auth, $record_id) = @_;
392 my $e = new_editor(authtoken=>$auth, xact=>1);
393 return $e->die_event unless $e->checkauth;
394 return $e->die_event unless $e->allowed('UPDATE_RECORD');
396 my $record = $e->retrieve_biblio_record_entry($record_id)
397 or return $e->die_event;
398 $record->deleted('f');
399 $record->active('t');
401 # Set the leader/05 to indicate that the record has been corrected/revised
402 my $marc = $record->marc();
403 $marc =~ s{(<leader>.{5}).}{$1c};
404 $record->marc($marc);
406 # no 2 non-deleted records can have the same tcn_value
407 my $existing = $e->search_biblio_record_entry(
409 tcn_value => $record->tcn_value,
410 id => {'!=' => $record_id}
412 return OpenILS::Event->new('TCN_EXISTS') if @$existing;
414 $e->update_biblio_record_entry($record) or return $e->die_event;
420 __PACKAGE__->register_method(
421 method => "biblio_record_xml_import",
422 api_name => "open-ils.cat.biblio.record.xml.import.override",
423 signature => q/@see open-ils.cat.biblio.record.xml.import/);
425 __PACKAGE__->register_method(
426 method => "biblio_record_xml_import",
427 api_name => "open-ils.cat.biblio.record.xml.import",
428 notes => <<" NOTES");
429 Takes a marcxml record and imports the record into the database. In this
430 case, the marcxml record is assumed to be a complete record (i.e. valid
431 MARC). The title control number is taken from (whichever comes first)
432 tags 001, 039[ab], 020a, 022a, 010, 035a and whichever does not already exist
434 user_session must have IMPORT_MARC permissions
438 sub biblio_record_xml_import {
439 my( $self, $client, $authtoken, $xml, $source, $auto_tcn, $oargs, $strip_grps) = @_;
440 my $e = new_editor(xact=>1, authtoken=>$authtoken);
441 return $e->die_event unless $e->checkauth;
442 return $e->die_event unless $e->allowed('IMPORT_MARC', $e->requestor->ws_ou);
444 if ($self->api_name =~ /override/) {
445 $oargs = { all => 1 } unless defined $oargs;
449 my $record = OpenILS::Application::Cat::BibCommon->biblio_record_xml_import(
450 $e, $xml, $source, $auto_tcn, $oargs, $strip_grps);
452 return $record if $U->event_code($record);
459 __PACKAGE__->register_method(
460 method => "biblio_record_record_metadata",
461 api_name => "open-ils.cat.biblio.record.metadata.retrieve",
463 argc => 2, #(session_id, list of bre ids )
464 notes => "Returns a list of slim-downed bre objects based on the " .
468 sub biblio_record_record_metadata {
469 my( $self, $client, $authtoken, $ids ) = @_;
471 return [] unless $ids and @$ids;
473 my $editor = new_editor(authtoken => $authtoken);
474 return $editor->event unless $editor->checkauth;
475 return $editor->event unless $editor->allowed('VIEW_USER');
480 return $editor->event unless
481 my $rec = $editor->retrieve_biblio_record_entry($_);
482 $rec->creator($editor->retrieve_actor_user($rec->creator));
483 $rec->editor($editor->retrieve_actor_user($rec->editor));
484 $rec->attrs($U->get_bre_attrs([$rec->id], $editor)->{$rec->id});
485 $rec->clear_marc; # slim the record down
486 push( @results, $rec );
494 __PACKAGE__->register_method(
495 method => "biblio_record_marc_cn",
496 api_name => "open-ils.cat.biblio.record.marc_cn.retrieve",
497 argc => 1, #(bib id )
499 desc => 'Extracts call number candidates from a bibliographic record',
501 {desc => 'Record ID', type => 'number'},
502 {desc => '(Optional) Classification scheme ID', type => 'number'},
505 return => {desc => 'Hash of candidate call numbers identified by tag' }
508 sub biblio_record_marc_cn {
509 my( $self, $client, $id, $class ) = @_;
511 my $e = new_editor();
512 my $marc = $e->retrieve_biblio_record_entry($id)->marc;
514 my $doc = XML::LibXML->new->parse_string($marc);
515 $doc->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
520 @fields = split(/,/, $e->retrieve_asset_call_number_class($class)->field);
522 @fields = qw/050ab 055ab 060ab 070ab 080ab 082ab 086ab 088ab 090 092 096 098 099/;
525 # Get field/subfield combos based on acnc value; for example "050ab,055ab"
527 foreach my $field (@fields) {
528 my $tag = substr($field, 0, 3);
529 $logger->debug("Tag = $tag");
530 my @node = $doc->findnodes("//marc:datafield[\@tag='$tag']");
532 # Now parse the subfields and build up the subfield XPath
533 my @subfields = split(//, substr($field, 3));
535 # If they give us no subfields to parse, default to just the 'a'
540 foreach my $sf (@subfields) {
541 $subxpath .= "\@code='$sf' or ";
543 $subxpath = substr($subxpath, 0, -4);
544 $logger->debug("subxpath = $subxpath");
546 # Find the contents of the specified subfields
547 foreach my $x (@node) {
548 my $cn = $x->findvalue("marc:subfield[$subxpath]");
549 push @res, {$tag => $cn} if ($cn);
556 __PACKAGE__->register_method(
557 method => 'autogen_barcodes',
558 api_name => "open-ils.cat.item.barcode.autogen",
560 desc => 'Returns N generated barcodes following a specified barcode.',
562 {desc => 'Authentication token', type => 'string'},
563 {desc => 'Barcode which the sequence should follow from', type => 'string'},
564 {desc => 'Number of barcodes to generate', type => 'number'},
565 {desc => 'Options hash. Currently you can pass in checkdigit : false to disable the use of checkdigits.'}
567 return => {desc => 'Array of generated barcodes'}
571 sub autogen_barcodes {
572 my( $self, $client, $auth, $barcode, $num_of_barcodes, $options ) = @_;
573 my $e = new_editor(authtoken => $auth);
574 return $e->event unless $e->checkauth;
575 return $e->event unless $e->allowed('UPDATE_COPY', $e->requestor->ws_ou);
578 my $barcode_text = '';
579 my $barcode_number = 0;
581 if ($barcode =~ /^(\D+)/) { $barcode_text = $1; }
582 if ($barcode =~ /(\d+)$/) { $barcode_number = $1; }
585 for (my $i = 1; $i <= $num_of_barcodes; $i++) {
586 my $calculated_barcode;
588 # default is to use checkdigits, so looking for an explicit false here
589 if (defined $$options{'checkdigit'} && ! $$options{'checkdigit'}) {
590 $calculated_barcode = $barcode_number + $i;
592 if ($barcode_number =~ /^\d{8}$/) {
593 $calculated_barcode = add_codabar_checkdigit($barcode_number + $i, 0);
594 } elsif ($barcode_number =~ /^\d{9}$/) {
595 $calculated_barcode = add_codabar_checkdigit($barcode_number + $i*10, 1); # strip last digit
596 } elsif ($barcode_number =~ /^\d{13}$/) {
597 $calculated_barcode = add_codabar_checkdigit($barcode_number + $i, 0);
598 } elsif ($barcode_number =~ /^\d{14}$/) {
599 $calculated_barcode = add_codabar_checkdigit($barcode_number + $i*10, 1); # strip last digit
601 $calculated_barcode = $barcode_number + $i;
604 push @res, $barcode_text . $calculated_barcode;
609 # Codabar doesn't define a checkdigit algorithm, but this one is typically used by libraries. gmcharlt++
610 sub add_codabar_checkdigit {
612 my $strip_last_digit = shift;
614 return $barcode if $barcode =~ /\D/;
615 $barcode = substr($barcode, 0, length($barcode)-1) if $strip_last_digit;
616 my @digits = split //, $barcode;
618 for (my $i = 1; $i < length($barcode); $i+=2) { # for a 13/14 digit barcode, would expect 1,3,5,7,9,11
619 $total += $digits[$i];
621 for (my $i = 0; $i < length($barcode); $i+=2) { # for a 13/14 digit barcode, would expect 0,2,4,6,8,10,12
622 $total += (2 * $digits[$i] >= 10) ? (2 * $digits[$i] - 9) : (2 * $digits[$i]);
624 my $remainder = $total % 10;
625 my $checkdigit = ($remainder == 0) ? $remainder : 10 - $remainder;
626 return $barcode . $checkdigit;
629 __PACKAGE__->register_method(
630 method => "orgs_for_title",
632 api_name => "open-ils.cat.actor.org_unit.retrieve_by_title"
636 my( $self, $client, $record_id ) = @_;
638 my $vols = $U->simple_scalar_request(
640 "open-ils.cstore.direct.asset.call_number.search.atomic",
641 { record => $record_id, deleted => 'f' });
643 my $orgs = { map {$_->owning_lib => 1 } @$vols };
644 return [ keys %$orgs ];
648 __PACKAGE__->register_method(
649 method => "retrieve_copies",
651 api_name => "open-ils.cat.asset.copy_tree.retrieve");
653 __PACKAGE__->register_method(
654 method => "retrieve_copies",
655 api_name => "open-ils.cat.asset.copy_tree.global.retrieve");
657 # user_session may be null/undef
658 sub retrieve_copies {
660 my( $self, $client, $user_session, $docid, @org_ids ) = @_;
662 if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
666 # grabbing copy trees should be available for everyone..
667 if(!@org_ids and $user_session) {
668 my($user_obj, $evt) = OpenILS::Application::AppUtils->checkses($user_session);
670 @org_ids = ($user_obj->home_ou);
673 # Create an editor that can be shared across all iterations of
674 # _build_volume_list(). Otherwise, .authoritative calls can result
675 # in creating too many cstore connections.
676 my $e = new_editor();
678 if( $self->api_name =~ /global/ ) {
679 return _build_volume_list($e, { record => $docid, deleted => 'f', label => { '<>' => '##URI##' } } );
684 for my $orgid (@org_ids) {
685 my $vols = _build_volume_list($e,
686 { record => $docid, owning_lib => $orgid, deleted => 'f', label => { '<>' => '##URI##' } } );
687 push( @all_vols, @$vols );
697 sub _build_volume_list {
699 my $search_hash = shift;
703 $search_hash->{deleted} = 'f';
705 my $vols = $e->search_asset_call_number([
709 flesh_fields => { acn => ['prefix','suffix','label_class'] },
710 'order_by' => { 'acn' => 'oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib' }
716 for my $volume (@$vols) {
718 my $copies = $e->search_asset_copy([
719 { call_number => $volume->id , deleted => 'f' },
725 bmp => { type => 'left' }
730 flesh_fields => { acp => ['stat_cat_entries','parts'] },
732 {'class' => 'bmp', 'field' => 'label_sortkey', 'transform' => 'oils_text_as_bytea'},
733 {'class' => 'bmp', 'field' => 'label', 'transform' => 'oils_text_as_bytea'},
734 {'class' => 'acp', 'field' => 'barcode'}
739 for my $c (@$copies) {
740 if( $c->status == OILS_COPY_STATUS_CHECKED_OUT ) {
742 $e->search_action_circulation(
744 { target_copy => $c->id },
746 order_by => { circ => 'xact_start desc' },
755 $volume->copies($copies);
756 push( @volumes, $volume );
759 #$session->disconnect();
765 __PACKAGE__->register_method(
766 method => "fleshed_copy_update",
767 api_name => "open-ils.cat.asset.copy.fleshed.batch.update",);
769 __PACKAGE__->register_method(
770 method => "fleshed_copy_update",
771 api_name => "open-ils.cat.asset.copy.fleshed.batch.update.override",);
774 sub fleshed_copy_update {
775 my( $self, $conn, $auth, $copies, $delete_stats, $oargs, $create_parts ) = @_;
776 return 1 unless ref $copies;
777 my( $reqr, $evt ) = $U->checkses($auth);
779 my $editor = new_editor(requestor => $reqr, xact => 1);
780 if ($self->api_name =~ /override/) {
781 $oargs = { all => 1 } unless defined $oargs;
785 my $retarget_holds = [];
786 $evt = OpenILS::Application::Cat::AssetCommon->update_fleshed_copies(
787 $editor, $oargs, undef, $copies, $delete_stats, $retarget_holds, undef, $create_parts);
790 $logger->info("fleshed copy update failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
796 $logger->info("fleshed copy update successfully updated ".scalar(@$copies)." copies");
797 reset_hold_list($auth, $retarget_holds);
802 sub reset_hold_list {
803 my($auth, $hold_ids) = @_;
804 return unless @$hold_ids;
805 $logger->info("reseting holds after copy status change: @$hold_ids");
806 my $ses = OpenSRF::AppSession->create('open-ils.circ');
807 $ses->request('open-ils.circ.hold.reset.batch', $auth, $hold_ids);
810 __PACKAGE__->register_method(
811 method => "transfer_copies_to_volume",
812 api_name => "open-ils.cat.transfer_copies_to_volume",
815 desc => 'Transfers specified copies to the specified call number, and changes Circ Lib to match the new Owning Lib.',
817 {desc => 'Authtoken', type => 'string'},
818 {desc => 'Call Number ID', type => 'number'},
819 {desc => 'Array of Copy IDs', type => 'array'},
822 return => {desc => '1 on success, Event on error'}
825 __PACKAGE__->register_method(
826 method => "transfer_copies_to_volume",
827 api_name => "open-ils.cat.transfer_copies_to_volume.override",);
829 sub transfer_copies_to_volume {
830 my( $self, $conn, $auth, $volume, $copies, $oargs ) = @_;
831 my $delete_stats = 1;
832 my $force_delete_empty_bib = undef;
833 my $create_parts = undef;
837 return 1 unless ref $copies;
838 my( $reqr, $evt ) = $U->checkses($auth);
840 my $editor = new_editor(requestor => $reqr, xact => 1);
841 if ($self->api_name =~ /override/) {
842 $oargs = { all => 1 } unless defined $oargs;
847 # does the volume exist? good, we also need its owning_lib later
848 my( $cn, $cn_evt ) = $U->fetch_callnumber( $volume, 0, $editor );
849 return $cn_evt if $cn_evt;
851 # flesh and munge the copies
852 my $fleshed_copies = [];
854 foreach my $copy_id ( @{ $copies } ) {
855 $copy = $editor->search_asset_copy([
856 { id => $copy_id , deleted => 'f' },
862 bmp => { type => 'left' }
867 flesh_fields => { acp => ['parts'] }
870 return OpenILS::Event->new('ASSET_COPY_NOT_FOUND') if !$copy;
871 $copy->call_number( $volume );
872 $copy->circ_lib( $cn->owning_lib() );
873 $copy->ischanged( 't' );
874 push @$fleshed_copies, $copy;
878 my $retarget_holds = [];
879 $evt = OpenILS::Application::Cat::AssetCommon->update_fleshed_copies(
880 $editor, $oargs, undef, $fleshed_copies, $delete_stats, $retarget_holds, $force_delete_empty_bib, $create_parts);
883 $logger->info("copy to volume transfer failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
888 # take care of the parts
889 for my $copy (@$fleshed_copies) {
890 my $parts = $copy->parts;
893 foreach my $part (@$parts) {
894 my $part_label = $part->label;
895 my $part_obj = $editor->search_biblio_monograph_part(
903 $part_obj = Fieldmapper::biblio::monograph_part->new();
904 $part_obj->label( $part_label );
905 $part_obj->record( $cn->record );
906 unless($editor->create_biblio_monograph_part($part_obj)) {
907 return $editor->die_event if $editor->die_event;
910 push @$part_objs, $part_obj;
912 $copy->parts( $part_objs );
914 $evt = OpenILS::Application::Cat::AssetCommon->update_copy_parts($editor, $copy, 1); #delete_parts=1
919 $logger->info("copy to volume transfer successfully updated ".scalar(@$copies)." copies");
920 reset_hold_list($auth, $retarget_holds);
925 __PACKAGE__->register_method(
926 method => 'in_db_merge',
927 api_name => 'open-ils.cat.biblio.records.merge',
929 Merges a group of records
930 @param auth The login session key
931 @param master The id of the record all other records should be merged into
932 @param records Array of records to be merged into the master record
933 @return 1 on success, Event on error.
938 my( $self, $conn, $auth, $master, $records ) = @_;
940 my $editor = new_editor( authtoken => $auth, xact => 1 );
941 return $editor->die_event unless $editor->checkauth;
942 return $editor->die_event unless $editor->allowed('MERGE_BIB_RECORDS'); # TODO see below about record ownership
945 for my $source ( @$records ) {
946 #XXX we actually /will/ want to check perms for master and sources after record ownership exists
948 # This stored proc (asset.merge_record_assets(target,source)) has the side effects of
949 # moving call_number, title-type (and some volume-type) hold_request and uri-mapping
950 # objects from the source record to the target record, so must be called from within
953 $count += $editor->json_query({
957 transform => 'asset.merge_record_assets',
963 where => { id => $master }
964 })->[0]->{count}; # count of objects moved, of all types
972 __PACKAGE__->register_method(
973 method => 'in_db_auth_merge',
974 api_name => 'open-ils.cat.authority.records.merge',
976 Merges a group of authority records
977 @param auth The login session key
978 @param master The id of the record all other records should be merged into
979 @param records Array of records to be merged into the master record
980 @return 1 on success, Event on error.
984 sub in_db_auth_merge {
985 my( $self, $conn, $auth, $master, $records ) = @_;
987 my $editor = new_editor( authtoken => $auth, xact => 1 );
988 return $editor->die_event unless $editor->checkauth;
989 return $editor->die_event unless $editor->allowed('MERGE_AUTH_RECORDS'); # TODO see below about record ownership
992 for my $source ( @$records ) {
993 $count += $editor->json_query({
997 transform => 'authority.merge_records',
1003 where => { id => $master }
1004 })->[0]->{count}; # count of objects moved, of all types
1011 __PACKAGE__->register_method(
1012 method => 'calculate_marc_merge',
1013 api_name => 'open-ils.cat.merge.marc.per_profile',
1015 Calculate the result of merging one or more MARC records
1016 per the specified merge profile
1017 @param auth The login session key
1018 @param merge_profile ID of the record merge profile
1019 @param records Array of two or more MARCXML records to be
1020 merged. If two are supplied, the first
1021 is treated as the record to be overlaid,
1022 and the the incoming record that will
1023 overlay the first. If more than two are
1024 supplied, the first is treated as the
1025 record to be overlaid, and each following
1026 record in turn will be merged into that
1028 @return MARCXML string of the results of the merge
1031 __PACKAGE__->register_method(
1032 method => 'calculate_bib_marc_merge',
1033 api_name => 'open-ils.cat.merge.biblio.per_profile',
1035 Calculate the result of merging one or more bib records
1036 per the specified merge profile
1037 @param auth The login session key
1038 @param merge_profile ID of the record merge profile
1039 @param records Array of two or more bib record IDs of
1040 the bibs to be merged.
1041 @return MARCXML string of the results of the merge
1044 __PACKAGE__->register_method(
1045 method => 'calculate_authority_marc_merge',
1046 api_name => 'open-ils.cat.merge.authority.per_profile',
1048 Calculate the result of merging one or more authority records
1049 per the specified merge profile
1050 @param auth The login session key
1051 @param merge_profile ID of the record merge profile
1052 @param records Array of two or more bib record IDs of
1053 the bibs to be merged.
1054 @return MARCXML string of the results of the merge
1058 sub _handle_marc_merge {
1059 my ($e, $merge_profile_id, $records) = @_;
1061 my $result = shift @$records;
1062 foreach my $incoming (@$records) {
1063 my $response = $e->json_query({
1065 'vandelay.merge_record_xml_using_profile',
1070 return unless ref($response);
1071 $result = $response->[0]->{'vandelay.merge_record_xml_using_profile'};
1076 sub calculate_marc_merge {
1077 my( $self, $conn, $auth, $merge_profile_id, $records ) = @_;
1079 my $e = new_editor(authtoken=>$auth, xact=>1);
1080 return $e->die_event unless $e->checkauth;
1082 my $merge_profile = $e->retrieve_vandelay_merge_profile($merge_profile_id)
1083 or return $e->die_event;
1084 return $e->die_event unless ref($records) && @$records >= 2;
1086 return _handle_marc_merge($e, $merge_profile_id, $records)
1089 sub calculate_bib_marc_merge {
1090 my( $self, $conn, $auth, $merge_profile_id, $bib_ids ) = @_;
1092 my $e = new_editor(authtoken=>$auth, xact=>1);
1093 return $e->die_event unless $e->checkauth;
1095 my $merge_profile = $e->retrieve_vandelay_merge_profile($merge_profile_id)
1096 or return $e->die_event;
1097 return $e->die_event unless ref($bib_ids) && @$bib_ids >= 2;
1100 foreach my $id (@$bib_ids) {
1101 my $bre = $e->retrieve_biblio_record_entry($id) or return $e->die_event;
1102 push @$records, $bre->marc();
1105 return _handle_marc_merge($e, $merge_profile_id, $records)
1108 sub calculate_authority_marc_merge {
1109 my( $self, $conn, $auth, $merge_profile_id, $authority_ids ) = @_;
1111 my $e = new_editor(authtoken=>$auth, xact=>1);
1112 return $e->die_event unless $e->checkauth;
1114 my $merge_profile = $e->retrieve_vandelay_merge_profile($merge_profile_id)
1115 or return $e->die_event;
1116 return $e->die_event unless ref($authority_ids) && @$authority_ids >= 2;
1119 foreach my $id (@$authority_ids) {
1120 my $are = $e->retrieve_authority_record_entry($id) or return $e->die_event;
1121 push @$records, $are->marc();
1124 return _handle_marc_merge($e, $merge_profile_id, $records)
1127 __PACKAGE__->register_method(
1128 method => "fleshed_volume_update",
1129 api_name => "open-ils.cat.asset.volume.fleshed.batch.update",);
1131 __PACKAGE__->register_method(
1132 method => "fleshed_volume_update",
1133 api_name => "open-ils.cat.asset.volume.fleshed.batch.update.override",);
1135 sub fleshed_volume_update {
1136 my( $self, $conn, $auth, $volumes, $delete_stats, $options, $oargs ) = @_;
1137 my( $reqr, $evt ) = $U->checkses($auth);
1138 return $evt if $evt;
1141 if ($self->api_name =~ /override/) {
1142 $oargs = { all => 1 } unless defined $oargs;
1146 my $editor = new_editor( requestor => $reqr, xact => 1 );
1147 my $retarget_holds = [];
1148 my $auto_merge_vols = $options->{auto_merge_vols};
1149 my $create_parts = $options->{create_parts};
1152 for my $vol (@$volumes) {
1153 $logger->info("vol-update: investigating volume ".$vol->id);
1155 $vol->editor($reqr->id);
1156 $vol->edit_date('now');
1158 my $copies = $vol->copies;
1161 $vol->editor($editor->requestor->id);
1162 $vol->edit_date('now');
1164 if( $vol->isdeleted ) {
1166 $logger->info("vol-update: deleting volume");
1167 return $editor->die_event unless
1168 $editor->allowed('UPDATE_VOLUME', $vol->owning_lib);
1170 if(my $evt = $assetcom->delete_volume($editor, $vol, $oargs, $$options{force_delete_copies})) {
1175 return $editor->die_event unless
1176 $editor->update_asset_call_number($vol);
1178 } elsif( $vol->isnew ) {
1179 $logger->info("vol-update: creating volume");
1180 ($vol,$evt) = $assetcom->create_volume( $auto_merge_vols ? { all => 1} : $oargs, $editor, $vol );
1181 return $evt if $evt;
1183 } elsif( $vol->ischanged ) {
1184 $logger->info("vol-update: update volume");
1185 my $resp = update_volume($vol, $editor, ($oargs->{all} or grep { $_ eq 'VOLUME_LABEL_EXISTS' } @{$oargs->{events}} or $auto_merge_vols));
1186 return $resp->{evt} if $resp->{evt};
1187 $vol = $resp->{merge_vol} if $resp->{merge_vol};
1190 # now update any attached copies
1191 if( $copies and @$copies and !$vol->isdeleted ) {
1192 $_->call_number($vol->id) for @$copies;
1193 $evt = $assetcom->update_fleshed_copies(
1194 $editor, $oargs, $vol, $copies, $delete_stats, $retarget_holds, undef, $create_parts);
1195 return $evt if $evt;
1196 push( @$copy_ids, $_->id ) for @$copies;
1201 reset_hold_list($auth, $retarget_holds);
1202 if ($options->{return_copy_ids}) {
1205 return scalar(@$volumes);
1213 my $auto_merge = shift;
1217 return {evt => $editor->event} unless
1218 $editor->allowed('UPDATE_VOLUME', $vol->owning_lib);
1220 return {evt => $evt}
1221 if ( $evt = OpenILS::Application::Cat::AssetCommon->org_cannot_have_vols($editor, $vol->owning_lib) );
1223 my $vols = $editor->search_asset_call_number({
1224 owning_lib => $vol->owning_lib,
1225 record => $vol->record,
1226 label => $vol->label,
1227 prefix => $vol->prefix,
1228 suffix => $vol->suffix,
1230 id => {'!=' => $vol->id}
1237 # If the auto-merge option is on, merge our updated volume into the existing
1238 # volume with the same record + owner + label.
1239 ($merge_vol, $evt) = OpenILS::Application::Cat::Merge::merge_volumes($editor, [$vol], $vols->[0]);
1240 return {evt => $evt, merge_vol => $merge_vol};
1243 return {evt => OpenILS::Event->new('VOLUME_LABEL_EXISTS', payload => $vol->id)};
1247 return {evt => $editor->die_event} unless $editor->update_asset_call_number($vol);
1253 __PACKAGE__->register_method (
1254 method => 'delete_bib_record',
1255 api_name => 'open-ils.cat.biblio.record_entry.delete');
1257 sub delete_bib_record {
1258 my($self, $conn, $auth, $rec_id) = @_;
1259 my $e = new_editor(xact=>1, authtoken=>$auth);
1260 return $e->die_event unless $e->checkauth;
1261 return $e->die_event unless $e->allowed('DELETE_RECORD', $e->requestor->ws_ou);
1262 my $vols = $e->search_asset_call_number({record=>$rec_id, deleted=>'f'});
1263 return OpenILS::Event->new('RECORD_NOT_EMPTY', payload=>$rec_id) if @$vols;
1264 my $evt = OpenILS::Application::Cat::BibCommon->delete_rec($e, $rec_id);
1265 if($evt) { $e->rollback; return $evt; }
1272 __PACKAGE__->register_method (
1273 method => 'batch_volume_transfer',
1274 api_name => 'open-ils.cat.asset.volume.batch.transfer',
1277 __PACKAGE__->register_method (
1278 method => 'batch_volume_transfer',
1279 api_name => 'open-ils.cat.asset.volume.batch.transfer.override',
1283 sub batch_volume_transfer {
1284 my( $self, $conn, $auth, $args, $oargs ) = @_;
1287 my $rec = $$args{docid};
1288 my $o_lib = $$args{lib};
1289 my $vol_ids = $$args{volumes};
1291 my $override = 1 if $self->api_name =~ /override/;
1292 $oargs = { all => 1 } unless defined $oargs;
1294 $logger->info("merge: transferring volumes to lib=$o_lib and record=$rec");
1296 my $e = new_editor(authtoken => $auth, xact =>1);
1297 return $e->event unless $e->checkauth;
1298 return $e->event unless $e->allowed('UPDATE_VOLUME', $o_lib);
1300 my $dorg = $e->retrieve_actor_org_unit($o_lib)
1301 or return $e->event;
1303 my $ou_type = $e->retrieve_actor_org_unit_type($dorg->ou_type)
1304 or return $e->event;
1306 return $evt if ( $evt = OpenILS::Application::Cat::AssetCommon->org_cannot_have_vols($e, $o_lib) );
1308 my $vols = $e->batch_retrieve_asset_call_number($vol_ids);
1313 for my $vol (@$vols) {
1315 # if we've already looked at this volume, go to the next
1316 next if !$vol or grep { $vol->id == $_ } @seen;
1318 # grab all of the volumes in the list that have
1319 # the same label so they can be merged
1320 my @all = grep { $_->label eq $vol->label } @$vols;
1322 # take note of the fact that we've looked at this set of volumes
1323 push( @seen, $_->id ) for @all;
1324 push( @rec_ids, $_->record ) for @all;
1326 # for each volume, see if there are any copies that have a
1327 # remote circ_lib (circ_lib != vol->owning_lib and != $o_lib ).
1329 unless( $override && ($oargs->{all} || grep { $_ eq 'COPY_REMOTE_CIRC_LIB' } @{$oargs->{events}}) ) {
1332 $logger->debug("merge: searching for copies with remote circ_lib for volume ".$v->id);
1334 call_number => $v->id,
1335 circ_lib => { "not in" => [ $o_lib, $v->owning_lib ] },
1339 my $copies = $e->search_asset_copy($args, {idlist=>1});
1341 # if the copy's circ_lib matches the destination lib,
1343 return OpenILS::Event->new('COPY_REMOTE_CIRC_LIB') if @$copies;
1347 # record the difference between the destination bib and the present bib
1348 my $same_bib = $vol->record == $rec;
1350 # see if there is a volume at the destination lib that
1351 # already has the requested label
1352 my $existing_vol = $e->search_asset_call_number(
1354 label => $vol->label,
1355 prefix => $vol->prefix,
1356 suffix => $vol->suffix,
1358 owning_lib => $o_lib,
1363 if( $existing_vol ) {
1365 if( grep { $_->id == $existing_vol->id } @all ) {
1366 # this volume is already accounted for in our list of volumes to merge
1367 $existing_vol = undef;
1370 # this volume exists on the destination record/owning_lib and must
1371 # be used as the destination for merging
1372 $logger->debug("merge: volume already exists at destination record: ".
1373 $existing_vol->id.' : '.$existing_vol->label) if $existing_vol;
1377 if( @all > 1 || $existing_vol ) {
1378 $logger->info("merge: found collisions in volume transfer");
1379 my @args = ($e, \@all);
1380 @args = ($e, \@all, $existing_vol) if $existing_vol;
1381 ($vol, $evt) = OpenILS::Application::Cat::Merge::merge_volumes(@args);
1382 return $evt if $evt;
1385 if( !$existing_vol ) {
1387 $vol->owning_lib($o_lib);
1389 $vol->editor($e->requestor->id);
1390 $vol->edit_date('now');
1392 $logger->info("merge: updating volume ".$vol->id);
1393 $e->update_asset_call_number($vol) or return $e->event;
1396 $logger->info("merge: bypassing volume update because existing volume used as target");
1399 # regardless of what volume was used as the destination,
1400 # update any copies that have moved over to the new lib
1401 my $copies = $e->search_asset_copy([
1402 { call_number => $vol->id , deleted => 'f' },
1408 bmp => { type => 'left' }
1413 flesh_fields => { acp => ['parts'] }
1417 # update circ lib on the copies - make this a method flag?
1418 for my $copy (@$copies) {
1419 next if $copy->circ_lib == $o_lib;
1420 $logger->info("merge: transfer moving circ lib on copy ".$copy->id);
1421 $copy->circ_lib($o_lib);
1422 $copy->editor($e->requestor->id);
1423 $copy->edit_date('now');
1424 $e->update_asset_copy($copy) or return $e->event;
1427 # update parts if volume is moving bib records
1429 for my $copy (@$copies) {
1430 my $parts = $copy->parts;
1433 foreach my $part (@$parts) {
1434 my $part_label = $part->label;
1435 my $part_obj = $e->search_biblio_monograph_part(
1444 $part_obj = Fieldmapper::biblio::monograph_part->new();
1445 $part_obj->label( $part_label );
1446 $part_obj->record( $rec );
1447 unless($e->create_biblio_monograph_part($part_obj)) {
1448 return $e->die_event if $e->die_event;
1451 push @$part_objs, $part_obj;
1454 $copy->parts( $part_objs );
1455 $copy->ischanged(1);
1456 $evt = OpenILS::Application::Cat::AssetCommon->update_copy_parts($e, $copy, 1); #delete_parts=1
1457 return $evt if $evt;
1461 # Now see if any empty records need to be deleted after all of this
1464 $logger->debug("merge: seeing if we should delete record $_...");
1465 $evt = OpenILS::Application::Cat::BibCommon->delete_rec($e, $_)
1466 if OpenILS::Application::Cat::BibCommon->title_is_empty($e, $_);
1467 return $evt if $evt;
1471 $logger->info("merge: transfer succeeded");
1479 __PACKAGE__->register_method(
1480 api_name => 'open-ils.cat.call_number.find_or_create',
1481 method => 'find_or_create_volume',
1484 sub find_or_create_volume {
1485 my( $self, $conn, $auth, $label, $record_id, $org_id, $prefix, $suffix, $label_class ) = @_;
1486 my $e = new_editor(authtoken=>$auth, xact=>1);
1487 return $e->die_event unless $e->checkauth;
1488 my ($vol, $evt, $exists) =
1489 OpenILS::Application::Cat::AssetCommon->find_or_create_volume($e, $label, $record_id, $org_id, $prefix, $suffix, $label_class);
1490 return $evt if $evt;
1491 $e->rollback if $exists;
1493 return { 'acn_id' => $vol->id, 'existed' => $exists };
1497 __PACKAGE__->register_method(
1498 method => "create_serial_record_xml",
1499 api_name => "open-ils.cat.serial.record.xml.create.override",
1500 signature => q/@see open-ils.cat.serial.record.xml.create/);
1502 __PACKAGE__->register_method(
1503 method => "create_serial_record_xml",
1504 api_name => "open-ils.cat.serial.record.xml.create",
1506 Inserts a new serial record with the given XML
1510 sub create_serial_record_xml {
1511 my( $self, $client, $login, $source, $owning_lib, $record_id, $xml, $oargs ) = @_;
1513 my $override = 1 if $self->api_name =~ /override/; # not currently used
1514 $oargs = { all => 1 } unless defined $oargs; # Not currently used, but here for consistency.
1516 my $e = new_editor(xact=>1, authtoken=>$login);
1517 return $e->die_event unless $e->checkauth;
1518 return $e->die_event unless $e->allowed('CREATE_MFHD_RECORD', $owning_lib);
1520 # Auto-populate the location field of a placeholder MFHD record with the library name
1521 my $aou = $e->retrieve_actor_org_unit($owning_lib) or return $e->die_event;
1523 my $mfhd = Fieldmapper::serial::record_entry->new;
1525 $mfhd->source($source) if $source;
1526 $mfhd->record($record_id);
1527 $mfhd->creator($e->requestor->id);
1528 $mfhd->editor($e->requestor->id);
1529 $mfhd->create_date('now');
1530 $mfhd->edit_date('now');
1531 $mfhd->owning_lib($owning_lib);
1533 # If the caller did not pass in MFHD XML, create a placeholder record.
1534 # The placeholder will only contain the name of the owning library.
1535 # The goal is to generate common patterns for the caller in the UI that
1536 # then get passed in here.
1538 my $aou_name = $aou->name;
1541 xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
1542 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1543 xmlns="http://www.loc.gov/MARC21/slim">
1544 <leader>00307ny a22001094 4500</leader>
1545 <controlfield tag="001">42153</controlfield>
1546 <controlfield tag="005">20090601182414.0</controlfield>
1547 <controlfield tag="004">$record_id</controlfield>
1548 <controlfield tag="008"> 4u####8###l# 4 uueng1 </controlfield>
1549 <datafield tag="852" ind1=" " ind2=" "> <subfield code="b">$aou_name</subfield></datafield>
1553 my $marcxml = XML::LibXML->new->parse_string($xml);
1554 $marcxml->documentElement->setNamespace("http://www.loc.gov/MARC21/slim", "marc", 1 );
1555 $marcxml->documentElement->setNamespace("http://www.loc.gov/MARC21/slim");
1557 $mfhd->marc($U->entityize($marcxml->documentElement->toString));
1559 $e->create_serial_record_entry($mfhd) or return $e->die_event;
1565 __PACKAGE__->register_method(
1566 method => "create_update_asset_copy_template",
1567 api_name => "open-ils.cat.asset.copy_template.create_or_update"
1570 sub create_update_asset_copy_template {
1571 my ($self, $client, $authtoken, $act) = @_;
1573 my $e = new_editor("xact" => 1, "authtoken" => $authtoken);
1574 return $e->die_event unless $e->checkauth;
1575 return $e->die_event unless $e->allowed(
1576 "ADMIN_ASSET_COPY_TEMPLATE", $act->owning_lib
1579 $act->editor($e->requestor->id);
1580 $act->edit_date("now");
1584 $act->creator($e->requestor->id);
1585 $act->create_date("now");
1587 $e->create_asset_copy_template($act) or return $e->die_event;
1590 $e->update_asset_copy_template($act) or return $e->die_event;
1591 $retval = $e->retrieve_asset_copy_template($e->data);
1593 $e->commit and return $retval;
1596 __PACKAGE__->register_method(
1597 method => "acn_sms_msg",
1598 api_name => "open-ils.cat.acn.send_sms_text",
1600 Send an SMS text from an A/T template for specified call numbers.
1602 First parameter is null or an auth token (whether a null is allowed
1603 depends on the sms.disable_authentication_requirement.callnumbers OU
1606 Second parameter is the id of the context org.
1608 Third parameter is the code of the SMS carrier from the
1609 config.sms_carrier table.
1611 Fourth parameter is the SMS number.
1613 Fifth parameter is the ACN id's to target, though currently only the
1614 first ACN is used by the template (and the UI is only sending one).
1619 my($self, $conn, $auth, $org_id, $carrier, $number, $target_ids) = @_;
1621 my $sms_enable = $U->ou_ancestor_setting_value(
1622 $org_id || $U->get_org_tree->id,
1625 # We could maybe make a Validator for this on the templates
1626 if (! $U->is_true($sms_enable)) {
1630 my $disable_auth = $U->ou_ancestor_setting_value(
1631 $org_id || $U->get_org_tree->id,
1632 'sms.disable_authentication_requirement.callnumbers'
1637 ? (authtoken => $auth, xact => 1)
1640 return $e->event unless $disable_auth || $e->checkauth;
1642 my $targets = $e->batch_retrieve_asset_call_number($target_ids);
1644 $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
1645 # simply making this method authoritative because of weirdness
1646 # with transaction handling in A/T code that causes rollback
1647 # failure down the line if handling many targets
1649 return undef unless @$targets;
1650 return $U->fire_object_event(
1652 'acn.format.sms_text', # hook
1655 undef, # granularity
1657 sms_carrier => $carrier,
1658 sms_notify => $number
1665 __PACKAGE__->register_method(
1666 method => "fixed_field_values_by_rec_type",
1667 api_name => "open-ils.cat.biblio.fixed_field_values.by_rec_type",
1670 desc => 'Given a record type (as in cmfpm.rec_type), return fixed fields and their possible values as known to the DB',
1672 {desc => 'Record Type', type => 'string'},
1673 {desc => '(Optional) Fixed field', type => 'string'},
1676 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' }
1680 sub fixed_field_values_by_rec_type {
1681 my ($self, $conn, $rec_type, $fixed_field) = @_;
1684 my $values = $e->json_query({
1686 crad => ["fixed_field"],
1687 ccvm => [qw/code value/],
1688 cmfpm => [qw/length default_val/],
1696 fkey => "fixed_field",
1697 field => "fixed_field"
1704 "+cmfpm" => {rec_type => $rec_type},
1705 defined $fixed_field ?
1706 ("+crad" => {fixed_field => $fixed_field}) : ()
1709 {class => "crad", field => "fixed_field"},
1710 {class => "ccvm", field => "code"}
1712 }) or return $e->die_event;
1715 for my $row (@$values) {
1716 $result->{$row->{fixed_field}} ||= [];
1717 push @{$result->{$row->{fixed_field}}}, [@$row{qw/code value length default_val/}];
1723 __PACKAGE__->register_method(
1724 method => "retrieve_tag_table",
1725 api_name => "open-ils.cat.tag_table.all.retrieve.local",
1729 desc => "Retrieve set of MARC tags, subfields, and indicator values for the user's OU",
1731 {desc => 'Authtoken', type => 'string'},
1732 {desc => 'MARC Format', type => 'string'},
1733 {desc => 'MARC Record Type', type => 'string'},
1736 return => {desc => 'Structure representing the tag table available to that user', type => 'object' }
1738 __PACKAGE__->register_method(
1739 method => "retrieve_tag_table",
1740 api_name => "open-ils.cat.tag_table.all.retrieve.stock",
1744 desc => 'Retrieve set of MARC tags, subfields, and indicator values for stock MARC standard',
1746 {desc => 'Authtoken', type => 'string'},
1747 {desc => 'MARC Format', type => 'string'},
1748 {desc => 'MARC Record Type', type => 'string'},
1751 return => {desc => 'Structure representing the stock tag table', type => 'object' }
1753 __PACKAGE__->register_method(
1754 method => "retrieve_tag_table",
1755 api_name => "open-ils.cat.tag_table.field_list.retrieve.local",
1759 desc => "Retrieve set of MARC tags for available to the user's OU",
1761 {desc => 'Authtoken', type => 'string'},
1762 {desc => 'MARC Format', type => 'string'},
1763 {desc => 'MARC Record Type', type => 'string'},
1766 return => {desc => 'Structure representing the tags available to that user', type => 'object' }
1768 __PACKAGE__->register_method(
1769 method => "retrieve_tag_table",
1770 api_name => "open-ils.cat.tag_table.field_list.retrieve.stock",
1774 desc => 'Retrieve set of MARC tags for stock MARC standard',
1776 {desc => 'Authtoken', type => 'string'},
1777 {desc => 'MARC Format', type => 'string'},
1778 {desc => 'MARC Record Type', type => 'string'},
1781 return => {desc => 'Structure representing the stock MARC tags', type => 'object' }
1784 sub retrieve_tag_table {
1785 my( $self, $conn, $auth, $marc_format, $marc_record_type ) = @_;
1786 my $e = new_editor( authtoken=>$auth, xact=>1 );
1787 return $e->die_event unless $e->checkauth;
1789 my $field_list_only = ($self->api_name =~ /\.field_list\./) ? 1 : 0;
1791 if ($self->api_name =~ /\.local$/) {
1792 $context_ou = $e->requestor->ws_ou;
1796 unless ($field_list_only) {
1797 my $subfields = $e->json_query(
1798 { from => [ 'config.ou_marc_subfields', 1, $marc_record_type, $context_ou ] }
1800 foreach my $sf (@$subfields) {
1802 code => $sf->{code},
1803 description => $sf->{description},
1804 mandatory => $sf->{mandatory},
1805 repeatable => $sf->{repeatable},
1807 if ($sf->{value_ctype}) {
1808 $sf_data->{value_list} = $e->json_query({
1809 select => { ccvm => [
1811 { column => 'value', alias => 'description' }
1815 where => { ctype => $sf->{value_ctype} },
1816 order_by => { ccvm => { code => {} } },
1819 push @{ $sf_by_tag{$sf->{tag}} }, $sf_data;
1823 my $fields = $e->json_query(
1824 { from => [ 'config.ou_marc_fields', 1, $marc_record_type, $context_ou ] }
1827 foreach my $field (@$fields) {
1828 next if $field->{hidden} eq 't';
1829 unless ($field_list_only) {
1830 my $tag = $field->{tag};
1831 if ($tag ge '010') {
1832 for my $pos (1..2) {
1833 my $ind_ccvm_key = "${marc_format}_${marc_record_type}_${tag}_ind_${pos}";
1834 my $indvals = $e->json_query({
1835 select => { ccvm => [
1837 { column => 'value', alias => 'description' }
1841 where => { ctype => $ind_ccvm_key }
1843 next unless defined($indvals);
1844 $field->{"ind$pos"} = $indvals;
1846 $field->{subfields} = exists($sf_by_tag{$tag}) ? $sf_by_tag{$tag} : [];
1849 $conn->respond($field);