1 package OpenILS::Application::Acq::BatchManager;
2 use OpenILS::Application::Acq::Financials;
3 use OpenSRF::AppSession;
4 use OpenSRF::EX qw/:try/;
5 use strict; use warnings;
8 my($class, %args) = @_;
9 my $self = bless(\%args, $class);
17 purchase_order => undef,
23 $self->{ingest_queue} = [];
25 $self->throttle(5) unless $self->throttle;
26 $self->{post_proc_queue} = [];
27 $self->{last_respond_progress} = 0;
33 $self->{conn} = $val if $val;
38 $self->{throttle} = $val if $val;
39 return $self->{throttle};
42 my($self, %other_args) = @_;
43 if($self->throttle and not %other_args) {
45 ($self->{args}->{progress} - $self->{last_respond_progress}) >= $self->throttle
48 $self->conn->respond({ %{$self->{args}}, %other_args });
49 $self->{last_respond_progress} = $self->{args}->{progress};
51 sub respond_complete {
52 my($self, %other_args) = @_;
54 $self->conn->respond_complete({ %{$self->{args}}, %other_args });
55 $self->run_post_response_hooks;
59 # run the post response hook subs, shifting them off as we go
60 sub run_post_response_hooks {
62 (shift @{$self->{post_proc_queue}})->() while @{$self->{post_proc_queue}};
65 # any subs passed to this method will be run after the call to respond_complete
68 push(@{$self->{post_proc_queue}}, $sub);
73 $self->{args}->{total} = $val if defined $val;
74 $self->{args}->{maximum} = $self->{args}->{total};
75 return $self->{args}->{total};
79 $self->{args}->{purchase_order} = $val if $val;
84 $self->{args}->{picklist} = $val if $val;
89 $self->{args}->{lid} += 1;
90 $self->{args}->{progress} += 1;
95 $self->{args}->{li} += 1;
96 $self->{args}->{progress} += 1;
101 $self->{args}->{copies} += 1;
102 $self->{args}->{progress} += 1;
107 $self->{args}->{bibs} += 1;
108 $self->{args}->{progress} += 1;
112 my($self, $amount) = @_;
113 $self->{args}->{debits_accrued} += $amount;
114 $self->{args}->{progress} += 1;
118 my($self, $editor) = @_;
119 $self->{editor} = $editor if defined $editor;
120 return $self->{editor};
124 $self->{args}->{complete} = 1;
129 my($self, $val) = @_;
130 $self->{ingest_ses} = $val if $val;
131 return $self->{ingest_ses};
134 sub push_ingest_queue {
135 my($self, $rec_id) = @_;
137 $self->ingest_ses(OpenSRF::AppSession->connect('open-ils.ingest'))
138 unless $self->ingest_ses;
140 my $req = $self->ingest_ses->request('open-ils.ingest.full.biblio.record', $rec_id);
142 push(@{$self->{ingest_queue}}, $req);
145 sub process_ingest_records {
147 return unless @{$self->{ingest_queue}};
149 for my $req (@{$self->{ingest_queue}}) {
153 $self->{args}->{indexed} += 1;
154 $self->{args}->{progress} += 1;
159 $self->ingest_ses->disconnect;
164 my($self, $org, $key, $val) = @_;
165 $self->{cache}->{$org} = {} unless $self->{cache}->{org};
166 $self->{cache}->{$org}->{$key} = $val if defined $val;
167 return $self->{cache}->{$org}->{$key};
171 package OpenILS::Application::Acq::Order;
172 use base qw/OpenILS::Application/;
173 use strict; use warnings;
174 # ----------------------------------------------------------------------------
175 # Break up each component of the order process and pieces into managable
176 # actions that can be shared across different workflows
177 # ----------------------------------------------------------------------------
179 use OpenSRF::Utils::Logger qw(:logger);
180 use OpenSRF::Utils::JSON;
181 use OpenSRF::AppSession;
182 use OpenILS::Utils::Fieldmapper;
183 use OpenILS::Utils::CStoreEditor q/:funcs/;
184 use OpenILS::Const qw/:const/;
185 use OpenSRF::EX q/:try/;
186 use OpenILS::Application::AppUtils;
187 use OpenILS::Application::Cat::BibCommon;
188 use OpenILS::Application::Cat::AssetCommon;
192 my $U = 'OpenILS::Application::AppUtils';
195 # ----------------------------------------------------------------------------
197 # ----------------------------------------------------------------------------
198 sub create_lineitem {
199 my($mgr, %args) = @_;
200 my $li = Fieldmapper::acq::lineitem->new;
201 $li->creator($mgr->editor->requestor->id);
202 $li->selector($li->creator);
203 $li->editor($li->creator);
204 $li->create_time('now');
205 $li->edit_time('now');
207 $li->$_($args{$_}) for keys %args;
210 $mgr->editor->create_acq_lineitem($li) or return 0;
212 unless($li->estimated_unit_price) {
213 # extract the price from the MARC data
214 my $price = get_li_price_from_attrs($li) or return $li;
215 $li->estimated_unit_price($price);
216 return update_lineitem($mgr, $li);
222 sub get_li_price_from_attr {
224 my $attrs = $li->attributes || $e->search_acq_lineitem_attr({lineitem => $li->id});
226 for my $attr_type (qw/
227 lineitem_local_attr_definition
228 lineitem_prov_attr_definition
229 lineitem_marc_attr_definition/) {
232 $_->attr_name eq 'estimated_price' and
233 $_->attr_type eq $attr_type } @$attrs;
235 return $attr->attr_value if $attr;
242 sub update_lineitem {
244 $li->edit_time('now');
245 $li->editor($mgr->editor->requestor->id);
247 return $mgr->editor->retrieve_acq_lineitem($mgr->editor->data) if
248 $mgr->editor->update_acq_lineitem($li);
253 # ----------------------------------------------------------------------------
254 # Create real holds from patron requests for a given lineitem
255 # ----------------------------------------------------------------------------
256 sub promote_lineitem_holds {
259 my $requests = $mgr->editor->search_acq_user_request(
260 { lineitem => $li->id,
262 [ { need_before => {'>' => 'now'} },
263 { need_before => undef }
268 for my $request ( @$requests ) {
270 $request->eg_bib( $li->eg_bib_id );
271 $mgr->editor->update_acq_user_request( $request ) or return 0;
273 next unless ($U->is_true( $request->hold ));
275 my $hold = Fieldmapper::action::hold_request->new;
276 $hold->usr( $request->usr );
277 $hold->requestor( $request->usr );
278 $hold->request_time( $request->request_date );
279 $hold->pickup_lib( $request->pickup_lib );
280 $hold->request_lib( $request->pickup_lib );
281 $hold->selection_ou( $request->pickup_lib );
282 $hold->phone_notify( $request->phone_notify );
283 $hold->email_notify( $request->email_notify );
284 $hold->expire_time( $request->need_before );
286 if ($request->holdable_formats) {
287 my $mrm = $mgr->editor->search_metabib_metarecord_source_map( { source => $li->eg_bib_id } )->[0];
289 $hold->hold_type( 'M' );
290 $hold->holdable_formats( $request->holdable_formats );
291 $hold->target( $mrm->metarecord );
295 if (!$hold->target) {
296 $hold->hold_type( 'T' );
297 $hold->target( $li->eg_bib_id );
300 $mgr->editor->create_actor_hold_request( $hold ) or return 0;
306 sub delete_lineitem {
308 $li = $mgr->editor->retrieve_acq_lineitem($li) unless ref $li;
310 # delete the attached lineitem_details
311 my $lid_ids = $mgr->editor->search_acq_lineitem_detail({lineitem => $li->id}, {idlist=>1});
312 for my $lid_id (@$lid_ids) {
313 return 0 unless delete_lineitem_detail($mgr, $lid_id);
317 return $mgr->editor->delete_acq_lineitem($li);
320 # begins and commit transactions as it goes
321 sub create_lineitem_list_assets {
322 my($mgr, $li_ids) = @_;
323 return undef if check_import_li_marc_perms($mgr, $li_ids);
325 # create the bibs/volumes/copies and ingest the records
326 for my $li_id (@$li_ids) {
327 $mgr->editor->xact_begin;
328 my $data = create_lineitem_assets($mgr, $li_id) or return undef;
329 $mgr->editor->xact_commit;
330 # XXX ingest is in-db now
331 #$mgr->push_ingest_queue($data->{li}->eg_bib_id) if $data->{new_bib};
334 $mgr->process_ingest_records;
338 # returns event on error, undef on success
339 sub check_import_li_marc_perms {
340 my($mgr, $li_ids) = @_;
342 # if there are any order records that are not linked to
343 # in-db bib records, verify staff has perms to import order records
344 my $order_li = $mgr->editor->search_acq_lineitem(
345 [{id => $li_ids, eg_bib_id => undef}, {limit => 1}], {idlist => 1})->[0];
348 return $mgr->editor->die_event unless
349 $mgr->editor->allowed('IMPORT_ACQ_LINEITEM_BIB_RECORD');
356 # ----------------------------------------------------------------------------
357 # if all of the lineitem details for this lineitem have
358 # been received, mark the lineitem as received
359 # returns 1 on non-received, li on received, 0 on error
360 # ----------------------------------------------------------------------------
362 sub describe_affected_po {
366 OpenILS::Application::Acq::Financials::build_price_summary(
371 "state" => $po->state,
372 "amount_encumbered" => $enc,
373 "amount_spent" => $spent
378 sub check_lineitem_received {
379 my($mgr, $li_id) = @_;
381 my $non_recv = $mgr->editor->search_acq_lineitem_detail(
382 {recv_time => undef, lineitem => $li_id}, {idlist=>1});
384 return 1 if @$non_recv;
386 my $li = $mgr->editor->retrieve_acq_lineitem($li_id);
387 $li->state('received');
388 return update_lineitem($mgr, $li);
391 sub receive_lineitem {
392 my($mgr, $li_id, $skip_complete_check) = @_;
393 my $li = $mgr->editor->retrieve_acq_lineitem($li_id) or return 0;
395 my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
396 {lineitem => $li_id, recv_time => undef}, {idlist => 1});
398 for my $lid_id (@$lid_ids) {
399 receive_lineitem_detail($mgr, $lid_id, 1) or return 0;
403 $li->state('received');
405 $li = update_lineitem($mgr, $li) or return 0;
406 $mgr->post_process( sub { create_lineitem_status_events($mgr, $li_id, 'aur.received'); });
410 $skip_complete_check or (
411 $po = check_purchase_order_received($mgr, $li->purchase_order)
414 my $result = {"li" => {$li->id => {"state" => $li->state}}};
415 $result->{"po"} = describe_affected_po($mgr->editor, $po) if ref $po;
419 sub rollback_receive_lineitem {
420 my($mgr, $li_id) = @_;
421 my $li = $mgr->editor->retrieve_acq_lineitem($li_id) or return 0;
423 my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
424 {lineitem => $li_id, recv_time => {'!=' => undef}}, {idlist => 1});
426 for my $lid_id (@$lid_ids) {
427 rollback_receive_lineitem_detail($mgr, $lid_id, 1) or return 0;
431 $li->state('on-order');
432 return update_lineitem($mgr, $li);
436 sub create_lineitem_status_events {
437 my($mgr, $li_id, $hook) = @_;
439 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
441 my $user_reqs = $mgr->editor->search_acq_user_request([
442 {lineitem => $li_id},
443 {flesh => 1, flesh_fields => {aur => ['usr']}}
446 for my $user_req (@$user_reqs) {
447 my $req = $ses->request('open-ils.trigger.event.autocreate', $hook, $user_req, $user_req->usr->home_ou);
455 # ----------------------------------------------------------------------------
457 # ----------------------------------------------------------------------------
458 sub create_lineitem_detail {
459 my($mgr, %args) = @_;
460 my $lid = Fieldmapper::acq::lineitem_detail->new;
461 $lid->$_($args{$_}) for keys %args;
464 return $mgr->editor->create_acq_lineitem_detail($lid);
468 # flesh out any required data with default values where appropriate
469 sub complete_lineitem_detail {
471 unless($lid->barcode) {
472 my $pfx = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.tmp_barcode_prefix') || 'ACQ';
473 $lid->barcode($pfx.$lid->id);
476 unless($lid->cn_label) {
477 my $pfx = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.tmp_callnumber_prefix') || 'ACQ';
478 $lid->cn_label($pfx.$lid->id);
481 if(!$lid->location and my $loc = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.default_copy_location')) {
482 $lid->location($loc);
485 if(!$lid->circ_modifier and my $mod = get_default_circ_modifier($mgr, $lid->owning_lib)) {
486 $lid->circ_modifier($mod);
489 $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
493 sub get_default_circ_modifier {
495 my $mod = $mgr->cache($org, 'def_circ_mod');
497 $mod = $U->ou_ancestor_setting_value($org, 'acq.default_circ_modifier');
498 return $mgr->cache($org, 'def_circ_mod', $mod) if $mod;
502 sub delete_lineitem_detail {
504 $lid = $mgr->editor->retrieve_acq_lineitem_detail($lid) unless ref $lid;
505 return $mgr->editor->delete_acq_lineitem_detail($lid);
509 sub receive_lineitem_detail {
510 my($mgr, $lid_id, $skip_complete_check) = @_;
511 my $e = $mgr->editor;
513 my $lid = $e->retrieve_acq_lineitem_detail([
517 acqlid => ['fund_debit']
522 return 1 if $lid->recv_time;
524 $lid->recv_time('now');
525 $e->update_acq_lineitem_detail($lid) or return 0;
527 my $copy = $e->retrieve_asset_copy($lid->eg_copy_id) or return 0;
528 $copy->status(OILS_COPY_STATUS_IN_PROCESS);
529 $copy->edit_date('now');
530 $copy->editor($e->requestor->id);
531 $e->update_asset_copy($copy) or return 0;
535 return 1 if $skip_complete_check;
537 my $li = check_lineitem_received($mgr, $lid->lineitem) or return 0;
538 return 1 if $li == 1; # li not received
540 return check_purchase_order_received($mgr, $li->purchase_order) or return 0;
544 sub rollback_receive_lineitem_detail {
545 my($mgr, $lid_id) = @_;
546 my $e = $mgr->editor;
548 my $lid = $e->retrieve_acq_lineitem_detail([
552 acqlid => ['fund_debit']
557 return 1 unless $lid->recv_time;
559 $lid->clear_recv_time;
560 $e->update_acq_lineitem_detail($lid) or return 0;
562 my $copy = $e->retrieve_asset_copy($lid->eg_copy_id) or return 0;
563 $copy->status(OILS_COPY_STATUS_ON_ORDER);
564 $copy->edit_date('now');
565 $copy->editor($e->requestor->id);
566 $e->update_asset_copy($copy) or return 0;
572 # ----------------------------------------------------------------------------
574 # ----------------------------------------------------------------------------
575 sub set_lineitem_attr {
576 my($mgr, %args) = @_;
577 my $attr_type = $args{attr_type};
579 # first, see if it's already set. May just need to overwrite it
580 my $attr = $mgr->editor->search_acq_lineitem_attr({
581 lineitem => $args{lineitem},
582 attr_type => $args{attr_type},
583 attr_name => $args{attr_name}
587 $attr->attr_value($args{attr_value});
588 return $attr if $mgr->editor->update_acq_lineitem_attr($attr);
593 $attr = Fieldmapper::acq::lineitem_attr->new;
594 $attr->$_($args{$_}) for keys %args;
596 unless($attr->definition) {
597 my $find = "search_acq_$attr_type";
598 my $attr_def_id = $mgr->editor->$find({code => $attr->attr_name}, {idlist=>1})->[0] or return 0;
599 $attr->definition($attr_def_id);
601 return $mgr->editor->create_acq_lineitem_attr($attr);
605 # ----------------------------------------------------------------------------
607 # ----------------------------------------------------------------------------
608 sub create_lineitem_debits {
611 unless($li->estimated_unit_price) {
612 $mgr->editor->event(OpenILS::Event->new('ACQ_LINEITEM_NO_PRICE', payload => $li->id));
613 $mgr->editor->rollback;
617 unless($li->provider) {
618 $mgr->editor->event(OpenILS::Event->new('ACQ_LINEITEM_NO_PROVIDER', payload => $li->id));
619 $mgr->editor->rollback;
623 my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
624 {lineitem => $li->id},
628 for my $lid_id (@$lid_ids) {
630 my $lid = $mgr->editor->retrieve_acq_lineitem_detail([
633 flesh_fields => {acqlid => ['fund']}
637 create_lineitem_detail_debit($mgr, $li, $lid) or return 0;
646 sub create_lineitem_detail_debit {
647 my($mgr, $li, $lid) = @_;
649 my $li_id = ref($li) ? $li->id : $li;
651 unless(ref $li and ref $li->provider) {
652 $li = $mgr->editor->retrieve_acq_lineitem([
655 flesh_fields => {jub => ['provider']},
660 unless(ref $lid and ref $lid->fund) {
661 $lid = $mgr->editor->retrieve_acq_lineitem_detail([
664 flesh_fields => {acqlid => ['fund']}
669 my $amount = $li->estimated_unit_price;
670 if($li->provider->currency_type ne $lid->fund->currency_type) {
672 # At Fund debit creation time, translate into the currency of the fund
673 # TODO: org setting to disable automatic currency conversion at debit create time?
675 $amount = $mgr->editor->json_query({
677 'acq.exchange_ratio',
678 $li->provider->currency_type, # source currency
679 $lid->fund->currency_type, # destination currency
680 $li->estimated_unit_price # source amount
685 my $debit = create_fund_debit(
687 fund => $lid->fund->id,
688 origin_amount => $li->estimated_unit_price,
689 origin_currency_type => $li->provider->currency_type,
693 $lid->fund_debit($debit->id);
694 $lid->fund($lid->fund->id);
695 $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
700 # ----------------------------------------------------------------------------
702 # ----------------------------------------------------------------------------
703 sub create_fund_debit {
704 my($mgr, %args) = @_;
706 # Verify the fund is not being spent beyond the hard stop amount
707 my $fund = $mgr->editor->retrieve_acq_fund($args{fund}) or return 0;
709 if($fund->balance_stop_percent) {
711 my $balance = $mgr->editor->search_acq_fund_combined_balance({fund => $fund->id})->[0];
712 my $allocations = $mgr->editor->search_acq_fund_allocation_total({fund => $fund->id})->[0];
713 $balance = ($balance) ? $balance->amount : 0;
714 $allocations = ($allocations) ? $allocations->amount : 0;
717 $allocations == 0 || # if no allocations were ever made, assume we have hit the stop percent
718 ( ( ( ($balance - $args{amount}) / $allocations ) * 100 ) < $fund->balance_stop_percent))
720 $mgr->editor->event(OpenILS::Event->new(
721 'FUND_EXCEEDS_STOP_PERCENT',
722 payload => {fund => $fund->id, debit_amount => $args{amount}}
728 my $debit = Fieldmapper::acq::fund_debit->new;
729 $debit->debit_type('purchase');
730 $debit->encumbrance('t');
731 $debit->$_($args{$_}) for keys %args;
733 $mgr->add_debit($debit->amount);
734 return $mgr->editor->create_acq_fund_debit($debit);
738 # ----------------------------------------------------------------------------
740 # ----------------------------------------------------------------------------
741 sub create_picklist {
742 my($mgr, %args) = @_;
743 my $picklist = Fieldmapper::acq::picklist->new;
744 $picklist->creator($mgr->editor->requestor->id);
745 $picklist->owner($picklist->creator);
746 $picklist->editor($picklist->creator);
747 $picklist->create_time('now');
748 $picklist->edit_time('now');
749 $picklist->org_unit($mgr->editor->requestor->ws_ou);
750 $picklist->owner($mgr->editor->requestor->id);
751 $picklist->$_($args{$_}) for keys %args;
753 $mgr->picklist($picklist);
754 return $mgr->editor->create_acq_picklist($picklist);
757 sub update_picklist {
758 my($mgr, $picklist) = @_;
759 $picklist = $mgr->editor->retrieve_acq_picklist($picklist) unless ref $picklist;
760 $picklist->edit_time('now');
761 $picklist->editor($mgr->editor->requestor->id);
762 $mgr->picklist($picklist);
763 return $picklist if $mgr->editor->update_acq_picklist($picklist);
767 sub delete_picklist {
768 my($mgr, $picklist) = @_;
769 $picklist = $mgr->editor->retrieve_acq_picklist($picklist) unless ref $picklist;
771 # delete all 'new' lineitems
772 my $li_ids = $mgr->editor->search_acq_lineitem({picklist => $picklist->id, state => 'new'}, {idlist => 1});
773 for my $li_id (@$li_ids) {
774 my $li = $mgr->editor->retrieve_acq_lineitem($li_id);
775 return 0 unless delete_lineitem($mgr, $li);
779 # detach all non-'new' lineitems
780 $li_ids = $mgr->editor->search_acq_lineitem({picklist => $picklist->id, state => {'!=' => 'new'}}, {idlist => 1});
781 for my $li_id (@$li_ids) {
782 my $li = $mgr->editor->retrieve_acq_lineitem($li_id);
784 return 0 unless update_lineitem($mgr, $li);
788 # remove any picklist-specific object perms
789 my $ops = $mgr->editor->search_permission_usr_object_perm_map({object_type => 'acqpl', object_id => ''.$picklist->id});
791 return 0 unless $mgr->editor->delete_usr_object_perm_map($op);
794 return $mgr->editor->delete_acq_picklist($picklist);
797 # ----------------------------------------------------------------------------
799 # ----------------------------------------------------------------------------
800 sub update_purchase_order {
802 $po = $mgr->editor->retrieve_acq_purchase_order($po) unless ref $po;
803 $po->editor($mgr->editor->requestor->id);
804 $po->edit_time('now');
805 $mgr->purchase_order($po);
806 return $mgr->editor->retrieve_acq_purchase_order($mgr->editor->data)
807 if $mgr->editor->update_acq_purchase_order($po);
811 sub create_purchase_order {
812 my($mgr, %args) = @_;
814 # verify the chosen provider is still active
815 my $provider = $mgr->editor->retrieve_acq_provider($args{provider}) or return 0;
816 unless($U->is_true($provider->active)) {
817 $logger->error("provider is not active. cannot create PO");
818 $mgr->editor->event(OpenILS::Event->new('ACQ_PROVIDER_INACTIVE'));
822 my $po = Fieldmapper::acq::purchase_order->new;
823 $po->creator($mgr->editor->requestor->id);
824 $po->editor($mgr->editor->requestor->id);
825 $po->owner($mgr->editor->requestor->id);
826 $po->edit_time('now');
827 $po->create_time('now');
828 $po->state('pending');
829 $po->ordering_agency($mgr->editor->requestor->ws_ou);
830 $po->$_($args{$_}) for keys %args;
832 $mgr->purchase_order($po);
833 return $mgr->editor->create_acq_purchase_order($po);
836 # ----------------------------------------------------------------------------
837 # if all of the lineitems for this PO are received,
838 # mark the PO as received
839 # ----------------------------------------------------------------------------
840 sub check_purchase_order_received {
841 my($mgr, $po_id) = @_;
843 my $non_recv_li = $mgr->editor->search_acq_lineitem(
844 { purchase_order => $po_id,
845 state => {'!=' => 'received'}
848 my $po = $mgr->editor->retrieve_acq_purchase_order($po_id);
849 return $po if @$non_recv_li;
851 $po->state('received');
852 return update_purchase_order($mgr, $po);
856 # ----------------------------------------------------------------------------
857 # Bib, Callnumber, and Copy data
858 # ----------------------------------------------------------------------------
860 sub create_lineitem_assets {
861 my($mgr, $li_id) = @_;
864 my $li = $mgr->editor->retrieve_acq_lineitem([
867 flesh_fields => {jub => ['purchase_order', 'attributes']}
871 # -----------------------------------------------------------------
872 # first, create the bib record if necessary
873 # -----------------------------------------------------------------
875 unless($li->eg_bib_id) {
876 create_bib($mgr, $li) or return 0;
881 # -----------------------------------------------------------------
882 # The lineitem is going live, promote user request holds to real holds
883 # -----------------------------------------------------------------
884 promote_lineitem_holds($mgr, $li) or return 0;
886 my $li_details = $mgr->editor->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
888 # -----------------------------------------------------------------
889 # for each lineitem_detail, create the volume if necessary, create
890 # a copy, and link them all together.
891 # -----------------------------------------------------------------
893 for my $lid_id (@{$li_details}) {
895 my $lid = $mgr->editor->retrieve_acq_lineitem_detail($lid_id) or return 0;
896 next if $lid->eg_copy_id;
898 # use the same callnumber label for all items within this lineitem
899 $lid->cn_label($first_cn) if $first_cn and not $lid->cn_label;
901 # apply defaults if necessary
902 return 0 unless complete_lineitem_detail($mgr, $lid);
904 $first_cn = $lid->cn_label unless $first_cn;
906 my $org = $lid->owning_lib;
907 my $label = $lid->cn_label;
908 my $bibid = $li->eg_bib_id;
910 my $volume = $mgr->cache($org, "cn.$bibid.$label");
912 $volume = create_volume($mgr, $li, $lid) or return 0;
913 $mgr->cache($org, "cn.$bibid.$label", $volume);
915 create_copy($mgr, $volume, $lid) or return 0;
918 return { li => $li, new_bib => $new_bib };
924 my $record = OpenILS::Application::Cat::BibCommon->biblio_record_xml_import(
929 1, # override tcn collisions
932 if($U->event_code($record)) {
933 $mgr->editor->event($record);
934 $mgr->editor->rollback;
938 $li->eg_bib_id($record->id);
940 return update_lineitem($mgr, $li);
944 my($mgr, $li, $lid) = @_;
947 OpenILS::Application::Cat::AssetCommon->find_or_create_volume(
955 $mgr->editor->event($evt);
963 my($mgr, $volume, $lid) = @_;
964 my $copy = Fieldmapper::asset::copy->new;
966 $copy->loan_duration(2);
967 $copy->fine_level(2);
968 $copy->status(OILS_COPY_STATUS_ON_ORDER);
969 $copy->barcode($lid->barcode);
970 $copy->location($lid->location);
971 $copy->call_number($volume->id);
972 $copy->circ_lib($volume->owning_lib);
973 $copy->circ_modifier($lid->circ_modifier);
975 my $evt = OpenILS::Application::Cat::AssetCommon->create_copy($mgr->editor, $volume, $copy);
977 $mgr->editor->event($evt);
982 $lid->eg_copy_id($copy->id);
983 $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
991 # ----------------------------------------------------------------------------
992 # Workflow: Build a selection list from a Z39.50 search
993 # ----------------------------------------------------------------------------
995 __PACKAGE__->register_method(
997 api_name => 'open-ils.acq.picklist.search.z3950',
1000 desc => 'Performs a z3950 federated search and creates a picklist and associated lineitems',
1002 {desc => 'Authentication token', type => 'string'},
1003 {desc => 'Search definition', type => 'object'},
1004 {desc => 'Picklist name, optional', type => 'string'},
1010 my($self, $conn, $auth, $search, $name, $options) = @_;
1011 my $e = new_editor(authtoken=>$auth);
1012 return $e->event unless $e->checkauth;
1013 return $e->event unless $e->allowed('CREATE_PICKLIST');
1015 $search->{limit} ||= 10;
1018 my $ses = OpenSRF::AppSession->create('open-ils.search');
1019 my $req = $ses->request('open-ils.search.z3950.search_class', $auth, $search);
1024 while(my $resp = $req->recv(timeout=>60)) {
1027 my $e = new_editor(requestor=>$e->requestor, xact=>1);
1028 $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1029 $picklist = zsearch_build_pl($mgr, $name);
1033 my $result = $resp->content;
1034 my $count = $result->{count} || 0;
1035 $mgr->total( (($count < $search->{limit}) ? $count : $search->{limit})+1 );
1037 for my $rec (@{$result->{records}}) {
1039 my $li = create_lineitem($mgr,
1040 picklist => $picklist->id,
1041 source_label => $result->{service},
1042 marc => $rec->{marcxml},
1043 eg_bib_id => $rec->{bibid}
1046 if($$options{respond_li}) {
1047 $li->attributes($mgr->editor->search_acq_lineitem_attr({lineitem => $li->id}))
1048 if $$options{flesh_attrs};
1049 $li->clear_marc if $$options{clear_marc};
1050 $mgr->respond(lineitem => $li);
1057 $mgr->editor->commit;
1058 return $mgr->respond_complete;
1061 sub zsearch_build_pl {
1062 my($mgr, $name) = @_;
1065 my $picklist = $mgr->editor->search_acq_picklist({
1066 owner => $mgr->editor->requestor->id,
1070 if($name eq '' and $picklist) {
1071 return 0 unless delete_picklist($mgr, $picklist);
1075 return update_picklist($mgr, $picklist) if $picklist;
1076 return create_picklist($mgr, name => $name);
1080 # ----------------------------------------------------------------------------
1081 # Workflow: Build a selection list / PO by importing a batch of MARC records
1082 # ----------------------------------------------------------------------------
1084 __PACKAGE__->register_method(
1085 method => 'upload_records',
1086 api_name => 'open-ils.acq.process_upload_records',
1090 sub upload_records {
1091 my($self, $conn, $auth, $key) = @_;
1093 my $e = new_editor(authtoken => $auth, xact => 1);
1094 return $e->die_event unless $e->checkauth;
1095 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1097 my $cache = OpenSRF::Utils::Cache->new;
1099 my $data = $cache->get_cache("vandelay_import_spool_$key");
1100 my $purpose = $data->{purpose};
1101 my $filename = $data->{path};
1102 my $provider = $data->{provider};
1103 my $picklist = $data->{picklist};
1104 my $create_po = $data->{create_po};
1105 my $activate_po = $data->{activate_po};
1106 my $ordering_agency = $data->{ordering_agency};
1107 my $create_assets = $data->{create_assets};
1111 unless(-r $filename) {
1112 $logger->error("unable to read MARC file $filename");
1114 return OpenILS::Event->new('FILE_UPLOAD_ERROR', payload => {filename => $filename});
1117 $provider = $e->retrieve_acq_provider($provider) or return $e->die_event;
1120 $picklist = $e->retrieve_acq_picklist($picklist) or return $e->die_event;
1121 if($picklist->owner != $e->requestor->id) {
1122 return $e->die_event unless
1123 $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
1125 $mgr->picklist($picklist);
1130 $po = create_purchase_order($mgr,
1131 ordering_agency => $ordering_agency,
1132 provider => $provider->id,
1134 ) or return $mgr->editor->die_event;
1137 $logger->info("acq processing MARC file=$filename");
1139 my $marctype = 'USMARC'; # ?
1140 my $batch = new MARC::Batch ($marctype, $filename);
1155 } catch Error with {
1157 $logger->warn("Proccessing of record $count in set $key failed with error $err. Skipping this record");
1164 ($xml = $r->as_xml_record()) =~ s/\n//sog;
1165 $xml =~ s/^<\?xml.+\?\s*>//go;
1166 $xml =~ s/>\s+</></go;
1167 $xml =~ s/\p{Cc}//go;
1168 $xml = $U->entityize($xml);
1169 $xml =~ s/[\x00-\x1f]//go;
1171 } catch Error with {
1173 $logger->warn("Proccessing XML of record $count in set $key failed with error $err. Skipping this record");
1176 next if $err or not $xml;
1179 source_label => $provider->code,
1180 provider => $provider->id,
1184 $args{picklist} = $picklist->id if $picklist;
1186 $args{purchase_order} = $po->id;
1187 $args{state} = 'order-pending';
1190 my $li = create_lineitem($mgr, %args) or return $mgr->editor->die_event;
1192 $li->provider($provider); # flesh it, we'll need it later
1194 import_lineitem_details($mgr, $ordering_agency, $li) or return $mgr->editor->die_event;
1197 push(@li_list, $li->id);
1201 my $die_event = activate_purchase_order_impl($mgr, $po->id) if $po;;
1202 return $die_event if $die_event;
1206 $cache->delete_cache('vandelay_import_spool_' . $key);
1208 if($create_assets) {
1209 create_lineitem_list_assets($mgr, \@li_list) or return $e->die_event;
1212 return $mgr->respond_complete;
1215 sub import_lineitem_details {
1216 my($mgr, $ordering_agency, $li) = @_;
1218 my $holdings = $mgr->editor->json_query({from => ['acq.extract_provider_holding_data', $li->id]});
1219 return 1 unless @$holdings;
1220 my $org_path = $U->get_org_ancestors($ordering_agency);
1221 $org_path = [ reverse (@$org_path) ];
1226 # create a lineitem detail for each copy in the data
1228 my $compiled = extract_lineitem_detail_data($mgr, $org_path, $holdings, $idx);
1229 last unless defined $compiled;
1230 return 0 unless $compiled;
1232 # this takes the price of the last copy and uses it as the lineitem price
1233 # need to determine if a given record would include different prices for the same item
1234 $price = $$compiled{price};
1236 for(1..$$compiled{quantity}) {
1237 my $lid = create_lineitem_detail($mgr,
1238 lineitem => $li->id,
1239 owning_lib => $$compiled{owning_lib},
1240 cn_label => $$compiled{call_number},
1241 fund => $$compiled{fund},
1242 circ_modifier => $$compiled{circ_modifier},
1243 note => $$compiled{note},
1244 location => $$compiled{copy_location},
1245 collection_code => $$compiled{collection_code}
1253 $li->estimated_unit_price($price);
1254 update_lineitem($mgr, $li) or return 0;
1258 # return hash on success, 0 on error, undef on no more holdings
1259 sub extract_lineitem_detail_data {
1260 my($mgr, $org_path, $holdings, $index) = @_;
1262 my @data_list = grep { $_->{holding} eq $index } @$holdings;
1263 return undef unless @data_list;
1265 my %compiled = map { $_->{attr} => $_->{data} } @data_list;
1266 my $base_org = $$org_path[0];
1270 $logger->error("Item import extraction error: $msg");
1271 $logger->error('Holdings Data: ' . OpenSRF::Utils::JSON->perl2JSON(\%compiled));
1272 $mgr->editor->rollback;
1273 $mgr->editor->event(OpenILS::Event->new('ACQ_IMPORT_ERROR', payload => $msg));
1277 $compiled{quantity} ||= 1;
1279 # ---------------------------------------------------------------------
1281 my $code = $compiled{fund_code};
1282 return $killme->('no fund code provided') unless $code;
1284 my $fund = $mgr->cache($base_org, "fund.$code");
1286 # search up the org tree for the most appropriate fund
1287 for my $org (@$org_path) {
1288 $fund = $mgr->editor->search_acq_fund(
1289 {org => $org, code => $code, year => DateTime->now->year}, {idlist => 1})->[0];
1293 return $killme->("no fund with code $code at orgs [@$org_path]") unless $fund;
1294 $compiled{fund} = $fund;
1295 $mgr->cache($base_org, "fund.$code", $fund);
1298 # ---------------------------------------------------------------------
1300 my $sn = $compiled{owning_lib};
1301 return $killme->('no owning_lib defined') unless $sn;
1303 $mgr->cache($base_org, "orgsn.$sn") ||
1304 $mgr->editor->search_actor_org_unit({shortname => $sn}, {idlist => 1})->[0];
1305 return $killme->("invalid owning_lib defined: $sn") unless $org_id;
1306 $compiled{owning_lib} = $org_id;
1307 $mgr->cache($$org_path[0], "orgsn.$sn", $org_id);
1310 # ---------------------------------------------------------------------
1313 $code = $compiled{circ_modifier};
1317 $mod = $mgr->cache($base_org, "mod.$code") ||
1318 $mgr->editor->retrieve_config_circ_modifier($code);
1319 return $killme->("invlalid circ_modifier $code") unless $mod;
1320 $mgr->cache($base_org, "mod.$code", $mod);
1324 $mod = get_default_circ_modifier($mgr, $base_org)
1325 or return $killme->('no circ_modifier defined');
1328 $compiled{circ_modifier} = $mod;
1331 # ---------------------------------------------------------------------
1333 my $name = $compiled{copy_location};
1335 my $loc = $mgr->cache($base_org, "copy_loc.$name");
1337 for my $org (@$org_path) {
1338 $loc = $mgr->editor->search_asset_copy_location(
1339 {owning_lib => $org, name => $name}, {idlist => 1})->[0];
1343 return $killme->("Invalid copy location $name") unless $loc;
1344 $compiled{copy_location} = $loc;
1345 $mgr->cache($base_org, "copy_loc.$name", $loc);
1353 # ----------------------------------------------------------------------------
1354 # Workflow: Given an existing purchase order, import/create the bibs,
1355 # callnumber and copy objects
1356 # ----------------------------------------------------------------------------
1358 __PACKAGE__->register_method(
1359 method => 'create_po_assets',
1360 api_name => 'open-ils.acq.purchase_order.assets.create',
1362 desc => q/Creates assets for each lineitem in the purchase order/,
1364 {desc => 'Authentication token', type => 'string'},
1365 {desc => 'The purchase order id', type => 'number'},
1367 return => {desc => 'Streams a total versus completed counts object, event on error'}
1371 sub create_po_assets {
1372 my($self, $conn, $auth, $po_id) = @_;
1374 my $e = new_editor(authtoken=>$auth, xact=>1);
1375 return $e->die_event unless $e->checkauth;
1376 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1378 my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1380 my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
1382 # it's ugly, but it's fast. Get the total count of lineitem detail objects to process
1383 my $lid_total = $e->json_query({
1384 select => { acqlid => [{aggregate => 1, transform => 'count', column => 'id'}] },
1390 join => {acqpo => {fkey => 'purchase_order', field => 'id'}}
1394 where => {'+acqpo' => {id => $po_id}}
1397 $mgr->total(scalar(@$li_ids) + $lid_total);
1399 create_lineitem_list_assets($mgr, $li_ids) or return $e->die_event;
1402 update_purchase_order($mgr, $po) or return $e->die_event;
1405 return $mgr->respond_complete;
1410 __PACKAGE__->register_method(
1411 method => 'create_purchase_order_api',
1412 api_name => 'open-ils.acq.purchase_order.create',
1414 desc => 'Creates a new purchase order',
1416 {desc => 'Authentication token', type => 'string'},
1417 {desc => 'purchase_order to create', type => 'object'}
1419 return => {desc => 'The purchase order id, Event on failure'}
1423 sub create_purchase_order_api {
1424 my($self, $conn, $auth, $po, $args) = @_;
1427 my $e = new_editor(xact=>1, authtoken=>$auth);
1428 return $e->die_event unless $e->checkauth;
1429 return $e->die_event unless $e->allowed('CREATE_PURCHASE_ORDER', $po->ordering_agency);
1430 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1433 my %pargs = (ordering_agency => $e->requestor->ws_ou); # default
1434 $pargs{provider} = $po->provider if $po->provider;
1435 $pargs{ordering_agency} = $po->ordering_agency if $po->ordering_agency;
1436 $pargs{prepayment_required} = $po->prepayment_required
1437 if $po->prepayment_required;
1438 $po = create_purchase_order($mgr, %pargs) or return $e->die_event;
1440 my $li_ids = $$args{lineitems};
1444 for my $li_id (@$li_ids) {
1446 my $li = $e->retrieve_acq_lineitem([
1448 {flesh => 1, flesh_fields => {jub => ['attributes']}}
1449 ]) or return $e->die_event;
1451 $li->provider($po->provider);
1452 $li->purchase_order($po->id);
1453 $li->state('pending-order');
1454 update_lineitem($mgr, $li) or return $e->die_event;
1459 # commit before starting the asset creation
1462 if($li_ids and $$args{create_assets}) {
1463 create_lineitem_list_assets($mgr, $li_ids) or return $e->die_event;
1466 return $mgr->respond_complete;
1470 __PACKAGE__->register_method(
1471 method => 'lineitem_detail_CUD_batch',
1472 api_name => 'open-ils.acq.lineitem_detail.cud.batch',
1475 desc => q/Creates a new purchase order line item detail.
1476 Additionally creates the associated fund_debit/,
1478 {desc => 'Authentication token', type => 'string'},
1479 {desc => 'List of lineitem_details to create', type => 'array'},
1481 return => {desc => 'Streaming response of current position in the array'}
1485 sub lineitem_detail_CUD_batch {
1486 my($self, $conn, $auth, $li_details) = @_;
1488 my $e = new_editor(xact=>1, authtoken=>$auth);
1489 return $e->die_event unless $e->checkauth;
1490 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1494 $mgr->total(scalar(@$li_details));
1498 for my $lid (@$li_details) {
1500 my $li = $li_cache{$lid->lineitem} || $e->retrieve_acq_lineitem($lid->lineitem);
1503 create_lineitem_detail($mgr, %{$lid->to_bare_hash}) or return $e->die_event;
1505 } elsif($lid->ischanged) {
1506 $e->update_acq_lineitem_detail($lid) or return $e->die_event;
1508 } elsif($lid->isdeleted) {
1509 delete_lineitem_detail($mgr, $lid) or return $e->die_event;
1512 $mgr->respond(li => $li);
1513 $li_cache{$lid->lineitem} = $li;
1517 return $mgr->respond_complete;
1521 __PACKAGE__->register_method(
1522 method => 'receive_po_api',
1523 api_name => 'open-ils.acq.purchase_order.receive'
1526 sub receive_po_api {
1527 my($self, $conn, $auth, $po_id) = @_;
1528 my $e = new_editor(xact => 1, authtoken => $auth);
1529 return $e->die_event unless $e->checkauth;
1530 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1532 my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1533 return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1535 my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
1537 for my $li_id (@$li_ids) {
1538 receive_lineitem($mgr, $li_id) or return $e->die_event;
1542 $po->state('received');
1543 update_purchase_order($mgr, $po) or return $e->die_event;
1546 return $mgr->respond_complete;
1550 # At the moment there's a lack of parallelism between the receive and unreceive
1551 # API methods for POs and the API methods for LIs and LIDs. The methods for
1552 # POs stream back objects as they act, whereas the methods for LIs and LIDs
1553 # atomically return an object that describes only what changed (in LIs and LIDs
1554 # themselves or in the objects to which to LIs and LIDs belong).
1556 # The methods for LIs and LIDs work the way they do to faciliate the UI's
1557 # maintaining correct information about the state of these things when a user
1558 # wants to receive or unreceive these objects without refreshing their whole
1559 # display. The UI feature for receiving and un-receiving a whole PO just
1560 # refreshes the whole display, so this absence of parallelism in the UI is also
1561 # relected in this module.
1563 # This could be neatened in the future by making POs receive and unreceive in
1564 # the same way the LIs and LIDs do.
1566 __PACKAGE__->register_method(
1567 method => 'receive_lineitem_detail_api',
1568 api_name => 'open-ils.acq.lineitem_detail.receive',
1570 desc => 'Mark a lineitem_detail as received',
1572 {desc => 'Authentication token', type => 'string'},
1573 {desc => 'lineitem detail ID', type => 'number'}
1576 "on success, object describing changes to LID and possibly " .
1577 "to LI and PO; on error, Event"
1582 sub receive_lineitem_detail_api {
1583 my($self, $conn, $auth, $lid_id) = @_;
1585 my $e = new_editor(xact=>1, authtoken=>$auth);
1586 return $e->die_event unless $e->checkauth;
1587 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1590 "flesh" => 2, "flesh_fields" => {
1591 "acqlid" => ["lineitem"], "jub" => ["purchase_order"]
1595 my $lid = $e->retrieve_acq_lineitem_detail([$lid_id, $fleshing]);
1597 return $e->die_event unless $e->allowed(
1598 'RECEIVE_PURCHASE_ORDER', $lid->lineitem->purchase_order->ordering_agency);
1601 my $recvd = receive_lineitem_detail($mgr, $lid_id) or return $e->die_event;
1603 # .. and re-retrieve
1604 $lid = $e->retrieve_acq_lineitem_detail([$lid_id, $fleshing]);
1606 # Now build result data structure.
1607 my $result = {"lid" => {$lid->id => {"recv_time" => $lid->recv_time}}};
1610 if ($recvd->class_name =~ /::purchase_order/) {
1611 $result->{"po"} = describe_affected_po($e, $recvd);
1613 $lid->lineitem->id => {"state" => $lid->lineitem->state}
1615 } elsif ($recvd->class_name =~ /::lineitem/) {
1616 $result->{"li"} = {$recvd->id => {"state" => $recvd->state}};
1620 describe_affected_po($e, $lid->lineitem->purchase_order);
1626 __PACKAGE__->register_method(
1627 method => 'receive_lineitem_api',
1628 api_name => 'open-ils.acq.lineitem.receive',
1630 desc => 'Mark a lineitem as received',
1632 {desc => 'Authentication token', type => 'string'},
1633 {desc => 'lineitem ID', type => 'number'}
1636 "on success, object describing changes to LI and possibly PO; " .
1642 sub receive_lineitem_api {
1643 my($self, $conn, $auth, $li_id) = @_;
1645 my $e = new_editor(xact=>1, authtoken=>$auth);
1646 return $e->die_event unless $e->checkauth;
1647 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1649 my $li = $e->retrieve_acq_lineitem([
1653 jub => ['purchase_order']
1656 ]) or return $e->die_event;
1658 return $e->die_event unless $e->allowed(
1659 'RECEIVE_PURCHASE_ORDER', $li->purchase_order->ordering_agency);
1661 my $res = receive_lineitem($mgr, $li_id) or return $e->die_event;
1663 $conn->respond_complete($res);
1664 $mgr->run_post_response_hooks;
1668 __PACKAGE__->register_method(
1669 method => 'rollback_receive_po_api',
1670 api_name => 'open-ils.acq.purchase_order.receive.rollback'
1673 sub rollback_receive_po_api {
1674 my($self, $conn, $auth, $po_id) = @_;
1675 my $e = new_editor(xact => 1, authtoken => $auth);
1676 return $e->die_event unless $e->checkauth;
1677 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1679 my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1680 return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1682 my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
1684 for my $li_id (@$li_ids) {
1685 rollback_receive_lineitem($mgr, $li_id) or return $e->die_event;
1689 $po->state('on-order');
1690 update_purchase_order($mgr, $po) or return $e->die_event;
1693 return $mgr->respond_complete;
1697 __PACKAGE__->register_method(
1698 method => 'rollback_receive_lineitem_detail_api',
1699 api_name => 'open-ils.acq.lineitem_detail.receive.rollback',
1701 desc => 'Mark a lineitem_detail as Un-received',
1703 {desc => 'Authentication token', type => 'string'},
1704 {desc => 'lineitem detail ID', type => 'number'}
1707 "on success, object describing changes to LID and possibly " .
1708 "to LI and PO; on error, Event"
1713 sub rollback_receive_lineitem_detail_api {
1714 my($self, $conn, $auth, $lid_id) = @_;
1716 my $e = new_editor(xact=>1, authtoken=>$auth);
1717 return $e->die_event unless $e->checkauth;
1718 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1720 my $lid = $e->retrieve_acq_lineitem_detail([
1724 acqlid => ['lineitem'],
1725 jub => ['purchase_order']
1729 my $li = $lid->lineitem;
1730 my $po = $li->purchase_order;
1732 return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1736 my $recvd = rollback_receive_lineitem_detail($mgr, $lid_id)
1737 or return $e->die_event;
1740 $result->{"lid"} = {$recvd->id => {"recv_time" => $recvd->recv_time}};
1742 $result->{"lid"} = {$lid->id => {"recv_time" => $lid->recv_time}};
1745 if ($li->state eq "received") {
1746 $li->state("on-order");
1747 $li = update_lineitem($mgr, $li) or return $e->die_event;
1748 $result->{"li"} = {$li->id => {"state" => $li->state}};
1751 if ($po->state eq "received") {
1752 $po->state("on-order");
1753 $po = update_purchase_order($mgr, $po) or return $e->die_event;
1755 $result->{"po"} = describe_affected_po($e, $po);
1757 $e->commit and return $result or return $e->die_event;
1760 __PACKAGE__->register_method(
1761 method => 'rollback_receive_lineitem_api',
1762 api_name => 'open-ils.acq.lineitem.receive.rollback',
1764 desc => 'Mark a lineitem as Un-received',
1766 {desc => 'Authentication token', type => 'string'},
1767 {desc => 'lineitem ID', type => 'number'}
1770 "on success, object describing changes to LI and possibly PO; " .
1776 sub rollback_receive_lineitem_api {
1777 my($self, $conn, $auth, $li_id) = @_;
1779 my $e = new_editor(xact=>1, authtoken=>$auth);
1780 return $e->die_event unless $e->checkauth;
1781 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1783 my $li = $e->retrieve_acq_lineitem([
1785 "flesh" => 1, "flesh_fields" => {"jub" => ["purchase_order"]}
1788 my $po = $li->purchase_order;
1790 return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1792 $li = rollback_receive_lineitem($mgr, $li_id) or return $e->die_event;
1794 my $result = {"li" => {$li->id => {"state" => $li->state}}};
1795 if ($po->state eq "received") {
1796 $po->state("on-order");
1797 $po = update_purchase_order($mgr, $po) or return $e->die_event;
1799 $result->{"po"} = describe_affected_po($e, $po);
1801 $e->commit and return $result or return $e->die_event;
1805 __PACKAGE__->register_method(
1806 method => 'set_lineitem_price_api',
1807 api_name => 'open-ils.acq.lineitem.price.set',
1809 desc => 'Set lineitem price. If debits already exist, update them as well',
1811 {desc => 'Authentication token', type => 'string'},
1812 {desc => 'lineitem ID', type => 'number'}
1814 return => {desc => 'status blob, Event on error'}
1818 sub set_lineitem_price_api {
1819 my($self, $conn, $auth, $li_id, $price) = @_;
1821 my $e = new_editor(xact=>1, authtoken=>$auth);
1822 return $e->die_event unless $e->checkauth;
1823 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1825 my $li = $e->retrieve_acq_lineitem([
1828 flesh_fields => {jub => ['purchase_order', 'picklist']}
1830 ]) or return $e->die_event;
1832 if($li->purchase_order) {
1833 return $e->die_event unless
1834 $e->allowed('CREATE_PURCHASE_ORDER', $li->purchase_order->ordering_agency);
1836 return $e->die_event unless
1837 $e->allowed('CREATE_PICKLIST', $li->picklist->org_unit);
1840 $li->estimated_unit_price($price);
1841 update_lineitem($mgr, $li) or return $e->die_event;
1843 my $lid_ids = $e->search_acq_lineitem_detail(
1844 {lineitem => $li_id, fund_debit => {'!=' => undef}},
1848 for my $lid_id (@$lid_ids) {
1850 my $lid = $e->retrieve_acq_lineitem_detail([
1852 flesh => 1, flesh_fields => {acqlid => ['fund', 'fund_debit']}}
1855 $lid->fund_debit->amount($price);
1856 $e->update_acq_fund_debit($lid->fund_debit) or return $e->die_event;
1862 return $mgr->respond_complete;
1866 __PACKAGE__->register_method(
1867 method => 'clone_picklist_api',
1868 api_name => 'open-ils.acq.picklist.clone',
1870 desc => 'Clones a picklist, including lineitem and lineitem details',
1872 {desc => 'Authentication token', type => 'string'},
1873 {desc => 'Picklist ID', type => 'number'},
1874 {desc => 'New Picklist Name', type => 'string'}
1876 return => {desc => 'status blob, Event on error'}
1880 sub clone_picklist_api {
1881 my($self, $conn, $auth, $pl_id, $name) = @_;
1883 my $e = new_editor(xact=>1, authtoken=>$auth);
1884 return $e->die_event unless $e->checkauth;
1885 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1887 my $old_pl = $e->retrieve_acq_picklist($pl_id);
1888 my $new_pl = create_picklist($mgr, %{$old_pl->to_bare_hash}, name => $name) or return $e->die_event;
1890 my $li_ids = $e->search_acq_lineitem({picklist => $pl_id}, {idlist => 1});
1892 for my $li_id (@$li_ids) {
1894 # copy the lineitems
1895 my $li = $e->retrieve_acq_lineitem($li_id);
1896 my $new_li = create_lineitem($mgr, %{$li->to_bare_hash}, picklist => $new_pl->id) or return $e->die_event;
1898 my $lid_ids = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist => 1});
1899 for my $lid_id (@$lid_ids) {
1901 # copy the lineitem details
1902 my $lid = $e->retrieve_acq_lineitem_detail($lid_id);
1903 create_lineitem_detail($mgr, %{$lid->to_bare_hash}, lineitem => $new_li->id) or return $e->die_event;
1910 return $mgr->respond_complete;
1914 __PACKAGE__->register_method(
1915 method => 'merge_picklist_api',
1916 api_name => 'open-ils.acq.picklist.merge',
1918 desc => 'Merges 2 or more picklists into a single list',
1920 {desc => 'Authentication token', type => 'string'},
1921 {desc => 'Lead Picklist ID', type => 'number'},
1922 {desc => 'List of subordinate picklist IDs', type => 'array'}
1924 return => {desc => 'status blob, Event on error'}
1928 sub merge_picklist_api {
1929 my($self, $conn, $auth, $lead_pl, $pl_list) = @_;
1931 my $e = new_editor(xact=>1, authtoken=>$auth);
1932 return $e->die_event unless $e->checkauth;
1933 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1935 # XXX perms on each picklist modified
1937 # point all of the lineitems at the lead picklist
1938 my $li_ids = $e->search_acq_lineitem({picklist => $pl_list}, {idlist => 1});
1940 for my $li_id (@$li_ids) {
1941 my $li = $e->retrieve_acq_lineitem($li_id);
1942 $li->picklist($lead_pl);
1943 update_lineitem($mgr, $li) or return $e->die_event;
1947 # now delete the subordinate lists
1948 for my $pl_id (@$pl_list) {
1949 my $pl = $e->retrieve_acq_picklist($pl_id);
1950 $e->delete_acq_picklist($pl) or return $e->die_event;
1954 return $mgr->respond_complete;
1958 __PACKAGE__->register_method(
1959 method => 'delete_picklist_api',
1960 api_name => 'open-ils.acq.picklist.delete',
1962 desc => q/Deletes a picklist. It also deletes any lineitems in the "new" state.
1963 Other attached lineitems are detached'/,
1965 {desc => 'Authentication token', type => 'string'},
1966 {desc => 'Picklist ID to delete', type => 'number'}
1968 return => {desc => '1 on success, Event on error'}
1972 sub delete_picklist_api {
1973 my($self, $conn, $auth, $picklist_id) = @_;
1974 my $e = new_editor(xact=>1, authtoken=>$auth);
1975 return $e->die_event unless $e->checkauth;
1976 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1977 my $pl = $e->retrieve_acq_picklist($picklist_id) or return $e->die_event;
1978 delete_picklist($mgr, $pl) or return $e->die_event;
1980 return $mgr->respond_complete;
1985 __PACKAGE__->register_method(
1986 method => 'activate_purchase_order',
1987 api_name => 'open-ils.acq.purchase_order.activate',
1989 desc => q/Activates a purchase order. This updates the status of the PO
1990 and Lineitems to 'on-order'. Activated PO's are ready for EDI delivery
1993 {desc => 'Authentication token', type => 'string'},
1994 {desc => 'Purchase ID', type => 'number'}
1996 return => {desc => '1 on success, Event on error'}
2000 sub activate_purchase_order {
2001 my($self, $conn, $auth, $po_id) = @_;
2002 my $e = new_editor(xact=>1, authtoken=>$auth);
2003 return $e->die_event unless $e->checkauth;
2004 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
2005 my $die_event = activate_purchase_order_impl($mgr, $po_id);
2006 return $die_event if $die_event;
2008 $conn->respond_complete(1);
2009 $mgr->run_post_response_hooks;
2013 sub activate_purchase_order_impl {
2014 my($mgr, $po_id) = @_;
2015 my $e = $mgr->editor;
2017 my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
2018 return $e->die_event unless $e->allowed('CREATE_PURCHASE_ORDER', $po->ordering_agency);
2020 $po->state('on-order');
2021 update_purchase_order($mgr, $po) or return $e->die_event;
2024 {purchase_order => $po_id, state => 'pending-order'},
2028 while( my $li = $e->search_acq_lineitem($query)->[0] ) {
2029 $li->state('on-order');
2030 create_lineitem_debits($mgr, $li) or return $e->die_event;
2031 update_lineitem($mgr, $li) or return $e->die_event;
2032 $mgr->post_process( sub { create_lineitem_status_events($mgr, $li->id, 'aur.ordered'); });
2040 __PACKAGE__->register_method(
2041 method => 'split_purchase_order_by_lineitems',
2042 api_name => 'open-ils.acq.purchase_order.split_by_lineitems',
2044 desc => q/Splits a PO into many POs, 1 per lineitem. Only works for
2045 POs a) with more than one lineitems, and b) in the "pending" state./,
2047 {desc => 'Authentication token', type => 'string'},
2048 {desc => 'Purchase order ID', type => 'number'}
2050 return => {desc => 'list of new PO IDs on success, Event on error'}
2054 sub split_purchase_order_by_lineitems {
2055 my ($self, $conn, $auth, $po_id) = @_;
2057 my $e = new_editor("xact" => 1, "authtoken" => $auth);
2058 return $e->die_event unless $e->checkauth;
2060 my $po = $e->retrieve_acq_purchase_order([
2063 "flesh_fields" => {"acqpo" => [qw/lineitems notes/]}
2065 ]) or return $e->die_event;
2067 return $e->die_event
2068 unless $e->allowed("CREATE_PURCHASE_ORDER", $po->ordering_agency);
2070 unless ($po->state eq "pending") {
2072 return new OpenILS::Event("ACQ_PURCHASE_ORDER_TOO_LATE");
2075 unless (@{$po->lineitems} > 1) {
2077 return new OpenILS::Event("ACQ_PURCHASE_ORDER_TOO_SHORT");
2080 # To split an existing PO into many, it seems unwise to just delete the
2081 # original PO, so we'll instead detach all of the original POs' lineitems
2082 # but the first, then create new POs for each of the remaining LIs, and
2083 # then attach the LIs to their new POs.
2085 my @po_ids = ($po->id);
2086 my @moving_li = @{$po->lineitems};
2087 shift @moving_li; # discard first LI
2089 foreach my $li (@moving_li) {
2090 my $new_po = $po->clone;
2092 $new_po->clear_name;
2093 $new_po->creator($e->requestor->id);
2094 $new_po->editor($e->requestor->id);
2095 $new_po->owner($e->requestor->id);
2096 $new_po->edit_time("now");
2097 $new_po->create_time("now");
2099 $new_po = $e->create_acq_purchase_order($new_po);
2101 # Clone any notes attached to the old PO and attach to the new one.
2102 foreach my $note (@{$po->notes}) {
2103 my $new_note = $note->clone;
2104 $new_note->clear_id;
2105 $new_note->edit_time("now");
2106 $new_note->purchase_order($new_po->id);
2107 $e->create_acq_po_note($new_note);
2110 $li->edit_time("now");
2111 $li->purchase_order($new_po->id);
2112 $e->update_acq_lineitem($li);
2114 push @po_ids, $new_po->id;
2117 $po->edit_time("now");
2118 $e->update_acq_purchase_order($po);
2120 return \@po_ids if $e->commit;
2121 return $e->die_event;
2125 sub not_cancelable {
2127 (ref $o eq "HASH" and $o->{"textcode"} eq "ACQ_NOT_CANCELABLE");
2130 __PACKAGE__->register_method(
2131 method => "cancel_purchase_order_api",
2132 api_name => "open-ils.acq.purchase_order.cancel",
2134 desc => q/Cancels an on-order purchase order/,
2136 {desc => "Authentication token", type => "string"},
2137 {desc => "PO ID to cancel", type => "number"},
2138 {desc => "Cancel reason ID", type => "number"}
2140 return => {desc => q/Object describing changed POs, LIs and LIDs
2141 on success; Event on error./}
2145 sub cancel_purchase_order_api {
2146 my ($self, $conn, $auth, $po_id, $cancel_reason) = @_;
2148 my $e = new_editor("xact" => 1, "authtoken" => $auth);
2149 return $e->die_event unless $e->checkauth;
2150 my $mgr = new OpenILS::Application::Acq::BatchManager(
2151 "editor" => $e, "conn" => $conn
2154 $cancel_reason = $mgr->editor->retrieve_acq_cancel_reason($cancel_reason) or
2155 return new OpenILS::Event(
2156 "BAD_PARAMS", "note" => "Provide cancel reason ID"
2159 my $result = cancel_purchase_order($mgr, $po_id, $cancel_reason) or
2160 return $e->die_event;
2161 if (not_cancelable($result)) { # event not from CStoreEditor
2164 } elsif ($result == -1) {
2166 return new OpenILS::Event("ACQ_ALREADY_CANCELED");
2169 $e->commit or return $e->die_event;
2171 # XXX create purchase order status events?
2175 sub cancel_purchase_order {
2176 my ($mgr, $po_id, $cancel_reason) = @_;
2178 my $po = $mgr->editor->retrieve_acq_purchase_order($po_id) or return 0;
2180 # XXX is "cancelled" a typo? It's not correct US spelling, anyway.
2181 # Depending on context, this may not warrant an event.
2182 return -1 if $po->state eq "cancelled";
2184 # But this always does.
2185 return new OpenILS::Event(
2186 "ACQ_NOT_CANCELABLE", "note" => "purchase_order $po_id"
2187 ) unless ($po->state eq "on-order" or $po->state eq "pending");
2190 $mgr->editor->allowed("CREATE_PURCHASE_ORDER", $po->ordering_agency);
2192 $po->state("cancelled");
2193 $po->cancel_reason($cancel_reason);
2195 my $li_ids = $mgr->editor->search_acq_lineitem(
2196 {"purchase_order" => $po_id}, {"idlist" => 1}
2199 my $result = {"li" => {}, "lid" => {}};
2200 foreach my $li_id (@$li_ids) {
2201 my $li_result = cancel_lineitem($mgr, $li_id, $cancel_reason)
2204 next if $li_result == -1; # already canceled:skip.
2205 return $li_result if not_cancelable($li_result); # not cancelable:stop.
2207 # Merge in each LI result (there's only going to be
2208 # one per call to cancel_lineitem).
2209 my ($k, $v) = each %{$li_result->{"li"}};
2210 $result->{"li"}->{$k} = $v;
2212 # Merge in each LID result (there may be many per call to
2214 while (($k, $v) = each %{$li_result->{"lid"}}) {
2215 $result->{"lid"}->{$k} = $v;
2219 # TODO who/what/where/how do we indicate this change for electronic orders?
2220 # TODO return changes to encumbered/spent
2221 # TODO maybe cascade up from smaller object to container object if last
2222 # smaller object in the container has been canceled?
2224 update_purchase_order($mgr, $po) or return 0;
2226 $po_id => {"state" => $po->state, "cancel_reason" => $cancel_reason}
2232 __PACKAGE__->register_method(
2233 method => "cancel_lineitem_api",
2234 api_name => "open-ils.acq.lineitem.cancel",
2236 desc => q/Cancels an on-order lineitem/,
2238 {desc => "Authentication token", type => "string"},
2239 {desc => "Lineitem ID to cancel", type => "number"},
2240 {desc => "Cancel reason ID", type => "number"}
2242 return => {desc => q/Object describing changed LIs and LIDs on success;
2247 __PACKAGE__->register_method(
2248 method => "cancel_lineitem_api",
2249 api_name => "open-ils.acq.lineitem.cancel.batch",
2251 desc => q/Batched version of open-ils.acq.lineitem.cancel/,
2252 return => {desc => q/Object describing changed LIs and LIDs on success;
2257 sub cancel_lineitem_api {
2258 my ($self, $conn, $auth, $li_id, $cancel_reason) = @_;
2260 my $batched = $self->api_name =~ /\.batch/;
2262 my $e = new_editor("xact" => 1, "authtoken" => $auth);
2263 return $e->die_event unless $e->checkauth;
2264 my $mgr = new OpenILS::Application::Acq::BatchManager(
2265 "editor" => $e, "conn" => $conn
2268 $cancel_reason = $mgr->editor->retrieve_acq_cancel_reason($cancel_reason) or
2269 return new OpenILS::Event(
2270 "BAD_PARAMS", "note" => "Provide cancel reason ID"
2273 my ($result, $maybe_event);
2276 $result = {"li" => {}, "lid" => {}};
2277 foreach my $one_li_id (@$li_id) {
2278 my $one = cancel_lineitem($mgr, $one_li_id, $cancel_reason) or
2279 return $e->die_event;
2280 if (not_cancelable($one)) {
2281 $maybe_event = $one;
2282 } elsif ($result == -1) {
2283 $maybe_event = new OpenILS::Event("ACQ_ALREADY_CANCELED");
2287 while (($k, $v) = each %{$one->{"li"}}) {
2288 $result->{"li"}->{$k} = $v;
2291 if ($one->{"lid"}) {
2292 while (($k, $v) = each %{$one->{"lid"}}) {
2293 $result->{"lid"}->{$k} = $v;
2299 $result = cancel_lineitem($mgr, $li_id, $cancel_reason) or
2300 return $e->die_event;
2302 if (not_cancelable($result)) {
2305 } elsif ($result == -1) {
2307 return new OpenILS::Event("ACQ_ALREADY_CANCELED");
2311 if ($batched and not scalar keys %{$result->{"li"}}) {
2313 return $maybe_event;
2315 $e->commit or return $e->die_event;
2316 # create_lineitem_status_events should handle array li_id ok
2317 create_lineitem_status_events($mgr, $li_id, "aur.cancelled");
2322 sub cancel_lineitem {
2323 my ($mgr, $li_id, $cancel_reason) = @_;
2324 my $li = $mgr->editor->retrieve_acq_lineitem([
2325 $li_id, {"flesh" => 1, "flesh_fields" => {"jub" => ["purchase_order"]}}
2328 return 0 unless $mgr->editor->allowed(
2329 "CREATE_PURCHASE_ORDER", $li->purchase_order->ordering_agency
2332 # Depending on context, this may not warrant an event.
2333 return -1 if $li->state eq "cancelled";
2335 # But this always does.
2336 return new OpenILS::Event(
2337 "ACQ_NOT_CANCELABLE", "note" => "lineitem $li_id"
2339 $li->purchase_order and (
2340 $li->state eq "on-order" or $li->state eq "pending-order"
2344 $li->state("cancelled");
2345 $li->cancel_reason($cancel_reason);
2347 my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
2348 {"lineitem" => $li_id}, {"idlist" => 1}
2351 my $result = {"lid" => {}};
2352 foreach my $lid_id (@$lid_ids) {
2353 my $lid_result = cancel_lineitem_detail($mgr, $lid_id, $cancel_reason)
2356 next if $lid_result == -1; # already canceled: just skip it.
2357 return $lid_result if not_cancelable($lid_result); # not cxlable: stop.
2359 # Merge in each LID result (there's only going to be one per call to
2360 # cancel_lineitem_detail).
2361 my ($k, $v) = each %{$lid_result->{"lid"}};
2362 $result->{"lid"}->{$k} = $v;
2365 # TODO delete the associated fund debits?
2366 # TODO who/what/where/how do we indicate this change for electronic orders?
2368 update_lineitem($mgr, $li) or return 0;
2371 "state" => $li->state,
2372 "cancel_reason" => $cancel_reason
2379 __PACKAGE__->register_method(
2380 method => "cancel_lineitem_detail_api",
2381 api_name => "open-ils.acq.lineitem_detail.cancel",
2383 desc => q/Cancels an on-order lineitem detail/,
2385 {desc => "Authentication token", type => "string"},
2386 {desc => "Lineitem detail ID to cancel", type => "number"},
2387 {desc => "Cancel reason ID", type => "number"}
2389 return => {desc => q/Object describing changed LIDs on success;
2394 sub cancel_lineitem_detail_api {
2395 my ($self, $conn, $auth, $lid_id, $cancel_reason) = @_;
2397 my $e = new_editor("xact" => 1, "authtoken" => $auth);
2398 return $e->die_event unless $e->checkauth;
2399 my $mgr = new OpenILS::Application::Acq::BatchManager(
2400 "editor" => $e, "conn" => $conn
2403 $cancel_reason = $mgr->editor->retrieve_acq_cancel_reason($cancel_reason) or
2404 return new OpenILS::Event(
2405 "BAD_PARAMS", "note" => "Provide cancel reason ID"
2408 my $result = cancel_lineitem_detail($mgr, $lid_id, $cancel_reason) or
2409 return $e->die_event;
2411 if (not_cancelable($result)) {
2414 } elsif ($result == -1) {
2416 return new OpenILS::Event("ACQ_ALREADY_CANCELED");
2419 $e->commit or return $e->die_event;
2421 # XXX create lineitem detail status events?
2425 sub cancel_lineitem_detail {
2426 my ($mgr, $lid_id, $cancel_reason) = @_;
2427 my $lid = $mgr->editor->retrieve_acq_lineitem_detail([
2431 "acqlid" => ["lineitem"], "jub" => ["purchase_order"]
2436 # Depending on context, this may not warrant an event.
2437 return -1 if $lid->cancel_reason;
2439 # But this always does.
2440 return new OpenILS::Event(
2441 "ACQ_NOT_CANCELABLE", "note" => "lineitem_detail $lid_id"
2443 (not $lid->recv_time) and
2445 $lid->lineitem->purchase_order and (
2446 $lid->lineitem->state eq "on-order" or
2447 $lid->lineitem->state eq "pending-order"
2451 return 0 unless $mgr->editor->allowed(
2452 "CREATE_PURCHASE_ORDER",
2453 $lid->lineitem->purchase_order->ordering_agency
2456 $lid->cancel_reason($cancel_reason);
2458 # TODO who/what/where/how do we indicate this change for electronic orders?
2460 # XXX LIDs don't have either an editor or a edit_time field. Should we
2461 # update these on the LI when we alter an LID?
2462 $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
2463 return {"lid" => {$lid_id => {"cancel_reason" => $cancel_reason}}};
2467 __PACKAGE__->register_method (
2468 method => 'user_requests',
2469 api_name => 'open-ils.acq.user_request.retrieve.by_user_id',
2472 Retrieve fleshed user requests and related data for a given user or users.
2473 @param authtoken Login session key
2474 @param owner Id or array of id's for the pertinent users.
2475 @param options Allows one to override the query's 'order_by', 'limit', and 'offset'. And the 'state' of the lineitem in the search.
2479 __PACKAGE__->register_method (
2480 method => 'user_requests',
2481 api_name => 'open-ils.acq.user_request.retrieve.by_home_ou',
2484 Retrieve fleshed user requests and related data for a given org unit or units.
2485 @param authtoken Login session key
2486 @param owner Id or array of id's for the pertinent org units.
2487 @param options Allows one to override the query's 'order_by', 'limit', and 'offset'. And the 'state' of the lineitem in the search.
2492 my($self, $conn, $auth, $search_value, $options) = @_;
2493 my $e = new_editor(authtoken => $auth);
2494 return $e->event unless $e->checkauth;
2495 my $rid = $e->requestor->id;
2498 "select"=>{"aur"=>["id"],"au"=>["home_ou", {column => 'id', alias => 'usr_id'} ]},
2499 "from"=>{ "aur" => { "au" => {}, "jub" => { "type" => "left" } } },
2503 {"id"=>undef}, # this with the left-join pulls in requests without lineitems
2504 {"state"=>["new","on-order","pending-order"]} # FIXME - probably needs softcoding
2508 "order_by"=>[{"class"=>"aur", "field"=>"request_date", "direction"=>"desc"}]
2511 if ($options && defined $options->{'order_by'}) {
2512 $query->{'order_by'} = $options->{'order_by'};
2514 if ($options && defined $options->{'limit'}) {
2515 $query->{'limit'} = $options->{'limit'};
2517 if ($options && defined $options->{'offset'}) {
2518 $query->{'offset'} = $options->{'offset'};
2520 if ($options && defined $options->{'state'}) {
2521 $query->{'where'}->{'+jub'}->{'-or'}->[1]->{'state'} = $options->{'state'};
2524 if ($self->api_name =~ /by_user_id/) {
2525 $query->{'where'}->{'usr'} = $search_value;
2527 $query->{'where'}->{'+au'} = { 'home_ou' => $search_value };
2530 my $pertinent_ids = $e->json_query($query);
2533 for my $id_blob (@$pertinent_ids) {
2534 if ($rid != $id_blob->{usr_id}) {
2535 if (!defined $perm_test{ $id_blob->{home_ou} }) {
2536 $perm_test{ $id_blob->{home_ou} } = $e->allowed( ['user_request.view'], $id_blob->{home_ou} );
2538 if (!$perm_test{ $id_blob->{home_ou} }) {
2542 my $aur_obj = $e->retrieve_acq_user_request([
2544 {flesh => 1, flesh_fields => { "aur" => [ 'lineitem' ] } }
2546 if (! $aur_obj) { next; }
2548 if ($aur_obj->lineitem()) {
2549 $aur_obj->lineitem()->clear_marc();
2551 $conn->respond($aur_obj);
2557 __PACKAGE__->register_method (
2558 method => 'update_user_request',
2559 api_name => 'open-ils.acq.user_request.cancel.batch',
2562 If given a cancel reason, will update the request with that reason, otherwise, this will delete the request altogether. The intention
2563 is for staff interfaces or processes to provide cancel reasons, and for patron interfaces to just delete the requests.
2564 @param authtoken Login session key
2565 @param ids Id or array of id's for the user requests to cancel.
2566 @param cancel_reason Optional Cancel Reason Id.
2569 __PACKAGE__->register_method (
2570 method => 'update_user_request',
2571 api_name => 'open-ils.acq.user_request.set_no_hold.batch',
2575 sub update_user_request {
2576 my($self, $conn, $auth, $aur_ids, $cancel_reason) = @_;
2577 my $e = new_editor(xact => 1, authtoken => $auth);
2578 return $e->die_event unless $e->checkauth;
2579 my $rid = $e->requestor->id;
2583 for my $id (@$aur_ids) {
2585 my $aur_obj = $e->retrieve_acq_user_request([
2588 flesh_fields => { "aur" => ['lineitem', 'usr'] }
2590 ]) or return $e->die_event;
2592 my $context_org = $aur_obj->usr()->home_ou();
2593 $aur_obj->usr( $aur_obj->usr()->id() );
2595 if ($rid != $aur_obj->usr) {
2596 if (!defined $perm_test{ $context_org }) {
2597 $perm_test{ $context_org } = $e->allowed( ['user_request.update'], $context_org );
2599 if (!$perm_test{ $context_org }) {
2604 if($self->api_name =~ /set_no_hold/) {
2605 if ($U->is_true($aur_obj->hold)) {
2607 $e->update_acq_user_request($aur_obj) or return $e->die_event;
2611 if($self->api_name =~ /cancel/) {
2612 if ( $cancel_reason ) {
2613 $aur_obj->cancel_reason( $cancel_reason );
2614 $e->update_acq_user_request($aur_obj) or return $e->die_event;
2616 $e->delete_acq_user_request($aur_obj);
2620 $conn->respond({maximum => scalar(@$aur_ids), progress => $x++});
2624 return {complete => 1};
2627 __PACKAGE__->register_method (
2628 method => 'new_user_request',
2629 api_name => 'open-ils.acq.user_request.create'
2632 sub new_user_request {
2633 my($self, $conn, $auth, $form_data) = @_;
2634 my $e = new_editor(xact => 1, authtoken => $auth);
2635 return $e->die_event unless $e->checkauth;
2636 my $rid = $e->requestor->id;
2637 my $target_user_fleshed;
2638 if (! defined $$form_data{'usr'}) {
2639 $$form_data{'usr'} = $rid;
2641 if ($$form_data{'usr'} != $rid) {
2642 # See if the requestor can place the request on behalf of a different user.
2643 $target_user_fleshed = $e->retrieve_actor_user($$form_data{'usr'}) or return $e->die_event;
2644 $e->allowed('user_request.create', $target_user_fleshed->home_ou) or return $e->die_event;
2646 $target_user_fleshed = $e->requestor;
2647 $e->allowed('CREATE_PURCHASE_REQUEST') or return $e->die_event;
2649 if (! defined $$form_data{'pickup_lib'}) {
2650 if ($target_user_fleshed->ws_ou) {
2651 $$form_data{'pickup_lib'} = $target_user_fleshed->ws_ou;
2653 $$form_data{'pickup_lib'} = $target_user_fleshed->home_ou;
2656 if (! defined $$form_data{'request_type'}) {
2657 $$form_data{'request_type'} = 1; # Books
2659 my $aur_obj = new Fieldmapper::acq::user_request;
2661 $aur_obj->usr( $$form_data{'usr'} );
2662 $aur_obj->request_date( 'now' );
2663 for my $field ( keys %$form_data ) {
2664 if (defined $$form_data{$field} and $field !~ /^(id|lineitem|eg_bib|request_date|cancel_reason)$/) {
2665 $aur_obj->$field( $$form_data{$field} );
2669 $aur_obj = $e->create_acq_user_request($aur_obj) or $e->die_event;
2677 __PACKAGE__->register_method(
2678 method => "po_note_CUD_batch",
2679 api_name => "open-ils.acq.po_note.cud.batch",
2682 desc => q/Manage purchase order notes/,
2684 {desc => "Authentication token", type => "string"},
2685 {desc => "List of po_notes to manage", type => "array"},
2687 return => {desc => "Stream of successfully managed objects"}
2691 sub po_note_CUD_batch {
2692 my ($self, $conn, $auth, $notes) = @_;
2694 my $e = new_editor("xact"=> 1, "authtoken" => $auth);
2695 return $e->die_event unless $e->checkauth;
2698 my $total = @$notes;
2701 foreach my $note (@$notes) {
2703 $note->editor($e->requestor->id);
2704 $note->edit_time("now");
2707 $note->creator($e->requestor->id);
2708 $note = $e->create_acq_po_note($note) or return $e->die_event;
2709 } elsif ($note->isdeleted) {
2710 $e->delete_acq_po_note($note) or return $e->die_event;
2711 } elsif ($note->ischanged) {
2712 $e->update_acq_po_note($note) or return $e->die_event;
2715 unless ($note->isdeleted) {
2716 $note = $e->retrieve_acq_po_note($note->id) or
2717 return $e->die_event;
2721 {"maximum" => $total, "progress" => ++$count, "note" => $note}
2725 $e->commit and $conn->respond_complete or return $e->die_event;