3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 OpenILS::Application::Serial - Performs serials-related tasks such as receiving issues and generating predictions
31 Dan Wells, dbw2@calvin.edu
35 package OpenILS::Application::Serial;
40 use OpenILS::Application;
41 use base qw/OpenILS::Application/;
42 use OpenILS::Application::AppUtils;
44 use OpenSRF::AppSession;
45 use OpenSRF::Utils qw/:datetime/;
46 use OpenSRF::Utils::Logger qw/:logger/;
47 use OpenILS::Utils::CStoreEditor q/:funcs/;
48 use OpenILS::Utils::Fieldmapper;
49 use OpenILS::Utils::MFHD;
50 use MARC::File::XML (BinaryEncoding => 'utf8');
51 my $U = 'OpenILS::Application::AppUtils';
52 my @MFHD_NAMES = ('basic','supplement','index');
53 my %MFHD_NAMES_BY_TAG = ( '853' => $MFHD_NAMES[0],
54 '863' => $MFHD_NAMES[0],
55 '854' => $MFHD_NAMES[1],
56 '864' => $MFHD_NAMES[1],
57 '855' => $MFHD_NAMES[2],
58 '865' => $MFHD_NAMES[2] );
59 my %MFHD_TAGS_BY_NAME = ( $MFHD_NAMES[0] => '853',
60 $MFHD_NAMES[1] => '854',
61 $MFHD_NAMES[2] => '855');
63 # helper method for conforming dates to ISO8601
68 foreach my $field (@$fields) {
69 $item->$field(OpenSRF::Utils::clense_ISO8601($item->$field)) if $item->$field;
77 "open-ils.search.biblio.record.mods_slim.retrieve",
83 ##########################################################################
86 __PACKAGE__->register_method(
87 method => 'fleshed_item_alter',
88 api_name => 'open-ils.serial.item.fleshed.batch.update',
92 desc => 'Receives an array of one or more items and updates the database as needed',
95 desc => 'Authtoken for current user session',
100 desc => 'Array of fleshed items',
106 desc => 'Returns 1 if successful, event if failed',
112 sub fleshed_item_alter {
113 my( $self, $conn, $auth, $items ) = @_;
114 return 1 unless ref $items;
115 my( $reqr, $evt ) = $U->checkses($auth);
117 my $editor = new_editor(requestor => $reqr, xact => 1);
118 my $override = $self->api_name =~ /override/;
120 # TODO: permission check
121 # return $editor->event unless
122 # $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
124 for my $item (@$items) {
126 my $itemid = $item->id;
127 $item->editor($editor->requestor->id);
128 $item->edit_date('now');
130 if( $item->isdeleted ) {
131 $evt = _delete_sitem( $editor, $override, $item);
132 } elsif( $item->isnew ) {
133 # TODO: reconsider this
134 # if the item has a new issuance, create the issuance first
135 if (ref $item->issuance eq 'Fieldmapper::serial::issuance' and $item->issuance->isnew) {
136 fleshed_issuance_alter($self, $conn, $auth, [$item->issuance]);
138 _cleanse_dates($item, ['date_expected','date_received']);
139 $evt = _create_sitem( $editor, $item );
141 _cleanse_dates($item, ['date_expected','date_received']);
142 $evt = _update_sitem( $editor, $override, $item );
147 $logger->info("fleshed item-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
151 $logger->debug("item-alter: done updating item batch");
153 $logger->info("fleshed item-alter successfully updated ".scalar(@$items)." items");
158 my ($editor, $override, $item) = @_;
159 $logger->info("item-alter: delete item ".OpenSRF::Utils::JSON->perl2JSON($item));
160 return $editor->event unless $editor->delete_serial_item($item);
165 my ($editor, $item) = @_;
167 $item->creator($editor->requestor->id);
168 $item->create_date('now');
170 $logger->info("item-alter: new item ".OpenSRF::Utils::JSON->perl2JSON($item));
171 return $editor->event unless $editor->create_serial_item($item);
176 my ($editor, $override, $item) = @_;
178 $logger->info("item-alter: retrieving item ".$item->id);
179 my $orig_item = $editor->retrieve_serial_item($item->id);
181 $logger->info("item-alter: original item ".OpenSRF::Utils::JSON->perl2JSON($orig_item));
182 $logger->info("item-alter: updated item ".OpenSRF::Utils::JSON->perl2JSON($item));
183 return $editor->event unless $editor->update_serial_item($item);
187 __PACKAGE__->register_method(
188 method => "fleshed_serial_item_retrieve_batch",
190 api_name => "open-ils.serial.item.fleshed.batch.retrieve"
193 sub fleshed_serial_item_retrieve_batch {
194 my( $self, $client, $ids ) = @_;
195 # FIXME: permissions?
196 $logger->info("Fetching fleshed serial items @$ids");
197 return $U->cstorereq(
198 "open-ils.cstore.direct.serial.item.search.atomic",
201 flesh_fields => {sitem => [ qw/issuance creator editor stream unit notes/ ], sstr => ["distribution"], sunit => ["call_number"], siss => [qw/creator editor subscription/]}
206 ##########################################################################
209 __PACKAGE__->register_method(
210 method => 'fleshed_issuance_alter',
211 api_name => 'open-ils.serial.issuance.fleshed.batch.update',
215 desc => 'Receives an array of one or more issuances and updates the database as needed',
218 desc => 'Authtoken for current user session',
223 desc => 'Array of fleshed issuances',
229 desc => 'Returns 1 if successful, event if failed',
235 sub fleshed_issuance_alter {
236 my( $self, $conn, $auth, $issuances ) = @_;
237 return 1 unless ref $issuances;
238 my( $reqr, $evt ) = $U->checkses($auth);
240 my $editor = new_editor(requestor => $reqr, xact => 1);
241 my $override = $self->api_name =~ /override/;
243 # TODO: permission support
244 # return $editor->event unless
245 # $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
247 for my $issuance (@$issuances) {
248 my $issuanceid = $issuance->id;
249 $issuance->editor($editor->requestor->id);
250 $issuance->edit_date('now');
252 if( $issuance->isdeleted ) {
253 $evt = _delete_siss( $editor, $override, $issuance);
254 } elsif( $issuance->isnew ) {
255 _cleanse_dates($issuance, ['date_published']);
256 $evt = _create_siss( $editor, $issuance );
258 _cleanse_dates($issuance, ['date_published']);
259 $evt = _update_siss( $editor, $override, $issuance );
264 $logger->info("fleshed issuance-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
268 $logger->debug("issuance-alter: done updating issuance batch");
270 $logger->info("fleshed issuance-alter successfully updated ".scalar(@$issuances)." issuances");
275 my ($editor, $override, $issuance) = @_;
276 $logger->info("issuance-alter: delete issuance ".OpenSRF::Utils::JSON->perl2JSON($issuance));
277 return $editor->event unless $editor->delete_serial_issuance($issuance);
282 my ($editor, $issuance) = @_;
284 $issuance->creator($editor->requestor->id);
285 $issuance->create_date('now');
287 $logger->info("issuance-alter: new issuance ".OpenSRF::Utils::JSON->perl2JSON($issuance));
288 return $editor->event unless $editor->create_serial_issuance($issuance);
293 my ($editor, $override, $issuance) = @_;
295 $logger->info("issuance-alter: retrieving issuance ".$issuance->id);
296 my $orig_issuance = $editor->retrieve_serial_issuance($issuance->id);
298 $logger->info("issuance-alter: original issuance ".OpenSRF::Utils::JSON->perl2JSON($orig_issuance));
299 $logger->info("issuance-alter: updated issuance ".OpenSRF::Utils::JSON->perl2JSON($issuance));
300 return $editor->event unless $editor->update_serial_issuance($issuance);
304 __PACKAGE__->register_method(
305 method => "fleshed_serial_issuance_retrieve_batch",
307 api_name => "open-ils.serial.issuance.fleshed.batch.retrieve"
310 sub fleshed_serial_issuance_retrieve_batch {
311 my( $self, $client, $ids ) = @_;
312 # FIXME: permissions?
313 $logger->info("Fetching fleshed serial issuances @$ids");
314 return $U->cstorereq(
315 "open-ils.cstore.direct.serial.issuance.search.atomic",
318 flesh_fields => {siss => [ qw/creator editor subscription/ ]}
323 ##########################################################################
326 __PACKAGE__->register_method(
327 method => 'fleshed_sunit_alter',
328 api_name => 'open-ils.serial.sunit.fleshed.batch.update',
332 desc => 'Receives an array of one or more Units and updates the database as needed',
335 desc => 'Authtoken for current user session',
340 desc => 'Array of fleshed Units',
346 desc => 'Returns 1 if successful, event if failed',
352 sub fleshed_sunit_alter {
353 my( $self, $conn, $auth, $sunits ) = @_;
354 return 1 unless ref $sunits;
355 my( $reqr, $evt ) = $U->checkses($auth);
357 my $editor = new_editor(requestor => $reqr, xact => 1);
358 my $override = $self->api_name =~ /override/;
360 # TODO: permission support
361 # return $editor->event unless
362 # $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
364 for my $sunit (@$sunits) {
365 if( $sunit->isdeleted ) {
366 $evt = _delete_sunit( $editor, $override, $sunit );
368 $sunit->default_location( $sunit->default_location->id ) if ref $sunit->default_location;
370 if( $sunit->isnew ) {
371 $evt = _create_sunit( $editor, $sunit );
373 $evt = _update_sunit( $editor, $override, $sunit );
379 $logger->info("fleshed sunit-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
383 $logger->debug("sunit-alter: done updating sunit batch");
385 $logger->info("fleshed sunit-alter successfully updated ".scalar(@$sunits)." Units");
390 my ($editor, $override, $sunit) = @_;
391 $logger->info("sunit-alter: delete sunit ".OpenSRF::Utils::JSON->perl2JSON($sunit));
392 return $editor->event unless $editor->delete_serial_unit($sunit);
397 my ($editor, $sunit) = @_;
399 $logger->info("sunit-alter: new Unit ".OpenSRF::Utils::JSON->perl2JSON($sunit));
400 return $editor->event unless $editor->create_serial_unit($sunit);
405 my ($editor, $override, $sunit) = @_;
407 $logger->info("sunit-alter: retrieving sunit ".$sunit->id);
408 my $orig_sunit = $editor->retrieve_serial_unit($sunit->id);
410 $logger->info("sunit-alter: original sunit ".OpenSRF::Utils::JSON->perl2JSON($orig_sunit));
411 $logger->info("sunit-alter: updated sunit ".OpenSRF::Utils::JSON->perl2JSON($sunit));
412 return $editor->event unless $editor->update_serial_unit($sunit);
416 __PACKAGE__->register_method(
417 method => "retrieve_unit_list",
419 api_name => "open-ils.serial.unit_list.retrieve"
422 sub retrieve_unit_list {
424 my( $self, $client, @sdist_ids ) = @_;
426 if(ref($sdist_ids[0])) { @sdist_ids = @{$sdist_ids[0]}; }
428 my $e = new_editor();
432 { 'sunit' => [ 'id', 'summary_contents', 'sort_key' ],
433 'sitem' => ['stream'],
434 'sstr' => ['distribution'],
435 'sdist' => [{'column' => 'label', 'alias' => 'sdist_label'}]
442 { 'join' => { 'sunit' => {} } }
447 'distinct' => 'true',
448 'where' => { '+sdist' => {'id' => \@sdist_ids} },
449 'order_by' => [{'class' => 'sunit', 'field' => 'sort_key'}]
452 my $unit_list_entries = $e->json_query($query);
455 foreach my $entry (@$unit_list_entries) {
456 my $value = {'sunit' => $entry->{id}, 'sstr' => $entry->{stream}, 'sdist' => $entry->{distribution}};
457 my $label = $entry->{summary_contents};
458 if (length($label) > 100) {
459 $label = substr($label, 0, 100) . '...'; # limited space in dropdown / menu
461 $label = "[$entry->{sdist_label}/$entry->{stream} #$entry->{id}] " . $label;
462 push (@entries, [$label, OpenSRF::Utils::JSON->perl2JSON($value)]);
470 ##########################################################################
471 # predict and receive methods
473 __PACKAGE__->register_method(
474 method => 'make_predictions',
475 api_name => 'open-ils.serial.make_predictions',
479 desc => 'Receives an ssub id and populates the issuance and item tables',
482 desc => 'Serial Subscription ID',
489 sub make_predictions {
490 my ($self, $conn, $authtoken, $args) = @_;
492 my $editor = OpenILS::Utils::CStoreEditor->new();
493 my $ssub_id = $args->{ssub_id};
494 my $mfhd = MFHD->new(MARC::Record->new());
496 my $ssub = $editor->retrieve_serial_subscription([$ssub_id]);
497 my $scaps = $editor->search_serial_caption_and_pattern({ subscription => $ssub_id, active => 't'});
498 my $sdists = $editor->search_serial_distribution( [{ subscription => $ssub->id }, { flesh => 1,
499 flesh_fields => {sdist => [ qw/ streams / ]}, limit => 1 }] ); #TODO: 'deleted' support?
503 foreach my $scap (@$scaps) {
504 my $caption_field = _revive_caption($scap);
505 $caption_field->update('8' => $link_id);
506 $mfhd->append_fields($caption_field);
508 'caption' => $caption_field,
509 'scap_id' => $scap->id,
510 'num_to_predict' => $args->{num_to_predict}
512 if ($args->{base_issuance}) { # predict from a given issuance
513 $options->{predict_from} = _revive_holding($args->{base_issuance}->holding_code, $caption_field, 1); # fresh MFHD Record, so we simply default to 1 for seqno
514 } else { # default to predicting from last published
515 my $last_published = $editor->search_serial_issuance([
516 {'caption_and_pattern' => $scap->id,
517 'subscription' => $ssub_id},
518 {limit => 1, order_by => { siss => "date_published DESC" }}]
520 if ($last_published->[0]) {
521 my $last_siss = $last_published->[0];
522 $options->{predict_from} = _revive_holding($last_siss->holding_code, $caption_field, 1);
524 #TODO: throw event (can't predict from nothing!)
527 push( @predictions, _generate_issuance_values($mfhd, $options) );
532 foreach my $prediction (@predictions) {
533 my $issuance = new Fieldmapper::serial::issuance;
535 $issuance->label($prediction->{label});
536 $issuance->date_published($prediction->{date_published}->strftime('%F'));
537 $issuance->holding_code(OpenSRF::Utils::JSON->perl2JSON($prediction->{holding_code}));
538 $issuance->holding_type($prediction->{holding_type});
539 $issuance->caption_and_pattern($prediction->{caption_and_pattern});
540 $issuance->subscription($ssub->id);
541 push (@issuances, $issuance);
544 fleshed_issuance_alter($self, $conn, $authtoken, \@issuances); # FIXME: catch events
547 for (my $i = 0; $i < @issuances; $i++) {
548 my $date_expected = $predictions[$i]->{date_published}->add(seconds => interval_to_seconds($ssub->expected_date_offset))->strftime('%F');
549 my $issuance = $issuances[$i];
550 #$issuance->label(interval_to_seconds($ssub->expected_date_offset));
551 foreach my $sdist (@$sdists) {
552 my $streams = $sdist->streams;
553 foreach my $stream (@$streams) {
554 my $item = new Fieldmapper::serial::item;
556 $item->stream($stream->id);
557 $item->date_expected($date_expected);
558 $item->issuance($issuance->id);
559 push (@items, $item);
563 fleshed_item_alter($self, $conn, $authtoken, \@items); # FIXME: catch events
568 # _generate_issuance_values() is an initial attempt at a function which can be used
569 # to populate an issuance table with a list of predicted issues. It accepts
570 # a hash ref of options initially defined as:
571 # caption : the caption field to predict on
572 # num_to_predict : the number of issues you wish to predict
573 # last_rec_date : the date of the last received issue, to be used as an offset
574 # for predicting future issues
576 # The basic method is to first convert to a single holding if compressed, then
577 # increment the holding and save the resulting values to @issuances.
579 # returns @issuance_values, an array of hashrefs containing (formatted
580 # label, formatted chronology date, formatted estimated arrival date, and an
581 # array ref of holding subfields as (key, value, key, value ...)) (not a hash
582 # to protect order and possible duplicate keys), and a holding type.
584 sub _generate_issuance_values {
585 my ($mfhd, $options) = @_;
586 my $caption = $options->{caption};
587 my $scap_id = $options->{scap_id};
588 my $num_to_predict = $options->{num_to_predict};
589 my $predict_from = $options->{predict_from}; # issuance to predict from
590 #my $last_rec_date = $options->{last_rec_date}; # expected or actual
592 # TODO: add support for predicting serials with no chronology by passing in
593 # a last_pub_date option?
596 # Only needed for 'real' MFHD records, not our temp records
597 # my $link_id = $caption->link_id;
598 # if(!$predict_from) {
599 # my $htag = $caption->tag;
600 # $htag =~ s/^85/86/;
601 # my @holdings = $mfhd->holdings($htag, $link_id);
602 # my $last_holding = $holdings[-1];
604 # #if ($last_holding->is_compressed) {
605 # # $last_holding->compressed_to_last; # convert to last in range
607 # $predict_from = $last_holding;
611 $predict_from->notes('public', []);
612 # add a note marker for system use (?)
613 $predict_from->notes('private', ['AUTOGEN']);
615 my $strp = new DateTime::Format::Strptime(pattern => '%F');
618 my @predictions = $mfhd->generate_predictions({'base_holding' => $predict_from, 'num_to_predict' => $num_to_predict});
619 foreach my $prediction (@predictions) {
620 $pub_date = $strp->parse_datetime($prediction->chron_to_date);
625 label => $prediction->format,
626 date_published => $pub_date,
627 #date_expected => $date_expected->strftime('%F'),
628 holding_code => [$prediction->indicator(1),$prediction->indicator(2),$prediction->subfields_list],
629 holding_type => $MFHD_NAMES_BY_TAG{$caption->tag},
630 caption_and_pattern => $scap_id
635 return @issuance_values;
638 sub _revive_caption {
641 my $pattern_code = $scap->pattern_code;
644 my $pattern_parts = OpenSRF::Utils::JSON->JSON2perl($pattern_code);
645 unshift(@$pattern_parts, $MFHD_TAGS_BY_NAME{$scap->type});
646 my $pattern_field = new MARC::Field(@$pattern_parts);
648 # build MFHD::Caption
649 return new MFHD::Caption($pattern_field);
652 sub _revive_holding {
653 my $holding_code = shift;
654 my $caption_field = shift;
658 my $holding_parts = OpenSRF::Utils::JSON->JSON2perl($holding_code);
659 my $captag = $caption_field->tag;
660 $captag =~ s/^85/86/;
661 unshift(@$holding_parts, $captag);
662 my $holding_field = new MARC::Field(@$holding_parts);
664 # build MFHD::Holding
665 return new MFHD::Holding($seqno, $holding_field, $caption_field);
668 __PACKAGE__->register_method(
669 method => 'unitize_items',
670 api_name => 'open-ils.serial.receive_items',
674 desc => 'Marks an item as received, updates the shelving unit (creating a new shelving unit if needed), and updates the summaries',
677 desc => 'array of serial items',
682 desc => 'Returns number of received items',
689 my ($self, $conn, $auth, $items) = @_;
691 my( $reqr, $evt ) = $U->checkses($auth);
693 my $editor = new_editor(requestor => $reqr, xact => 1);
694 $self->api_name =~ /serial\.(\w*)_items/;
698 my %found_stream_ids;
701 my %stream_ids_by_unit_id;
704 my %sdist_by_unit_id;
705 my %sdist_by_stream_id;
707 my $new_unit_id; # id for '-2' units to share
708 foreach my $item (@$items) {
709 # for debugging only, TODO: delete
710 if (!ref $item) { # hopefully we got an id instead
711 $item = $editor->retrieve_serial_item($item);
714 my $unit_id = ref($item->unit) ? $item->unit->id : $item->unit;
715 my $stream_id = ref($item->stream) ? $item->stream->id : $item->stream;
716 my $issuance_id = ref($item->issuance) ? $item->issuance->id : $item->issuance;
717 #TODO: evt on any missing ids
719 if ($mode eq 'receive') {
720 $item->date_received('now');
721 $item->status('Received');
723 $item->status('Bindery');
726 # check for types to trigger summary updates
728 if (!ref $item->issuance) {
729 my $scaps = $editor->search_serial_caption_and_pattern([{"+siss" => {"id" => $issuance_id}}, { "join" => {"siss" => {}} }]);
731 } elsif (!ref $item->issuance->caption_and_pattern) {
732 $scap = $editor->retrieve_serial_caption_and_pattern($item->issuance->caption_and_pattern);
734 $scap = $editor->issuance->caption_and_pattern;
736 if (!exists($found_types{$stream_id})) {
737 $found_types{$stream_id} = {};
739 $found_types{$stream_id}->{$scap->type} = 1;
741 # create unit if needed
742 if ($unit_id == -1 or (!$new_unit_id and $unit_id == -2)) { # create unit per item
744 my $sdists = $editor->search_serial_distribution([{"+sstr" => {"id" => $stream_id}}, { "join" => {"sstr" => {}} }]);
745 $unit = _build_unit($editor, $sdists->[0], $mode);
746 my $evt = _create_sunit($editor, $unit);
748 if ($unit_id == -2) {
749 $new_unit_id = $unit->id;
750 $unit_id = $new_unit_id;
752 $unit_id = $unit->id;
754 $item->unit($unit_id);
756 # get unit with 'DEFAULT's and save unit and sdist for later use
757 $unit = $editor->retrieve_serial_unit($unit->id);
758 $unit_map{$unit_id} = $unit;
759 $sdist_by_unit_id{$unit_id} = $sdists->[0];
760 $sdist_by_stream_id{$stream_id} = $sdists->[0];
761 } elsif ($unit_id == -2) { # create one unit for all '-2' items
762 $unit_id = $new_unit_id;
763 $item->unit($unit_id);
766 $found_unit_ids{$unit_id} = 1;
767 $found_stream_ids{$stream_id} = 1;
769 # save the stream_id for this unit_id
770 # TODO: prevent items from different streams in same unit? (perhaps in interface)
771 $stream_ids_by_unit_id{$unit_id} = $stream_id;
773 my $evt = _update_sitem($editor, undef, $item);
777 # deal with unit level labels
778 foreach my $unit_id (keys %found_unit_ids) {
780 # get all the needed issuances for unit
781 my $issuances = $editor->search_serial_issuance([ {"+sitem" => {"unit" => $unit_id, "status" => "Received"}}, {"join" => {"sitem" => {}}, "order_by" => {"siss" => "date_published"}} ]);
782 #TODO: evt on search failure
784 my ($mfhd, $formatted_parts) = _summarize_contents($editor, $issuances);
786 # special case for single formatted_part (may have summarized version)
787 if (@$formatted_parts == 1) {
788 #TODO: MFHD.pm should have a 'format_summary' method for this
791 # retrieve and update unit contents
795 # if we just created the unit, we will already have it and the distribution stored
796 if (exists $unit_map{$unit_id}) {
797 $sunit = $unit_map{$unit_id};
798 $sdist = $sdist_by_unit_id{$unit_id};
800 $sunit = $editor->retrieve_serial_unit($unit_id);
801 $sdist = $editor->search_serial_distribution([{"+sstr" => {"id" => $stream_ids_by_unit_id{$unit_id}}}, { "join" => {"sstr" => {}} }]);
802 $sdist = $sdist->[0];
805 $sunit->detailed_contents($sdist->unit_label_prefix . ' '
806 . join(', ', @$formatted_parts) . ' '
807 . $sdist->unit_label_suffix);
809 $sunit->summary_contents($sunit->detailed_contents); #TODO: change this when real summary contents are available
811 # create sort_key by left padding numbers to 6 digits
812 my $sort_key = $sunit->detailed_contents;
813 $sort_key =~ s/(\d+)/sprintf '%06d', $1/eg; # this may need improvement
814 $sunit->sort_key($sort_key);
816 if ($mode eq 'bind') {
817 $sunit->status(2); # set to 'Bindery' status
820 my $evt = _update_sunit($editor, undef, $sunit);
824 # TODO: cleanup 'dead' units (units which are now emptied of their items)
826 if ($mode eq 'receive') { # the summary holdings do not change when binding
827 # deal with stream level summaries
828 # summaries will be built from the "primary" stream only, that is, the stream with the lowest ID per distribution
829 # (TODO: consider direct designation)
830 my %primary_streams_by_sdist;
831 my %streams_by_sdist;
833 # see if we have primary streams, and if so, associate them with their distributions
834 foreach my $stream_id (keys %found_stream_ids) {
836 if (exists $sdist_by_stream_id{$stream_id}) {
837 $sdist = $sdist_by_stream_id{$stream_id};
839 $sdist = $editor->search_serial_distribution([{"+sstr" => {"id" => $stream_id}}, { "join" => {"sstr" => {}} }]);
840 $sdist = $sdist->[0];
843 if (!exists($streams_by_sdist{$sdist->id})) {
844 $streams = $editor->search_serial_stream([{"distribution" => $sdist->id}, {"order_by" => {"sstr" => "id"}}]);
845 $streams_by_sdist{$sdist->id} = $streams;
847 $streams = $streams_by_sdist{$sdist->id};
849 $primary_streams_by_sdist{$sdist->id} = $streams->[0] if ($stream_id == $streams->[0]->id);
852 # retrieve and update summaries for each affected primary stream's distribution
853 foreach my $sdist_id (keys %primary_streams_by_sdist) {
854 my $stream = $primary_streams_by_sdist{$sdist_id};
855 my $stream_id = $stream->id;
856 # get all the needed issuances for stream
857 # FIXME: search in Bindery/Bound/Not Published? as well as Received
858 foreach my $type (keys %{$found_types{$stream_id}}) {
859 my $issuances = $editor->search_serial_issuance([ {"+sitem" => {"stream" => $stream_id, "status" => "Received"}, "+scap" => {"type" => $type}}, {"join" => {"sitem" => {}, "scap" => {}}, "order_by" => {"siss" => "date_published"}} ]);
860 #TODO: evt on search failure
862 my ($mfhd, $formatted_parts) = _summarize_contents($editor, $issuances);
864 # retrieve and update the generated_coverage of the summary
865 my $search_method = "search_serial_${type}_summary";
866 my $summary = $editor->$search_method([{"distribution" => $sdist_id}]);
867 $summary = $summary->[0];
868 $summary->generated_coverage(join(', ', @$formatted_parts));
869 my $update_method = "update_serial_${type}_summary";
870 return $editor->event unless $editor->$update_method($summary);
876 return {'num_items_received' => scalar @$items, 'new_unit_id' => $new_unit_id};
884 my $attr = $mode . '_unit_template';
885 my $template = $editor->retrieve_asset_copy_template($sdist->$attr);
887 my @parts = qw( status location loan_duration fine_level age_protect circulate deposit ref holdable deposit_amount price circ_modifier circ_as_type alert_message opac_visible floating mint_condition );
889 my $unit = new Fieldmapper::serial::unit;
890 foreach my $part (@parts) {
891 my $value = $template->$part;
892 next if !defined($value);
893 $unit->$part($value);
896 # ignore circ_lib in template, set to distribution holding_lib
897 $unit->circ_lib($sdist->holding_lib);
898 $unit->creator($editor->requestor->id);
899 $unit->editor($editor->requestor->id);
900 $attr = $mode . '_call_number';
901 $unit->call_number($sdist->$attr);
902 $unit->barcode('AUTO');
904 $unit->summary_contents('');
905 $unit->detailed_contents('');
911 sub _summarize_contents {
913 my $issuances = shift;
916 my $mfhd = MFHD->new(MARC::Record->new());
919 my @scap_fields_ordered;
922 foreach my $issuance (@$issuances) {
923 my $scap_id = $issuance->caption_and_pattern;
924 next if (!$scap_id); # skip issuances with no caption/pattern
928 # if this is the first appearance of this scap, retrieve it and add it to the temporary record
929 if (!exists $scaps{$issuance->caption_and_pattern}) {
930 $scaps{$scap_id} = $editor->retrieve_serial_caption_and_pattern($scap_id);
931 $scap = $scaps{$scap_id};
932 $scap_field = _revive_caption($scap);
933 $scap_fields{$scap_id} = $scap_field;
934 push(@scap_fields_ordered, $scap_field);
935 $scap_field->update('8' => $link_id);
936 $mfhd->append_fields($scap_field);
939 $scap = $scaps{$scap_id};
940 $scap_field = $scap_fields{$scap_id};
943 $mfhd->append_fields(_revive_holding($issuance->holding_code, $scap_field, $seqno));
948 foreach my $scap_field (@scap_fields_ordered) { #TODO: use generic MFHD "summarize" method, once available
949 my @updated_holdings = $mfhd->get_compressed_holdings($scap_field);
950 foreach my $holding (@updated_holdings) {
951 push(@formatted_parts, $holding->format);
955 return ($mfhd, \@formatted_parts);
958 ##########################################################################
961 __PACKAGE__->register_method(
962 method => 'fetch_notes',
963 api_name => 'open-ils.serial.item_note.retrieve.all',
965 Returns an array of copy note objects.
966 @param args A named hash of parameters including:
967 authtoken : Required if viewing non-public notes
968 item_id : The id of the item whose notes we want to retrieve
969 pub : True if all the caller wants are public notes
970 @return An array of note objects
974 __PACKAGE__->register_method(
975 method => 'fetch_notes',
976 api_name => 'open-ils.serial.subscription_note.retrieve.all',
978 Returns an array of copy note objects.
979 @param args A named hash of parameters including:
980 authtoken : Required if viewing non-public notes
981 subscription_id : The id of the item whose notes we want to retrieve
982 pub : True if all the caller wants are public notes
983 @return An array of note objects
987 __PACKAGE__->register_method(
988 method => 'fetch_notes',
989 api_name => 'open-ils.serial.distribution_note.retrieve.all',
991 Returns an array of copy note objects.
992 @param args A named hash of parameters including:
993 authtoken : Required if viewing non-public notes
994 distribution_id : The id of the item whose notes we want to retrieve
995 pub : True if all the caller wants are public notes
996 @return An array of note objects
1000 # TODO: revisit this method to consider replacing cstore direct calls
1002 my( $self, $connection, $args ) = @_;
1004 $self->api_name =~ /serial\.(\w*)_note/;
1007 my $id = $$args{object_id};
1008 my $authtoken = $$args{authtoken};
1012 return $U->cstorereq(
1013 'open-ils.cstore.direct.serial.'.$type.'_note.search.atomic',
1014 { $type => $id, pub => 't' } );
1016 # FIXME: restore perm check
1017 # ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
1018 # return $evt if $evt;
1019 return $U->cstorereq(
1020 'open-ils.cstore.direct.serial.'.$type.'_note.search.atomic', {$type => $id} );
1026 __PACKAGE__->register_method(
1027 method => 'create_note',
1028 api_name => 'open-ils.serial.item_note.create',
1030 Creates a new item note
1031 @param authtoken The login session key
1032 @param note The note object to create
1033 @return The id of the new note object
1037 __PACKAGE__->register_method(
1038 method => 'create_note',
1039 api_name => 'open-ils.serial.subscription_note.create',
1041 Creates a new subscription note
1042 @param authtoken The login session key
1043 @param note The note object to create
1044 @return The id of the new note object
1048 __PACKAGE__->register_method(
1049 method => 'create_note',
1050 api_name => 'open-ils.serial.distribution_note.create',
1052 Creates a new distribution note
1053 @param authtoken The login session key
1054 @param note The note object to create
1055 @return The id of the new note object
1060 my( $self, $connection, $authtoken, $note ) = @_;
1062 $self->api_name =~ /serial\.(\w*)_note/;
1065 my $e = new_editor(xact=>1, authtoken=>$authtoken);
1066 return $e->event unless $e->checkauth;
1068 # FIXME: restore permission support
1069 # my $item = $e->retrieve_serial_item(
1075 # return $e->event unless
1076 # $e->allowed('CREATE_COPY_NOTE', $item->call_number->owning_lib);
1078 $note->create_date('now');
1079 $note->creator($e->requestor->id);
1080 $note->pub( ($U->is_true($note->pub)) ? 't' : 'f' );
1083 my $method = "create_serial_${type}_note";
1084 $e->$method($note) or return $e->event;
1089 __PACKAGE__->register_method(
1090 method => 'delete_note',
1091 api_name => 'open-ils.serial.item_note.delete',
1093 Deletes an existing item note
1094 @param authtoken The login session key
1095 @param noteid The id of the note to delete
1096 @return 1 on success - Event otherwise.
1100 __PACKAGE__->register_method(
1101 method => 'delete_note',
1102 api_name => 'open-ils.serial.subscription_note.delete',
1104 Deletes an existing subscription note
1105 @param authtoken The login session key
1106 @param noteid The id of the note to delete
1107 @return 1 on success - Event otherwise.
1111 __PACKAGE__->register_method(
1112 method => 'delete_note',
1113 api_name => 'open-ils.serial.distribution_note.delete',
1115 Deletes an existing distribution note
1116 @param authtoken The login session key
1117 @param noteid The id of the note to delete
1118 @return 1 on success - Event otherwise.
1123 my( $self, $conn, $authtoken, $noteid ) = @_;
1125 $self->api_name =~ /serial\.(\w*)_note/;
1128 my $e = new_editor(xact=>1, authtoken=>$authtoken);
1129 return $e->die_event unless $e->checkauth;
1131 my $method = "retrieve_serial_${type}_note";
1132 my $note = $e->$method([
1134 ]) or return $e->die_event;
1136 # FIXME: restore permissions check
1137 # if( $note->creator ne $e->requestor->id ) {
1138 # return $e->die_event unless
1139 # $e->allowed('DELETE_COPY_NOTE', $note->item->call_number->owning_lib);
1142 $method = "delete_serial_${type}_note";
1143 $e->$method($note) or return $e->die_event;
1149 ##########################################################################
1150 # subscription methods
1152 __PACKAGE__->register_method(
1153 method => 'fleshed_ssub_alter',
1154 api_name => 'open-ils.serial.subscription.fleshed.batch.update',
1158 desc => 'Receives an array of one or more subscriptions and updates the database as needed',
1160 name => 'authtoken',
1161 desc => 'Authtoken for current user session',
1165 name => 'subscriptions',
1166 desc => 'Array of fleshed subscriptions',
1172 desc => 'Returns 1 if successful, event if failed',
1178 sub fleshed_ssub_alter {
1179 my( $self, $conn, $auth, $ssubs ) = @_;
1180 return 1 unless ref $ssubs;
1181 my( $reqr, $evt ) = $U->checkses($auth);
1182 return $evt if $evt;
1183 my $editor = new_editor(requestor => $reqr, xact => 1);
1184 my $override = $self->api_name =~ /override/;
1186 # TODO: permission check
1187 # return $editor->event unless
1188 # $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
1190 for my $ssub (@$ssubs) {
1192 my $ssubid = $ssub->id;
1194 if( $ssub->isdeleted ) {
1195 $evt = _delete_ssub( $editor, $override, $ssub);
1196 } elsif( $ssub->isnew ) {
1197 _cleanse_dates($ssub, ['start_date','end_date']);
1198 $evt = _create_ssub( $editor, $ssub );
1200 _cleanse_dates($ssub, ['start_date','end_date']);
1201 $evt = _update_ssub( $editor, $override, $ssub );
1206 $logger->info("fleshed subscription-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
1210 $logger->debug("subscription-alter: done updating subscription batch");
1212 $logger->info("fleshed subscription-alter successfully updated ".scalar(@$ssubs)." subscriptions");
1217 my ($editor, $override, $ssub) = @_;
1218 $logger->info("subscription-alter: delete subscription ".OpenSRF::Utils::JSON->perl2JSON($ssub));
1219 my $sdists = $editor->search_serial_distribution(
1220 { subscription => $ssub->id }, { limit => 1 } ); #TODO: 'deleted' support?
1221 my $cps = $editor->search_serial_caption_and_pattern(
1222 { subscription => $ssub->id }, { limit => 1 } ); #TODO: 'deleted' support?
1223 my $sisses = $editor->search_serial_issuance(
1224 { subscription => $ssub->id }, { limit => 1 } ); #TODO: 'deleted' support?
1225 return OpenILS::Event->new(
1226 'SERIAL_SUBSCRIPTION_NOT_EMPTY', payload => $ssub->id ) if (@$sdists or @$cps or @$sisses);
1228 return $editor->event unless $editor->delete_serial_subscription($ssub);
1233 my ($editor, $ssub) = @_;
1235 $logger->info("subscription-alter: new subscription ".OpenSRF::Utils::JSON->perl2JSON($ssub));
1236 return $editor->event unless $editor->create_serial_subscription($ssub);
1241 my ($editor, $override, $ssub) = @_;
1243 $logger->info("subscription-alter: retrieving subscription ".$ssub->id);
1244 my $orig_ssub = $editor->retrieve_serial_subscription($ssub->id);
1246 $logger->info("subscription-alter: original subscription ".OpenSRF::Utils::JSON->perl2JSON($orig_ssub));
1247 $logger->info("subscription-alter: updated subscription ".OpenSRF::Utils::JSON->perl2JSON($ssub));
1248 return $editor->event unless $editor->update_serial_subscription($ssub);
1252 __PACKAGE__->register_method(
1253 method => "fleshed_serial_subscription_retrieve_batch",
1255 api_name => "open-ils.serial.subscription.fleshed.batch.retrieve"
1258 sub fleshed_serial_subscription_retrieve_batch {
1259 my( $self, $client, $ids ) = @_;
1260 # FIXME: permissions?
1261 $logger->info("Fetching fleshed subscriptions @$ids");
1262 return $U->cstorereq(
1263 "open-ils.cstore.direct.serial.subscription.search.atomic",
1266 flesh_fields => {ssub => [ qw/owning_lib notes/ ]}
1270 __PACKAGE__->register_method(
1271 method => "retrieve_sub_tree",
1273 api_name => "open-ils.serial.subscription_tree.retrieve"
1276 __PACKAGE__->register_method(
1277 method => "retrieve_sub_tree",
1278 api_name => "open-ils.serial.subscription_tree.global.retrieve"
1281 sub retrieve_sub_tree {
1283 my( $self, $client, $user_session, $docid, @org_ids ) = @_;
1285 if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
1289 # TODO: permission support
1290 if(!@org_ids and $user_session) {
1292 OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
1293 @org_ids = ($user_obj->home_ou);
1296 if( $self->api_name =~ /global/ ) {
1297 return _build_subs_list( { record_entry => $docid } ); # TODO: filter for !deleted, or active?
1302 for my $orgid (@org_ids) {
1303 my $subs = _build_subs_list(
1304 { record_entry => $docid, owning_lib => $orgid } );# TODO: filter for !deleted, or active?
1305 push( @all_subs, @$subs );
1314 sub _build_subs_list {
1315 my $search_hash = shift;
1317 #$search_hash->{deleted} = 'f';
1318 my $e = new_editor();
1320 my $subs = $e->search_serial_subscription([$search_hash, { 'order_by' => {'ssub' => 'id'} }]);
1324 for my $sub (@$subs) {
1326 # TODO: filter on !deleted?
1327 my $dists = $e->search_serial_distribution(
1328 [{ subscription => $sub->id }, { 'order_by' => {'sdist' => 'label'} }]
1331 #$dists = [ sort { $a->label cmp $b->label } @$dists ];
1333 $sub->distributions($dists);
1335 # TODO: filter on !deleted?
1336 my $issuances = $e->search_serial_issuance(
1337 [{ subscription => $sub->id }, { 'order_by' => {'siss' => 'label'} }]
1340 #$issuances = [ sort { $a->label cmp $b->label } @$issuances ];
1341 $sub->issuances($issuances);
1343 # TODO: filter on !deleted?
1344 my $scaps = $e->search_serial_caption_and_pattern(
1345 [{ subscription => $sub->id }, { 'order_by' => {'scap' => 'id'} }]
1348 #$scaps = [ sort { $a->id cmp $b->id } @$scaps ];
1349 $sub->scaps($scaps);
1350 push( @built_subs, $sub );
1353 return \@built_subs;
1357 __PACKAGE__->register_method(
1358 method => "subscription_orgs_for_title",
1360 api_name => "open-ils.serial.subscription.retrieve_orgs_by_title"
1363 sub subscription_orgs_for_title {
1364 my( $self, $client, $record_id ) = @_;
1366 my $subs = $U->simple_scalar_request(
1368 "open-ils.cstore.direct.serial.subscription.search.atomic",
1369 { record_entry => $record_id }); # TODO: filter on !deleted?
1371 my $orgs = { map {$_->owning_lib => 1 } @$subs };
1372 return [ keys %$orgs ];
1376 ##########################################################################
1377 # distribution methods
1379 __PACKAGE__->register_method(
1380 method => 'fleshed_sdist_alter',
1381 api_name => 'open-ils.serial.distribution.fleshed.batch.update',
1385 desc => 'Receives an array of one or more distributions and updates the database as needed',
1387 name => 'authtoken',
1388 desc => 'Authtoken for current user session',
1392 name => 'distributions',
1393 desc => 'Array of fleshed distributions',
1399 desc => 'Returns 1 if successful, event if failed',
1405 sub fleshed_sdist_alter {
1406 my( $self, $conn, $auth, $sdists ) = @_;
1407 return 1 unless ref $sdists;
1408 my( $reqr, $evt ) = $U->checkses($auth);
1409 return $evt if $evt;
1410 my $editor = new_editor(requestor => $reqr, xact => 1);
1411 my $override = $self->api_name =~ /override/;
1413 # TODO: permission check
1414 # return $editor->event unless
1415 # $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
1417 for my $sdist (@$sdists) {
1418 my $sdistid = $sdist->id;
1420 if( $sdist->isdeleted ) {
1421 $evt = _delete_sdist( $editor, $override, $sdist);
1422 } elsif( $sdist->isnew ) {
1423 $evt = _create_sdist( $editor, $sdist );
1425 $evt = _update_sdist( $editor, $override, $sdist );
1430 $logger->info("fleshed distribution-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
1434 $logger->debug("distribution-alter: done updating distribution batch");
1436 $logger->info("fleshed distribution-alter successfully updated ".scalar(@$sdists)." distributions");
1441 my ($editor, $override, $sdist) = @_;
1442 $logger->info("distribution-alter: delete distribution ".OpenSRF::Utils::JSON->perl2JSON($sdist));
1443 return $editor->event unless $editor->delete_serial_distribution($sdist);
1448 my ($editor, $sdist) = @_;
1450 $logger->info("distribution-alter: new distribution ".OpenSRF::Utils::JSON->perl2JSON($sdist));
1451 return $editor->event unless $editor->create_serial_distribution($sdist);
1453 # create summaries too
1454 my $summary = new Fieldmapper::serial::basic_summary;
1455 $summary->distribution($sdist->id);
1456 $summary->generated_coverage('');
1457 return $editor->event unless $editor->create_serial_basic_summary($summary);
1458 $summary = new Fieldmapper::serial::supplement_summary;
1459 $summary->distribution($sdist->id);
1460 $summary->generated_coverage('');
1461 return $editor->event unless $editor->create_serial_supplement_summary($summary);
1462 $summary = new Fieldmapper::serial::index_summary;
1463 $summary->distribution($sdist->id);
1464 $summary->generated_coverage('');
1465 return $editor->event unless $editor->create_serial_index_summary($summary);
1467 # create a starter stream (TODO: reconsider this)
1468 my $stream = new Fieldmapper::serial::stream;
1469 $stream->distribution($sdist->id);
1470 return $editor->event unless $editor->create_serial_stream($stream);
1476 my ($editor, $override, $sdist) = @_;
1478 $logger->info("distribution-alter: retrieving distribution ".$sdist->id);
1479 my $orig_sdist = $editor->retrieve_serial_distribution($sdist->id);
1481 $logger->info("distribution-alter: original distribution ".OpenSRF::Utils::JSON->perl2JSON($orig_sdist));
1482 $logger->info("distribution-alter: updated distribution ".OpenSRF::Utils::JSON->perl2JSON($sdist));
1483 return $editor->event unless $editor->update_serial_distribution($sdist);
1487 __PACKAGE__->register_method(
1488 method => "fleshed_serial_distribution_retrieve_batch",
1490 api_name => "open-ils.serial.distribution.fleshed.batch.retrieve"
1493 sub fleshed_serial_distribution_retrieve_batch {
1494 my( $self, $client, $ids ) = @_;
1495 # FIXME: permissions?
1496 $logger->info("Fetching fleshed distributions @$ids");
1497 return $U->cstorereq(
1498 "open-ils.cstore.direct.serial.distribution.search.atomic",
1501 flesh_fields => {sdist => [ qw/ holding_lib receive_call_number receive_unit_template bind_call_number bind_unit_template streams / ]}
1505 ##########################################################################
1506 # caption and pattern methods
1508 __PACKAGE__->register_method(
1509 method => 'scap_alter',
1510 api_name => 'open-ils.serial.caption_and_pattern.batch.update',
1514 desc => 'Receives an array of one or more caption and patterns and updates the database as needed',
1516 name => 'authtoken',
1517 desc => 'Authtoken for current user session',
1522 desc => 'Array of caption and patterns',
1528 desc => 'Returns 1 if successful, event if failed',
1535 my( $self, $conn, $auth, $scaps ) = @_;
1536 return 1 unless ref $scaps;
1537 my( $reqr, $evt ) = $U->checkses($auth);
1538 return $evt if $evt;
1539 my $editor = new_editor(requestor => $reqr, xact => 1);
1540 my $override = $self->api_name =~ /override/;
1542 # TODO: permission check
1543 # return $editor->event unless
1544 # $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
1546 for my $scap (@$scaps) {
1547 my $scapid = $scap->id;
1549 if( $scap->isdeleted ) {
1550 $evt = _delete_scap( $editor, $override, $scap);
1551 } elsif( $scap->isnew ) {
1552 $evt = _create_scap( $editor, $scap );
1554 $evt = _update_scap( $editor, $override, $scap );
1559 $logger->info("caption_and_pattern-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
1563 $logger->debug("caption_and_pattern-alter: done updating caption_and_pattern batch");
1565 $logger->info("caption_and_pattern-alter successfully updated ".scalar(@$scaps)." caption_and_patterns");
1570 my ($editor, $override, $scap) = @_;
1571 $logger->info("caption_and_pattern-alter: delete caption_and_pattern ".OpenSRF::Utils::JSON->perl2JSON($scap));
1572 my $sisses = $editor->search_serial_issuance(
1573 { caption_and_pattern => $scap->id }, { limit => 1 } ); #TODO: 'deleted' support?
1574 return OpenILS::Event->new(
1575 'SERIAL_CAPTION_AND_PATTERN_HAS_ISSUANCES', payload => $scap->id ) if (@$sisses);
1577 return $editor->event unless $editor->delete_serial_caption_and_pattern($scap);
1582 my ($editor, $scap) = @_;
1584 $logger->info("caption_and_pattern-alter: new caption_and_pattern ".OpenSRF::Utils::JSON->perl2JSON($scap));
1585 return $editor->event unless $editor->create_serial_caption_and_pattern($scap);
1590 my ($editor, $override, $scap) = @_;
1592 $logger->info("caption_and_pattern-alter: retrieving caption_and_pattern ".$scap->id);
1593 my $orig_scap = $editor->retrieve_serial_caption_and_pattern($scap->id);
1595 $logger->info("caption_and_pattern-alter: original caption_and_pattern ".OpenSRF::Utils::JSON->perl2JSON($orig_scap));
1596 $logger->info("caption_and_pattern-alter: updated caption_and_pattern ".OpenSRF::Utils::JSON->perl2JSON($scap));
1597 return $editor->event unless $editor->update_serial_caption_and_pattern($scap);
1601 __PACKAGE__->register_method(
1602 method => "serial_caption_and_pattern_retrieve_batch",
1604 api_name => "open-ils.serial.caption_and_pattern.batch.retrieve"
1607 sub serial_caption_and_pattern_retrieve_batch {
1608 my( $self, $client, $ids ) = @_;
1609 $logger->info("Fetching caption_and_patterns @$ids");
1610 return $U->cstorereq(
1611 "open-ils.cstore.direct.serial.caption_and_pattern.search.atomic",
1616 __PACKAGE__->register_method(
1617 "method" => "bre_by_identifier",
1618 "api_name" => "open-ils.serial.biblio.record_entry.by_identifier",
1621 "desc" => "Find instances of biblio.record_entry given a search token" .
1622 " that could be a value for any identifier defined in " .
1623 "config.metabib_field",
1625 {"desc" => "Search token", "type" => "string"},
1626 {"desc" => "Options: require_subscriptions, add_mvr, is_actual_id" .
1627 " (all boolean)", "type" => "object"}
1630 "desc" => "Any matching BREs, or if the add_mvr option is true, " .
1631 "objects with a 'bre' key/value pair, and an 'mvr' " .
1632 "key-value pair. BREs have subscriptions fleshed on.",
1638 sub bre_by_identifier {
1639 my ($self, $client, $term, $options) = @_;
1641 return new OpenILS::Event("BAD_PARAMS") unless $term;
1644 my $e = new_editor();
1648 if ($options->{"is_actual_id"}) {
1652 $e->search_config_metabib_field({"field_class" => "identifier"})
1653 or return $e->die_event;
1655 my @identifiers = map { $_->name } @$cmf;
1656 my $query = join(" || ", map { "id|$_: $term" } @identifiers);
1658 my $search = create OpenSRF::AppSession("open-ils.search");
1659 my $search_result = $search->request(
1660 "open-ils.search.biblio.multiclass.query.staff", {}, $query
1662 $search->disconnect;
1664 # Un-nest results. They tend to look like [[1],[2],[3]] for some reason.
1665 @ids = map { @{$_} } @{$search_result->{"ids"}};
1673 my $bre = $e->search_biblio_record_entry([
1675 "flesh" => 2, "flesh_fields" => {
1676 "bre" => ["subscriptions"],
1677 "ssub" => ["owning_lib"]
1680 ]) or return $e->die_event;
1682 if (@$bre && $options->{"require_subscriptions"}) {
1683 $bre = [ grep { @{$_->subscriptions} } @$bre ];
1688 if (@$bre) { # re-evaluate after possible grep
1689 if ($options->{"add_mvr"}) {
1691 {"bre" => $_, "mvr" => _get_mvr($_->id)}
1694 $client->respond($_) foreach (@$bre);
1701 __PACKAGE__->register_method(
1702 "method" => "get_receivable_items",
1703 "api_name" => "open-ils.serial.items.receivable.by_subscription",
1706 "desc" => "Return all receivable items under a given subscription",
1708 {"desc" => "Authtoken", "type" => "string"},
1709 {"desc" => "Subscription ID", "type" => "number"},
1712 "desc" => "All receivable items under a given subscription",
1718 __PACKAGE__->register_method(
1719 "method" => "get_receivable_items",
1720 "api_name" => "open-ils.serial.items.receivable.by_issuance",
1723 "desc" => "Return all receivable items under a given issuance",
1725 {"desc" => "Authtoken", "type" => "string"},
1726 {"desc" => "Issuance ID", "type" => "number"},
1729 "desc" => "All receivable items under a given issuance",
1735 sub get_receivable_items {
1736 my ($self, $client, $auth, $term) = @_;
1738 my $e = new_editor("authtoken" => $auth);
1739 return $e->die_event unless $e->checkauth;
1743 my $by = ($self->api_name =~ /by_(\w+)$/)[0];
1746 "issuance" => {"issuance" => $term},
1747 "subscription" => {"+siss" => {"subscription" => $term}}
1750 my $item_ids = $e->json_query(
1752 "select" => {"sitem" => ["id"]},
1753 "from" => {"sitem" => "siss"},
1755 %{$where{$by}}, "date_received" => undef
1757 "order_by" => {"sitem" => ["id"]}
1759 ) or return $e->die_event;
1761 return undef unless @$item_ids;
1763 foreach (map { $_->{"id"} } @$item_ids) {
1765 $e->retrieve_serial_item([
1769 "sitem" => ["stream", "issuance"],
1770 "sstr" => ["distribution"],
1771 "sdist" => ["holding_lib"]
1782 __PACKAGE__->register_method(
1783 "method" => "get_receivable_issuances",
1784 "api_name" => "open-ils.serial.issuances.receivable",
1787 "desc" => "Return all issuances with receivable items given " .
1788 "a subscription ID",
1790 {"desc" => "Authtoken", "type" => "string"},
1791 {"desc" => "Subscription ID", "type" => "number"},
1794 "desc" => "All issuances with receivable items " .
1795 "(but not the items themselves)", "type" => "object"
1800 sub get_receivable_issuances {
1801 my ($self, $client, $auth, $sub_id) = @_;
1803 my $e = new_editor("authtoken" => $auth);
1804 return $e->die_event unless $e->checkauth;
1808 my $issuance_ids = $e->json_query({
1811 {"transform" => "distinct", "column" => "id"}
1814 "from" => {"siss" => "sitem"},
1816 "subscription" => $sub_id,
1817 "+sitem" => {"date_received" => undef}
1819 }) or return $e->die_event;
1821 $client->respond($e->retrieve_serial_issuance($_->{"id"}))
1822 foreach (@$issuance_ids);
1828 __PACKAGE__->register_method(
1829 "method" => "receive_items_by_id",
1830 "api_name" => "open-ils.serial.items.receive_by_id",
1833 "desc" => "Given sitem IDs, just set their date_received to now()",
1835 {"desc" => "Authtoken", "type" => "string"},
1836 {"desc" => "Serial Item IDs", "type" => "array"},
1839 "desc" => "Stream of updated items", "type" => "object"
1844 sub receive_items_by_id {
1845 my ($self, $client, $auth, $id_list) = @_;
1847 my $e = new_editor("authtoken" => $auth, "xact" => 1);
1848 return $e->die_event unless $e->checkauth;
1852 # for now this function doesn't do nearly enough. simply sets
1853 # date_received to now()
1856 foreach (@$id_list) {
1857 my $sitem = $e->retrieve_serial_item($_) or return $e->die_event;
1859 $sitem->date_received("now");
1860 $e->update_serial_item($sitem) or return $e->die_event;
1862 push @results, $sitem;
1866 $client->respond($_) foreach @results;