1 package OpenILS::Application::Acq::BatchManager;
2 use OpenSRF::AppSession;
3 use OpenSRF::EX qw/:try/;
4 use strict; use warnings;
7 my($class, %args) = @_;
8 my $self = bless(\%args, $class);
16 purchase_order => undef,
22 $self->{ingest_queue} = [];
24 $self->throttle(5) unless $self->throttle;
30 $self->{conn} = $val if $val;
35 $self->{throttle} = $val if $val;
36 return $self->{throttle};
39 my($self, %other_args) = @_;
40 if($self->throttle and not %other_args) {
42 ($self->{args}->{progress} - $self->{last_respond_progress}) >= $self->throttle
45 $self->conn->respond({ %{$self->{args}}, %other_args });
46 $self->{last_respond_progress} = $self->{args}->{progress};
48 sub respond_complete {
49 my($self, %other_args) = @_;
51 $self->conn->respond_complete({ %{$self->{args}}, %other_args });
56 $self->{args}->{total} = $val if defined $val;
57 $self->{args}->{maximum} = $self->{args}->{total};
58 return $self->{args}->{total};
62 $self->{args}->{purchase_order} = $val if $val;
67 $self->{args}->{picklist} = $val if $val;
72 $self->{args}->{lid} += 1;
73 $self->{args}->{progress} += 1;
78 $self->{args}->{li} += 1;
79 $self->{args}->{progress} += 1;
84 $self->{args}->{copies} += 1;
85 $self->{args}->{progress} += 1;
90 $self->{args}->{bibs} += 1;
91 $self->{args}->{progress} += 1;
95 my($self, $amount) = @_;
96 $self->{args}->{debits_accrued} += $amount;
97 $self->{args}->{progress} += 1;
101 my($self, $editor) = @_;
102 $self->{editor} = $editor if defined $editor;
103 return $self->{editor};
107 $self->{args}->{complete} = 1;
112 my($self, $val) = @_;
113 $self->{ingest_ses} = $val if $val;
114 return $self->{ingest_ses};
117 sub push_ingest_queue {
118 my($self, $rec_id) = @_;
120 $self->ingest_ses(OpenSRF::AppSession->connect('open-ils.ingest'))
121 unless $self->ingest_ses;
123 my $req = $self->ingest_ses->request('open-ils.ingest.full.biblio.record', $rec_id);
125 push(@{$self->{ingest_queue}}, $req);
128 sub process_ingest_records {
130 return unless @{$self->{ingest_queue}};
132 for my $req (@{$self->{ingest_queue}}) {
136 $self->{args}->{indexed} += 1;
137 $self->{args}->{progress} += 1;
142 $self->ingest_ses->disconnect;
147 my($self, $org, $key, $val) = @_;
148 $self->{cache}->{$org} = {} unless $self->{cache}->{org};
149 $self->{cache}->{$org}->{$key} = $val if defined $val;
150 return $self->{cache}->{$org}->{$key};
154 package OpenILS::Application::Acq::Order;
155 use base qw/OpenILS::Application/;
156 use strict; use warnings;
157 # ----------------------------------------------------------------------------
158 # Break up each component of the order process and pieces into managable
159 # actions that can be shared across different workflows
160 # ----------------------------------------------------------------------------
162 use OpenSRF::Utils::Logger qw(:logger);
163 use OpenSRF::Utils::JSON;
164 use OpenILS::Utils::Fieldmapper;
165 use OpenILS::Utils::CStoreEditor q/:funcs/;
166 use OpenILS::Const qw/:const/;
167 use OpenSRF::EX q/:try/;
168 use OpenILS::Application::AppUtils;
169 use OpenILS::Application::Cat::BibCommon;
170 use OpenILS::Application::Cat::AssetCommon;
174 my $U = 'OpenILS::Application::AppUtils';
177 # ----------------------------------------------------------------------------
179 # ----------------------------------------------------------------------------
180 sub create_lineitem {
181 my($mgr, %args) = @_;
182 my $li = Fieldmapper::acq::lineitem->new;
183 $li->creator($mgr->editor->requestor->id);
184 $li->selector($li->creator);
185 $li->editor($li->creator);
186 $li->create_time('now');
187 $li->edit_time('now');
189 $li->$_($args{$_}) for keys %args;
192 return $mgr->editor->create_acq_lineitem($li);
195 sub update_lineitem {
197 $li->edit_time('now');
198 $li->editor($mgr->editor->requestor->id);
200 return $li if $mgr->editor->update_acq_lineitem($li);
205 # ----------------------------------------------------------------------------
206 # Create real holds from patron requests for a given lineitem
207 # ----------------------------------------------------------------------------
208 sub promote_lineitem_holds {
211 my $requests = $mgr->editor->search_acq_user_request(
212 { lineitem => $li->id,
214 [ { need_before => {'>' => 'now'} },
215 { need_before => undef }
220 for my $request ( @$requests ) {
222 $request->eg_bib( $li->eg_bib_id );
223 $mgr->editor->update_acq_user_request( $request ) or return 0;
225 next unless ($U->is_true( $request->hold ));
227 my $hold = Fieldmapper::action::hold_request->new;
228 $hold->usr( $request->usr );
229 $hold->requestor( $request->usr );
230 $hold->request_time( $request->request_date );
231 $hold->pickup_lib( $request->pickup_lib );
232 $hold->request_lib( $request->pickup_lib );
233 $hold->selection_ou( $request->pickup_lib );
234 $hold->phone_notify( $request->phone_notify );
235 $hold->email_notify( $request->email_notify );
236 $hold->expire_time( $request->need_before );
238 if ($request->holdable_formats) {
239 my $mrm = $mgr->editor->search_metabib_metarecord_source_map( { source => $li->eg_bib_id } )->[0];
241 $hold->hold_type( 'M' );
242 $hold->holdable_formats( $request->holdable_formats );
243 $hold->target( $mrm->metarecord );
247 if (!$hold->target) {
248 $hold->hold_type( 'T' );
249 $hold->target( $li->eg_bib_id );
252 $mgr->editor->create_actor_hold_request( $hold ) or return 0;
258 sub delete_lineitem {
260 $li = $mgr->editor->retrieve_acq_lineitem($li) unless ref $li;
262 # delete the attached lineitem_details
263 my $lid_ids = $mgr->editor->search_acq_lineitem_detail({lineitem => $li->id}, {idlist=>1});
264 for my $lid_id (@$lid_ids) {
265 return 0 unless delete_lineitem_detail($mgr, $lid_id);
269 return $mgr->editor->delete_acq_lineitem($li);
272 # begins and commit transactions as it goes
273 sub create_lineitem_list_assets {
274 my($mgr, $li_ids) = @_;
275 return undef if check_import_li_marc_perms($mgr, $li_ids);
277 # create the bibs/volumes/copies and ingest the records
278 for my $li_id (@$li_ids) {
279 $mgr->editor->xact_begin;
280 my $data = create_lineitem_assets($mgr, $li_id) or return undef;
281 $mgr->editor->xact_commit;
282 # XXX ingest is in-db now
283 #$mgr->push_ingest_queue($data->{li}->eg_bib_id) if $data->{new_bib};
286 $mgr->process_ingest_records;
290 # returns event on error, undef on success
291 sub check_import_li_marc_perms {
292 my($mgr, $li_ids) = @_;
294 # if there are any order records that are not linked to
295 # in-db bib records, verify staff has perms to import order records
296 my $order_li = $mgr->editor->search_acq_lineitem(
297 [{id => $li_ids, eg_bib_id => undef}, {limit => 1}], {idlist => 1})->[0];
300 return $mgr->editor->die_event unless
301 $mgr->editor->allowed('IMPORT_ACQ_LINEITEM_BIB_RECORD');
308 # ----------------------------------------------------------------------------
309 # if all of the lineitem details for this lineitem have
310 # been received, mark the lineitem as received
311 # returns 1 on non-received, li on received, 0 on error
312 # ----------------------------------------------------------------------------
313 sub check_lineitem_received {
314 my($mgr, $li_id) = @_;
316 my $non_recv = $mgr->editor->search_acq_lineitem_detail(
317 {recv_time => undef, lineitem => $li_id}, {idlist=>1});
319 return 1 if @$non_recv;
321 my $li = $mgr->editor->retrieve_acq_lineitem($li_id);
322 $li->state('received');
323 return update_lineitem($mgr, $li);
326 sub receive_lineitem {
327 my($mgr, $li_id, $skip_complete_check) = @_;
328 my $li = $mgr->editor->retrieve_acq_lineitem($li_id) or return 0;
330 my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
331 {lineitem => $li_id, recv_time => undef}, {idlist => 1});
333 for my $lid_id (@$lid_ids) {
334 receive_lineitem_detail($mgr, $lid_id, 1) or return 0;
338 $li->state('received');
339 update_lineitem($mgr, $li) or return 0;
340 return 1 if $skip_complete_check;
342 return check_purchase_order_received($mgr, $li->purchase_order);
345 sub rollback_receive_lineitem {
346 my($mgr, $li_id) = @_;
347 my $li = $mgr->editor->retrieve_acq_lineitem($li_id) or return 0;
349 my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
350 {lineitem => $li_id, recv_time => {'!=' => undef}}, {idlist => 1});
352 for my $lid_id (@$lid_ids) {
353 rollback_receive_lineitem_detail($mgr, $lid_id, 1) or return 0;
357 $li->state('on-order');
358 return update_lineitem($mgr, $li);
361 # ----------------------------------------------------------------------------
363 # ----------------------------------------------------------------------------
364 sub create_lineitem_detail {
365 my($mgr, %args) = @_;
366 my $lid = Fieldmapper::acq::lineitem_detail->new;
367 $lid->$_($args{$_}) for keys %args;
370 return $mgr->editor->create_acq_lineitem_detail($lid);
374 # flesh out any required data with default values where appropriate
375 sub complete_lineitem_detail {
377 unless($lid->barcode) {
378 my $pfx = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.tmp_barcode_prefix') || 'ACQ';
379 $lid->barcode($pfx.$lid->id);
382 unless($lid->cn_label) {
383 my $pfx = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.tmp_callnumber_prefix') || 'ACQ';
384 $lid->cn_label($pfx.$lid->id);
387 if(!$lid->location and my $loc = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.default_copy_location')) {
388 $lid->location($loc);
391 if(!$lid->circ_modifier and my $mod = get_default_circ_modifier($mgr, $lid->owning_lib)) {
392 $lid->circ_modifier($mod);
395 $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
399 sub get_default_circ_modifier {
401 my $mod = $mgr->cache($org, 'def_circ_mod');
403 $mod = $U->ou_ancestor_setting_value($org, 'acq.default_circ_modifier');
404 return $mgr->cache($org, 'def_circ_mod', $mod) if $mod;
408 sub delete_lineitem_detail {
410 $lid = $mgr->editor->retrieve_acq_lineitem_detail($lid) unless ref $lid;
411 return $mgr->editor->delete_acq_lineitem_detail($lid);
415 sub receive_lineitem_detail {
416 my($mgr, $lid_id, $skip_complete_check) = @_;
417 my $e = $mgr->editor;
419 my $lid = $e->retrieve_acq_lineitem_detail([
423 acqlid => ['fund_debit']
428 return 1 if $lid->recv_time;
430 $lid->recv_time('now');
431 $e->update_acq_lineitem_detail($lid) or return 0;
433 my $copy = $e->retrieve_asset_copy($lid->eg_copy_id) or return 0;
434 $copy->status(OILS_COPY_STATUS_IN_PROCESS);
435 $copy->edit_date('now');
436 $copy->editor($e->requestor->id);
437 $e->update_asset_copy($copy) or return 0;
439 if($lid->fund_debit) {
440 $lid->fund_debit->encumbrance('f');
441 $e->update_acq_fund_debit($lid->fund_debit) or return 0;
446 return 1 if $skip_complete_check;
448 my $li = check_lineitem_received($mgr, $lid->lineitem) or return 0;
449 return 1 if $li == 1; # li not received
451 return check_purchase_order_received($mgr, $li->purchase_order);
455 sub rollback_receive_lineitem_detail {
456 my($mgr, $lid_id) = @_;
457 my $e = $mgr->editor;
459 my $lid = $e->retrieve_acq_lineitem_detail([
463 acqlid => ['fund_debit']
468 return 1 unless $lid->recv_time;
470 $lid->clear_recv_time;
471 $e->update_acq_lineitem_detail($lid) or return 0;
473 my $copy = $e->retrieve_asset_copy($lid->eg_copy_id) or return 0;
474 $copy->status(OILS_COPY_STATUS_ON_ORDER);
475 $copy->edit_date('now');
476 $copy->editor($e->requestor->id);
477 $e->update_asset_copy($copy) or return 0;
479 if($lid->fund_debit) {
480 $lid->fund_debit->encumbrance('t');
481 $e->update_acq_fund_debit($lid->fund_debit) or return 0;
488 # ----------------------------------------------------------------------------
490 # ----------------------------------------------------------------------------
491 sub set_lineitem_attr {
492 my($mgr, %args) = @_;
493 my $attr_type = $args{attr_type};
495 # first, see if it's already set. May just need to overwrite it
496 my $attr = $mgr->editor->search_acq_lineitem_attr({
497 lineitem => $args{lineitem},
498 attr_type => $args{attr_type},
499 attr_name => $args{attr_name}
503 $attr->attr_value($args{attr_value});
504 return $attr if $mgr->editor->update_acq_lineitem_attr($attr);
509 $attr = Fieldmapper::acq::lineitem_attr->new;
510 $attr->$_($args{$_}) for keys %args;
512 unless($attr->definition) {
513 my $find = "search_acq_$attr_type";
514 my $attr_def_id = $mgr->editor->$find({code => $attr->attr_name}, {idlist=>1})->[0] or return 0;
515 $attr->definition($attr_def_id);
517 return $mgr->editor->create_acq_lineitem_attr($attr);
523 my $attrs = $li->attributes;
524 my ($marc_estimated, $local_estimated, $local_actual, $prov_estimated, $prov_actual);
526 for my $attr (@$attrs) {
527 if($attr->attr_name eq 'estimated_price') {
528 $local_estimated = $attr->attr_value
529 if $attr->attr_type eq 'lineitem_local_attr_definition';
530 $prov_estimated = $attr->attr_value
531 if $attr->attr_type eq 'lineitem_prov_attr_definition';
532 $marc_estimated = $attr->attr_value
533 if $attr->attr_type eq 'lineitem_marc_attr_definition';
535 } elsif($attr->attr_name eq 'actual_price') {
536 $local_actual = $attr->attr_value
537 if $attr->attr_type eq 'lineitem_local_attr_definition';
538 $prov_actual = $attr->attr_value
539 if $attr->attr_type eq 'lineitem_prov_attr_definition';
543 return ($local_actual, 1) if $local_actual;
544 return ($prov_actual, 2) if $prov_actual;
545 return ($local_estimated, 1) if $local_estimated;
546 return ($prov_estimated, 2) if $prov_estimated;
547 return ($marc_estimated, 3);
551 # ----------------------------------------------------------------------------
553 # ----------------------------------------------------------------------------
554 sub create_lineitem_debits {
555 my($mgr, $li, $price, $ptype) = @_;
557 ($price, $ptype) = get_li_price($li) unless $price;
560 $mgr->editor->event(OpenILS::Event->new('ACQ_LINEITEM_NO_PRICE', payload => $li->id));
561 $mgr->editor->rollback;
565 unless($li->provider) {
566 $mgr->editor->event(OpenILS::Event->new('ACQ_LINEITEM_NO_PROVIDER', payload => $li->id));
567 $mgr->editor->rollback;
571 my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
572 {lineitem => $li->id},
576 for my $lid_id (@$lid_ids) {
578 my $lid = $mgr->editor->retrieve_acq_lineitem_detail([
581 flesh_fields => {acqlid => ['fund']}
585 create_lineitem_detail_debit($mgr, $li, $lid, $price, $ptype) or return 0;
594 # ptype 1=local, 2=provider, 3=marc
595 sub create_lineitem_detail_debit {
596 my($mgr, $li, $lid, $price, $ptype) = @_;
598 unless(ref $li and ref $li->provider) {
599 $li = $mgr->editor->retrieve_acq_lineitem([
602 flesh_fields => {jub => ['provider']},
607 unless(ref $lid and ref $lid->fund) {
608 $lid = $mgr->editor->retrieve_acq_lineitem_detail([
611 flesh_fields => {acqlid => ['fund']}
616 my $ctype = $lid->fund->currency_type;
619 if($ptype == 2) { # price from vendor
620 $ctype = $li->provider->currency_type;
621 $amount = currency_conversion($mgr, $ctype, $lid->fund->currency_type, $price);
624 my $debit = create_fund_debit(
626 fund => $lid->fund->id,
627 origin_amount => $price,
628 origin_currency_type => $ctype,
632 $lid->fund_debit($debit->id);
633 $lid->fund($lid->fund->id);
634 $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
639 # ----------------------------------------------------------------------------
641 # ----------------------------------------------------------------------------
642 sub create_fund_debit {
643 my($mgr, %args) = @_;
645 # Verify the fund is not being spent beyond the hard stop amount
646 my $fund = $mgr->editor->retrieve_acq_fund($args{fund}) or return 0;
648 if($fund->balance_stop_percent) {
650 my $balance = $mgr->editor->search_acq_fund_combined_balance({fund => $fund->id})->[0];
651 my $allocations = $mgr->editor->search_acq_fund_allocation_total({fund => $fund->id})->[0];
652 $balance = ($balance) ? $balance->amount : 0;
653 $allocations = ($allocations) ? $allocations->amount : 0;
656 $allocations == 0 || # if no allocations were ever made, assume we have hit the stop percent
657 ( ( ( ($balance - $args{amount}) / $allocations ) * 100 ) < $fund->balance_stop_percent))
659 $mgr->editor->event(OpenILS::Event->new(
660 'FUND_EXCEEDS_STOP_PERCENT',
661 payload => {fund => $fund->id, debit_amount => $args{amount}}
667 my $debit = Fieldmapper::acq::fund_debit->new;
668 $debit->debit_type('purchase');
669 $debit->encumbrance('t');
670 $debit->$_($args{$_}) for keys %args;
672 $mgr->add_debit($debit->amount);
673 return $mgr->editor->create_acq_fund_debit($debit);
676 sub currency_conversion {
677 my($mgr, $src_currency, $dest_currency, $amount) = @_;
678 my $result = $mgr->editor->json_query(
679 {from => ['acq.exchange_ratio', $src_currency, $dest_currency, $amount]});
680 return $result->[0]->{'acq.exchange_ratio'};
684 # ----------------------------------------------------------------------------
686 # ----------------------------------------------------------------------------
687 sub create_picklist {
688 my($mgr, %args) = @_;
689 my $picklist = Fieldmapper::acq::picklist->new;
690 $picklist->creator($mgr->editor->requestor->id);
691 $picklist->owner($picklist->creator);
692 $picklist->editor($picklist->creator);
693 $picklist->create_time('now');
694 $picklist->edit_time('now');
695 $picklist->org_unit($mgr->editor->requestor->ws_ou);
696 $picklist->owner($mgr->editor->requestor->id);
697 $picklist->$_($args{$_}) for keys %args;
699 $mgr->picklist($picklist);
700 return $mgr->editor->create_acq_picklist($picklist);
703 sub update_picklist {
704 my($mgr, $picklist) = @_;
705 $picklist = $mgr->editor->retrieve_acq_picklist($picklist) unless ref $picklist;
706 $picklist->edit_time('now');
707 $picklist->editor($mgr->editor->requestor->id);
708 $mgr->picklist($picklist);
709 return $picklist if $mgr->editor->update_acq_picklist($picklist);
713 sub delete_picklist {
714 my($mgr, $picklist) = @_;
715 $picklist = $mgr->editor->retrieve_acq_picklist($picklist) unless ref $picklist;
717 # delete all 'new' lineitems
718 my $li_ids = $mgr->editor->search_acq_lineitem({picklist => $picklist->id, state => 'new'}, {idlist => 1});
719 for my $li_id (@$li_ids) {
720 my $li = $mgr->editor->retrieve_acq_lineitem($li_id);
721 return 0 unless delete_lineitem($mgr, $li);
725 # detach all non-'new' lineitems
726 $li_ids = $mgr->editor->search_acq_lineitem({picklist => $picklist->id, state => {'!=' => 'new'}}, {idlist => 1});
727 for my $li_id (@$li_ids) {
728 my $li = $mgr->editor->retrieve_acq_lineitem($li_id);
730 return 0 unless update_lineitem($mgr, $li);
734 # remove any picklist-specific object perms
735 my $ops = $mgr->editor->search_permission_usr_object_perm_map({object_type => 'acqpl', object_id => ''.$picklist->id});
737 return 0 unless $mgr->editor->delete_usr_object_perm_map($op);
740 return $mgr->editor->delete_acq_picklist($picklist);
743 # ----------------------------------------------------------------------------
745 # ----------------------------------------------------------------------------
746 sub update_purchase_order {
748 $po = $mgr->editor->retrieve_acq_purchase_order($po) unless ref $po;
749 $po->editor($mgr->editor->requestor->id);
750 $po->edit_time('now');
751 $mgr->purchase_order($po);
752 return $po if $mgr->editor->update_acq_purchase_order($po);
756 sub create_purchase_order {
757 my($mgr, %args) = @_;
759 # verify the chosen provider is still active
760 my $provider = $mgr->editor->retrieve_acq_provider($args{provider}) or return 0;
761 unless($U->is_true($provider->active)) {
762 $logger->error("provider is not active. cannot create PO");
763 $mgr->editor->event(OpenILS::Event->new('ACQ_PROVIDER_INACTIVE'));
767 my $po = Fieldmapper::acq::purchase_order->new;
768 $po->creator($mgr->editor->requestor->id);
769 $po->editor($mgr->editor->requestor->id);
770 $po->owner($mgr->editor->requestor->id);
771 $po->edit_time('now');
772 $po->create_time('now');
773 $po->state('pending');
774 $po->ordering_agency($mgr->editor->requestor->ws_ou);
775 $po->$_($args{$_}) for keys %args;
777 $mgr->purchase_order($po);
778 return $mgr->editor->create_acq_purchase_order($po);
781 # ----------------------------------------------------------------------------
782 # if all of the lineitems for this PO are received,
783 # mark the PO as received
784 # ----------------------------------------------------------------------------
785 sub check_purchase_order_received {
786 my($mgr, $po_id) = @_;
788 my $non_recv_li = $mgr->editor->search_acq_lineitem(
789 { purchase_order => $po_id,
790 state => {'!=' => 'received'}
793 return 1 if @$non_recv_li;
795 my $po = $mgr->editor->retrieve_acq_purchase_order($po_id);
796 $po->state('received');
797 return update_purchase_order($mgr, $po);
801 # ----------------------------------------------------------------------------
802 # Bib, Callnumber, and Copy data
803 # ----------------------------------------------------------------------------
805 sub create_lineitem_assets {
806 my($mgr, $li_id) = @_;
809 my $li = $mgr->editor->retrieve_acq_lineitem([
812 flesh_fields => {jub => ['purchase_order', 'attributes']}
816 # -----------------------------------------------------------------
817 # first, create the bib record if necessary
818 # -----------------------------------------------------------------
820 unless($li->eg_bib_id) {
821 create_bib($mgr, $li) or return 0;
826 # -----------------------------------------------------------------
827 # The lineitem is going live, promote user request holds to real holds
828 # -----------------------------------------------------------------
829 promote_lineitem_holds($mgr, $li) or return 0;
831 my $li_details = $mgr->editor->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
833 # -----------------------------------------------------------------
834 # for each lineitem_detail, create the volume if necessary, create
835 # a copy, and link them all together.
836 # -----------------------------------------------------------------
838 for my $lid_id (@{$li_details}) {
840 my $lid = $mgr->editor->retrieve_acq_lineitem_detail($lid_id) or return 0;
841 next if $lid->eg_copy_id;
843 # use the same callnumber label for all items within this lineitem
844 $lid->cn_label($first_cn) if $first_cn and not $lid->cn_label;
846 # apply defaults if necessary
847 return 0 unless complete_lineitem_detail($mgr, $lid);
849 $first_cn = $lid->cn_label unless $first_cn;
851 my $org = $lid->owning_lib;
852 my $label = $lid->cn_label;
853 my $bibid = $li->eg_bib_id;
855 my $volume = $mgr->cache($org, "cn.$bibid.$label");
857 $volume = create_volume($mgr, $li, $lid) or return 0;
858 $mgr->cache($org, "cn.$bibid.$label", $volume);
860 create_copy($mgr, $volume, $lid) or return 0;
863 return { li => $li, new_bib => $new_bib };
869 my $record = OpenILS::Application::Cat::BibCommon->biblio_record_xml_import(
874 1, # override tcn collisions
877 if($U->event_code($record)) {
878 $mgr->editor->event($record);
879 $mgr->editor->rollback;
883 $li->eg_bib_id($record->id);
885 return update_lineitem($mgr, $li);
889 my($mgr, $li, $lid) = @_;
892 OpenILS::Application::Cat::AssetCommon->find_or_create_volume(
900 $mgr->editor->event($evt);
908 my($mgr, $volume, $lid) = @_;
909 my $copy = Fieldmapper::asset::copy->new;
911 $copy->loan_duration(2);
912 $copy->fine_level(2);
913 $copy->status(OILS_COPY_STATUS_ON_ORDER);
914 $copy->barcode($lid->barcode);
915 $copy->location($lid->location);
916 $copy->call_number($volume->id);
917 $copy->circ_lib($volume->owning_lib);
918 $copy->circ_modifier($lid->circ_modifier);
920 my $evt = OpenILS::Application::Cat::AssetCommon->create_copy($mgr->editor, $volume, $copy);
922 $mgr->editor->event($evt);
927 $lid->eg_copy_id($copy->id);
928 $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
936 # ----------------------------------------------------------------------------
937 # Workflow: Build a selection list from a Z39.50 search
938 # ----------------------------------------------------------------------------
940 __PACKAGE__->register_method(
942 api_name => 'open-ils.acq.picklist.search.z3950',
945 desc => 'Performs a z3950 federated search and creates a picklist and associated lineitems',
947 {desc => 'Authentication token', type => 'string'},
948 {desc => 'Search definition', type => 'object'},
949 {desc => 'Picklist name, optional', type => 'string'},
955 my($self, $conn, $auth, $search, $name, $options) = @_;
956 my $e = new_editor(authtoken=>$auth);
957 return $e->event unless $e->checkauth;
958 return $e->event unless $e->allowed('CREATE_PICKLIST');
960 $search->{limit} ||= 10;
963 my $ses = OpenSRF::AppSession->create('open-ils.search');
964 my $req = $ses->request('open-ils.search.z3950.search_class', $auth, $search);
969 while(my $resp = $req->recv(timeout=>60)) {
972 my $e = new_editor(requestor=>$e->requestor, xact=>1);
973 $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
974 $picklist = zsearch_build_pl($mgr, $name);
978 my $result = $resp->content;
979 my $count = $result->{count};
980 $mgr->total( (($count < $search->{limit}) ? $count : $search->{limit})+1 );
982 for my $rec (@{$result->{records}}) {
984 my $li = create_lineitem($mgr,
985 picklist => $picklist->id,
986 source_label => $result->{service},
987 marc => $rec->{marcxml},
988 eg_bib_id => $rec->{bibid}
991 if($$options{respond_li}) {
992 $li->attributes($mgr->editor->search_acq_lineitem_attr({lineitem => $li->id}))
993 if $$options{flesh_attrs};
994 $li->clear_marc if $$options{clear_marc};
995 $mgr->respond(lineitem => $li);
1002 $mgr->editor->commit;
1003 return $mgr->respond_complete;
1006 sub zsearch_build_pl {
1007 my($mgr, $name) = @_;
1010 my $picklist = $mgr->editor->search_acq_picklist({
1011 owner => $mgr->editor->requestor->id,
1015 if($name eq '' and $picklist) {
1016 return 0 unless delete_picklist($mgr, $picklist);
1020 return update_picklist($mgr, $picklist) if $picklist;
1021 return create_picklist($mgr, name => $name);
1025 # ----------------------------------------------------------------------------
1026 # Workflow: Build a selection list / PO by importing a batch of MARC records
1027 # ----------------------------------------------------------------------------
1029 __PACKAGE__->register_method(
1030 method => 'upload_records',
1031 api_name => 'open-ils.acq.process_upload_records',
1035 sub upload_records {
1036 my($self, $conn, $auth, $key) = @_;
1038 my $e = new_editor(authtoken => $auth, xact => 1);
1039 return $e->die_event unless $e->checkauth;
1040 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1042 my $cache = OpenSRF::Utils::Cache->new;
1044 my $data = $cache->get_cache("vandelay_import_spool_$key");
1045 my $purpose = $data->{purpose};
1046 my $filename = $data->{path};
1047 my $provider = $data->{provider};
1048 my $picklist = $data->{picklist};
1049 my $create_po = $data->{create_po};
1050 my $ordering_agency = $data->{ordering_agency};
1051 my $create_assets = $data->{create_assets};
1055 unless(-r $filename) {
1056 $logger->error("unable to read MARC file $filename");
1058 return OpenILS::Event->new('FILE_UPLOAD_ERROR', payload => {filename => $filename});
1061 $provider = $e->retrieve_acq_provider($provider) or return $e->die_event;
1064 $picklist = $e->retrieve_acq_picklist($picklist) or return $e->die_event;
1065 if($picklist->owner != $e->requestor->id) {
1066 return $e->die_event unless
1067 $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
1069 $mgr->picklist($picklist);
1074 $po = create_purchase_order($mgr,
1075 ordering_agency => $ordering_agency,
1076 provider => $provider->id,
1078 ) or return $mgr->editor->die_event;
1081 $logger->info("acq processing MARC file=$filename");
1083 my $marctype = 'USMARC'; # ?
1084 my $batch = new MARC::Batch ($marctype, $filename);
1099 } catch Error with {
1101 $logger->warn("Proccessing of record $count in set $key failed with error $err. Skipping this record");
1108 ($xml = $r->as_xml_record()) =~ s/\n//sog;
1109 $xml =~ s/^<\?xml.+\?\s*>//go;
1110 $xml =~ s/>\s+</></go;
1111 $xml =~ s/\p{Cc}//go;
1112 $xml = $U->entityize($xml);
1113 $xml =~ s/[\x00-\x1f]//go;
1115 } catch Error with {
1117 $logger->warn("Proccessing XML of record $count in set $key failed with error $err. Skipping this record");
1120 next if $err or not $xml;
1123 source_label => $provider->code,
1124 provider => $provider->id,
1128 $args{picklist} = $picklist->id if $picklist;
1130 $args{purchase_order} = $po->id;
1131 $args{state} = 'on-order';
1134 my $li = create_lineitem($mgr, %args) or return $mgr->editor->die_event;
1136 $li->provider($provider); # flesh it, we'll need it later
1138 import_lineitem_details($mgr, $ordering_agency, $li) or return $mgr->editor->die_event;
1141 push(@li_list, $li->id);
1147 $cache->delete_cache('vandelay_import_spool_' . $key);
1149 if($create_assets) {
1150 create_lineitem_list_assets($mgr, \@li_list) or return $e->die_event;
1153 return $mgr->respond_complete;
1156 sub import_lineitem_details {
1157 my($mgr, $ordering_agency, $li) = @_;
1159 my $holdings = $mgr->editor->json_query({from => ['acq.extract_provider_holding_data', $li->id]});
1160 return 1 unless @$holdings;
1161 my $org_path = $U->get_org_ancestors($ordering_agency);
1162 $org_path = [ reverse (@$org_path) ];
1167 # create a lineitem detail for each copy in the data
1169 my $compiled = extract_lineitem_detail_data($mgr, $org_path, $holdings, $idx);
1170 last unless defined $compiled;
1171 return 0 unless $compiled;
1173 # this takes the price of the last copy and uses it as the lineitem price
1174 # need to determine if a given record would include different prices for the same item
1175 $price = $$compiled{price};
1177 for(1..$$compiled{quantity}) {
1178 my $lid = create_lineitem_detail($mgr,
1179 lineitem => $li->id,
1180 owning_lib => $$compiled{owning_lib},
1181 cn_label => $$compiled{call_number},
1182 fund => $$compiled{fund},
1183 circ_modifier => $$compiled{circ_modifier},
1184 note => $$compiled{note},
1185 location => $$compiled{copy_location},
1186 collection_code => $$compiled{collection_code}
1194 # set the price attr so we'll know the source of the price
1197 attr_name => 'estimated_price',
1198 attr_type => 'lineitem_local_attr_definition',
1199 attr_value => $price,
1203 # if we're creating a purchase order, create the debits
1204 if($li->purchase_order) {
1205 create_lineitem_debits($mgr, $li, $price, 2) or return 0;
1212 # return hash on success, 0 on error, undef on no more holdings
1213 sub extract_lineitem_detail_data {
1214 my($mgr, $org_path, $holdings, $index) = @_;
1216 my @data_list = grep { $_->{holding} eq $index } @$holdings;
1217 return undef unless @data_list;
1219 my %compiled = map { $_->{attr} => $_->{data} } @data_list;
1220 my $base_org = $$org_path[0];
1224 $logger->error("Item import extraction error: $msg");
1225 $logger->error('Holdings Data: ' . OpenSRF::Utils::JSON->perl2JSON(\%compiled));
1226 $mgr->editor->rollback;
1227 $mgr->editor->event(OpenILS::Event->new('ACQ_IMPORT_ERROR', payload => $msg));
1231 $compiled{quantity} ||= 1;
1233 # ---------------------------------------------------------------------
1235 my $code = $compiled{fund_code};
1236 return $killme->('no fund code provided') unless $code;
1238 my $fund = $mgr->cache($base_org, "fund.$code");
1240 # search up the org tree for the most appropriate fund
1241 for my $org (@$org_path) {
1242 $fund = $mgr->editor->search_acq_fund(
1243 {org => $org, code => $code, year => DateTime->now->year}, {idlist => 1})->[0];
1247 return $killme->("no fund with code $code at orgs [@$org_path]") unless $fund;
1248 $compiled{fund} = $fund;
1249 $mgr->cache($base_org, "fund.$code", $fund);
1252 # ---------------------------------------------------------------------
1254 my $sn = $compiled{owning_lib};
1255 return $killme->('no owning_lib defined') unless $sn;
1257 $mgr->cache($base_org, "orgsn.$sn") ||
1258 $mgr->editor->search_actor_org_unit({shortname => $sn}, {idlist => 1})->[0];
1259 return $killme->("invalid owning_lib defined: $sn") unless $org_id;
1260 $compiled{owning_lib} = $org_id;
1261 $mgr->cache($$org_path[0], "orgsn.$sn", $org_id);
1264 # ---------------------------------------------------------------------
1267 $code = $compiled{circ_modifier};
1271 $mod = $mgr->cache($base_org, "mod.$code") ||
1272 $mgr->editor->retrieve_config_circ_modifier($code);
1273 return $killme->("invlalid circ_modifier $code") unless $mod;
1274 $mgr->cache($base_org, "mod.$code", $mod);
1278 $mod = get_default_circ_modifier($mgr, $base_org)
1279 or return $killme->('no circ_modifier defined');
1282 $compiled{circ_modifier} = $mod;
1285 # ---------------------------------------------------------------------
1287 my $name = $compiled{copy_location};
1289 my $loc = $mgr->cache($base_org, "copy_loc.$name");
1291 for my $org (@$org_path) {
1292 $loc = $mgr->editor->search_asset_copy_location(
1293 {owning_lib => $org, name => $name}, {idlist => 1})->[0];
1297 return $killme->("Invalid copy location $name") unless $loc;
1298 $compiled{copy_location} = $loc;
1299 $mgr->cache($base_org, "copy_loc.$name", $loc);
1307 # ----------------------------------------------------------------------------
1308 # Workflow: Given an existing purchase order, import/create the bibs,
1309 # callnumber and copy objects
1310 # ----------------------------------------------------------------------------
1312 __PACKAGE__->register_method(
1313 method => 'create_po_assets',
1314 api_name => 'open-ils.acq.purchase_order.assets.create',
1316 desc => q/Creates assets for each lineitem in the purchase order/,
1318 {desc => 'Authentication token', type => 'string'},
1319 {desc => 'The purchase order id', type => 'number'},
1321 return => {desc => 'Streams a total versus completed counts object, event on error'}
1325 sub create_po_assets {
1326 my($self, $conn, $auth, $po_id) = @_;
1328 my $e = new_editor(authtoken=>$auth, xact=>1);
1329 return $e->die_event unless $e->checkauth;
1330 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1332 my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1334 my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
1336 # it's ugly, but it's fast. Get the total count of lineitem detail objects to process
1337 my $lid_total = $e->json_query({
1338 select => { acqlid => [{aggregate => 1, transform => 'count', column => 'id'}] },
1344 join => {acqpo => {fkey => 'purchase_order', field => 'id'}}
1348 where => {'+acqpo' => {id => $po_id}}
1351 $mgr->total(scalar(@$li_ids) + $lid_total);
1353 create_lineitem_list_assets($mgr, $li_ids) or return $e->die_event;
1356 update_purchase_order($mgr, $po) or return $e->die_event;
1359 return $mgr->respond_complete;
1364 __PACKAGE__->register_method(
1365 method => 'create_purchase_order_api',
1366 api_name => 'open-ils.acq.purchase_order.create',
1368 desc => 'Creates a new purchase order',
1370 {desc => 'Authentication token', type => 'string'},
1371 {desc => 'purchase_order to create', type => 'object'}
1373 return => {desc => 'The purchase order id, Event on failure'}
1377 sub create_purchase_order_api {
1378 my($self, $conn, $auth, $po, $args) = @_;
1381 my $e = new_editor(xact=>1, authtoken=>$auth);
1382 return $e->die_event unless $e->checkauth;
1383 return $e->die_event unless $e->allowed('CREATE_PURCHASE_ORDER', $po->ordering_agency);
1384 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1387 my %pargs = (ordering_agency => $e->requestor->ws_ou); # default
1388 $pargs{provider} = $po->provider if $po->provider;
1389 $pargs{ordering_agency} = $po->ordering_agency if $po->ordering_agency;
1390 $po = create_purchase_order($mgr, %pargs) or return $e->die_event;
1392 my $li_ids = $$args{lineitems};
1396 for my $li_id (@$li_ids) {
1398 my $li = $e->retrieve_acq_lineitem([
1400 {flesh => 1, flesh_fields => {jub => ['attributes']}}
1401 ]) or return $e->die_event;
1403 $li->provider($po->provider);
1404 $li->purchase_order($po->id);
1405 $li->state('pending-order');
1406 update_lineitem($mgr, $li) or return $e->die_event;
1409 create_lineitem_debits($mgr, $li) or return $e->die_event;
1413 # commit before starting the asset creation
1416 if($li_ids and $$args{create_assets}) {
1417 create_lineitem_list_assets($mgr, $li_ids) or return $e->die_event;
1420 return $mgr->respond_complete;
1424 __PACKAGE__->register_method(
1425 method => 'lineitem_detail_CUD_batch',
1426 api_name => 'open-ils.acq.lineitem_detail.cud.batch',
1429 desc => q/Creates a new purchase order line item detail.
1430 Additionally creates the associated fund_debit/,
1432 {desc => 'Authentication token', type => 'string'},
1433 {desc => 'List of lineitem_details to create', type => 'array'},
1435 return => {desc => 'Streaming response of current position in the array'}
1439 sub lineitem_detail_CUD_batch {
1440 my($self, $conn, $auth, $li_details) = @_;
1442 my $e = new_editor(xact=>1, authtoken=>$auth);
1443 return $e->die_event unless $e->checkauth;
1444 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1448 $mgr->total(scalar(@$li_details));
1452 for my $lid (@$li_details) {
1454 my $li = $li_cache{$lid->lineitem} || $e->retrieve_acq_lineitem($lid->lineitem);
1457 create_lineitem_detail($mgr, %{$lid->to_bare_hash}) or return $e->die_event;
1459 } elsif($lid->ischanged) {
1460 $e->update_acq_lineitem_detail($lid) or return $e->die_event;
1462 } elsif($lid->isdeleted) {
1463 delete_lineitem_detail($mgr, $lid) or return $e->die_event;
1466 $mgr->respond(li => $li);
1467 $li_cache{$lid->lineitem} = $li;
1471 return $mgr->respond_complete;
1475 __PACKAGE__->register_method(
1476 method => 'receive_po_api',
1477 api_name => 'open-ils.acq.purchase_order.receive'
1480 sub receive_po_api {
1481 my($self, $conn, $auth, $po_id) = @_;
1482 my $e = new_editor(xact => 1, authtoken => $auth);
1483 return $e->die_event unless $e->checkauth;
1484 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1486 my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1487 return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1489 my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
1491 for my $li_id (@$li_ids) {
1492 receive_lineitem($mgr, $li_id) or return $e->die_event;
1496 $po->state('received');
1497 update_purchase_order($mgr, $po) or return $e->die_event;
1500 return $mgr->respond_complete;
1504 __PACKAGE__->register_method(
1505 method => 'receive_lineitem_detail_api',
1506 api_name => 'open-ils.acq.lineitem_detail.receive',
1508 desc => 'Mark a lineitem_detail as received',
1510 {desc => 'Authentication token', type => 'string'},
1511 {desc => 'lineitem detail ID', type => 'number'}
1513 return => {desc => '1 on success, Event on error'}
1517 sub receive_lineitem_detail_api {
1518 my($self, $conn, $auth, $lid_id) = @_;
1520 my $e = new_editor(xact=>1, authtoken=>$auth);
1521 return $e->die_event unless $e->checkauth;
1522 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1524 my $lid = $e->retrieve_acq_lineitem_detail([
1528 acqlid => ['lineitem'],
1529 jub => ['purchase_order']
1534 return $e->die_event unless $e->allowed(
1535 'RECEIVE_PURCHASE_ORDER', $lid->lineitem->purchase_order->ordering_agency);
1537 receive_lineitem_detail($mgr, $lid_id) or return $e->die_event;
1542 __PACKAGE__->register_method(
1543 method => 'receive_lineitem_api',
1544 api_name => 'open-ils.acq.lineitem.receive',
1546 desc => 'Mark a lineitem as received',
1548 {desc => 'Authentication token', type => 'string'},
1549 {desc => 'lineitem detail ID', type => 'number'}
1551 return => {desc => '1 on success, Event on error'}
1555 sub receive_lineitem_api {
1556 my($self, $conn, $auth, $li_id) = @_;
1558 my $e = new_editor(xact=>1, authtoken=>$auth);
1559 return $e->die_event unless $e->checkauth;
1560 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1562 my $li = $e->retrieve_acq_lineitem([
1566 jub => ['purchase_order']
1569 ]) or return $e->die_event;
1571 return $e->die_event unless $e->allowed(
1572 'RECEIVE_PURCHASE_ORDER', $li->purchase_order->ordering_agency);
1574 receive_lineitem($mgr, $li_id) or return $e->die_event;
1580 __PACKAGE__->register_method(
1581 method => 'rollback_receive_po_api',
1582 api_name => 'open-ils.acq.purchase_order.receive.rollback'
1585 sub rollback_receive_po_api {
1586 my($self, $conn, $auth, $po_id) = @_;
1587 my $e = new_editor(xact => 1, authtoken => $auth);
1588 return $e->die_event unless $e->checkauth;
1589 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1591 my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1592 return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1594 my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
1596 for my $li_id (@$li_ids) {
1597 rollback_receive_lineitem($mgr, $li_id) or return $e->die_event;
1601 $po->state('on-order');
1602 update_purchase_order($mgr, $po) or return $e->die_event;
1605 return $mgr->respond_complete;
1609 __PACKAGE__->register_method(
1610 method => 'rollback_receive_lineitem_detail_api',
1611 api_name => 'open-ils.acq.lineitem_detail.receive.rollback',
1613 desc => 'Mark a lineitem_detail as received',
1615 {desc => 'Authentication token', type => 'string'},
1616 {desc => 'lineitem detail ID', type => 'number'}
1618 return => {desc => '1 on success, Event on error'}
1622 sub rollback_receive_lineitem_detail_api {
1623 my($self, $conn, $auth, $lid_id) = @_;
1625 my $e = new_editor(xact=>1, authtoken=>$auth);
1626 return $e->die_event unless $e->checkauth;
1627 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1629 my $lid = $e->retrieve_acq_lineitem_detail([
1633 acqlid => ['lineitem'],
1634 jub => ['purchase_order']
1638 my $li = $lid->lineitem;
1639 my $po = $li->purchase_order;
1641 return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1642 rollback_receive_lineitem_detail($mgr, $lid_id) or return $e->die_event;
1644 $li->state('on-order');
1645 $po->state('on-order');
1646 udpate_lineitem($mgr, $li) or return $e->die_event;
1647 udpate_purchase_order($mgr, $po) or return $e->die_event;
1653 __PACKAGE__->register_method(
1654 method => 'rollback_receive_lineitem_api',
1655 api_name => 'open-ils.acq.lineitem.receive.rollback',
1657 desc => 'Mark a lineitem as received',
1659 {desc => 'Authentication token', type => 'string'},
1660 {desc => 'lineitem detail ID', type => 'number'}
1662 return => {desc => '1 on success, Event on error'}
1666 sub rollback_receive_lineitem_api {
1667 my($self, $conn, $auth, $li_id) = @_;
1669 my $e = new_editor(xact=>1, authtoken=>$auth);
1670 return $e->die_event unless $e->checkauth;
1671 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1673 my $li = $e->retrieve_acq_lineitem_detail([
1677 jub => ['purchase_order']
1681 my $po = $li->purchase_order;
1683 return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1685 rollback_receive_lineitem($mgr, $li_id) or return $e->die_event;
1687 $po->state('on-order');
1688 update_purchase_order($mgr, $po) or return $e->die_event;
1695 __PACKAGE__->register_method(
1696 method => 'set_lineitem_price_api',
1697 api_name => 'open-ils.acq.lineitem.price.set',
1699 desc => 'Set lineitem price. If debits already exist, update them as well',
1701 {desc => 'Authentication token', type => 'string'},
1702 {desc => 'lineitem ID', type => 'number'}
1704 return => {desc => 'status blob, Event on error'}
1708 sub set_lineitem_price_api {
1709 my($self, $conn, $auth, $li_id, $price, $currency) = @_;
1711 my $e = new_editor(xact=>1, authtoken=>$auth);
1712 return $e->die_event unless $e->checkauth;
1713 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1717 my $li = $e->retrieve_acq_lineitem($li_id) or return $e->die_event;
1719 # update the local attr for estimated price
1722 attr_name => 'estimated_price',
1723 attr_type => 'lineitem_local_attr_definition',
1724 attr_value => $price,
1726 ) or return $e->die_event;
1728 my $lid_ids = $e->search_acq_lineitem_detail(
1729 {lineitem => $li_id, fund_debit => {'!=' => undef}},
1733 for my $lid_id (@$lid_ids) {
1735 my $lid = $e->retrieve_acq_lineitem_detail([
1737 flesh => 1, flesh_fields => {acqlid => ['fund', 'fund_debit']}}
1740 # onless otherwise specified, assume currency of new price is same as currency type of the fund
1741 $currency ||= $lid->fund->currency_type;
1742 my $amount = $price;
1744 if($lid->fund->currency_type ne $currency) {
1745 $amount = currency_conversion($mgr, $currency, $lid->fund->currency_type, $price);
1748 $lid->fund_debit->origin_currency_type($currency);
1749 $lid->fund_debit->origin_amount($price);
1750 $lid->fund_debit->amount($amount);
1752 $e->update_acq_fund_debit($lid->fund_debit) or return $e->die_event;
1758 return $mgr->respond_complete;
1762 __PACKAGE__->register_method(
1763 method => 'clone_picklist_api',
1764 api_name => 'open-ils.acq.picklist.clone',
1766 desc => 'Clones a picklist, including lineitem and lineitem details',
1768 {desc => 'Authentication token', type => 'string'},
1769 {desc => 'Picklist ID', type => 'number'},
1770 {desc => 'New Picklist Name', type => 'string'}
1772 return => {desc => 'status blob, Event on error'}
1776 sub clone_picklist_api {
1777 my($self, $conn, $auth, $pl_id, $name) = @_;
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 $old_pl = $e->retrieve_acq_picklist($pl_id);
1784 my $new_pl = create_picklist($mgr, %{$old_pl->to_bare_hash}, name => $name) or return $e->die_event;
1786 my $li_ids = $e->search_acq_lineitem({picklist => $pl_id}, {idlist => 1});
1788 for my $li_id (@$li_ids) {
1790 # copy the lineitems
1791 my $li = $e->retrieve_acq_lineitem($li_id);
1792 my $new_li = create_lineitem($mgr, %{$li->to_bare_hash}, picklist => $new_pl->id) or return $e->die_event;
1794 my $lid_ids = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist => 1});
1795 for my $lid_id (@$lid_ids) {
1797 # copy the lineitem details
1798 my $lid = $e->retrieve_acq_lineitem_detail($lid_id);
1799 create_lineitem_detail($mgr, %{$lid->to_bare_hash}, lineitem => $new_li->id) or return $e->die_event;
1806 return $mgr->respond_complete;
1810 __PACKAGE__->register_method(
1811 method => 'merge_picklist_api',
1812 api_name => 'open-ils.acq.picklist.merge',
1814 desc => 'Merges 2 or more picklists into a single list',
1816 {desc => 'Authentication token', type => 'string'},
1817 {desc => 'Lead Picklist ID', type => 'number'},
1818 {desc => 'List of subordinate picklist IDs', type => 'array'}
1820 return => {desc => 'status blob, Event on error'}
1824 sub merge_picklist_api {
1825 my($self, $conn, $auth, $lead_pl, $pl_list) = @_;
1827 my $e = new_editor(xact=>1, authtoken=>$auth);
1828 return $e->die_event unless $e->checkauth;
1829 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1831 # XXX perms on each picklist modified
1833 # point all of the lineitems at the lead picklist
1834 my $li_ids = $e->search_acq_lineitem({picklist => $pl_list}, {idlist => 1});
1836 for my $li_id (@$li_ids) {
1837 my $li = $e->retrieve_acq_lineitem($li_id);
1838 $li->picklist($lead_pl);
1839 update_lineitem($mgr, $li) or return $e->die_event;
1843 # now delete the subordinate lists
1844 for my $pl_id (@$pl_list) {
1845 my $pl = $e->retrieve_acq_picklist($pl_id);
1846 $e->delete_acq_picklist($pl) or return $e->die_event;
1850 return $mgr->respond_complete;
1854 __PACKAGE__->register_method(
1855 method => 'delete_picklist_api',
1856 api_name => 'open-ils.acq.picklist.delete',
1858 desc => q/Deletes a picklist. It also deletes any lineitems in the "new" state.
1859 Other attached lineitems are detached'/,
1861 {desc => 'Authentication token', type => 'string'},
1862 {desc => 'Picklist ID to delete', type => 'number'}
1864 return => {desc => '1 on success, Event on error'}
1868 sub delete_picklist_api {
1869 my($self, $conn, $auth, $picklist_id) = @_;
1870 my $e = new_editor(xact=>1, authtoken=>$auth);
1871 return $e->die_event unless $e->checkauth;
1872 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1873 my $pl = $e->retrieve_acq_picklist($picklist_id) or return $e->die_event;
1874 delete_picklist($mgr, $pl) or return $e->die_event;
1876 return $mgr->respond_complete;
1881 __PACKAGE__->register_method(
1882 method => 'activate_purchase_order',
1883 api_name => 'open-ils.acq.purchase_order.activate',
1885 desc => q/Activates a purchase order. This updates the status of the PO
1886 and Lineitems to 'on-order'. Activated PO's are ready for EDI delivery
1889 {desc => 'Authentication token', type => 'string'},
1890 {desc => 'Purchase ID', type => 'number'}
1892 return => {desc => '1 on success, Event on error'}
1896 sub activate_purchase_order {
1897 my($self, $conn, $auth, $po_id) = @_;
1898 my $e = new_editor(xact=>1, authtoken=>$auth);
1899 return $e->die_event unless $e->checkauth;
1900 my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1902 my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1903 return $e->die_event unless $e->allowed('CREATE_PURCHASE_ORDER', $po->ordering_agency);
1905 $po->state('on-order');
1906 update_purchase_order($mgr, $po) or return $e->die_event;
1909 {purchase_order => $po_id, state => 'pending-order'},
1913 while( my $li = $e->search_acq_lineitem($query)->[0] ) {
1914 $li->state('on-order');
1915 update_lineitem($mgr, $li) or return $e->die_event;
1924 __PACKAGE__->register_method(
1925 method => 'split_purchase_order_by_lineitems',
1926 api_name => 'open-ils.acq.purchase_order.split_by_lineitems',
1928 desc => q/Splits a PO into many POs, 1 per lineitem. Only works for
1929 POs a) with more than one lineitems, and b) in the "pending" state./,
1931 {desc => 'Authentication token', type => 'string'},
1932 {desc => 'Purchase order ID', type => 'number'}
1934 return => {desc => 'list of new PO IDs on success, Event on error'}
1938 sub split_purchase_order_by_lineitems {
1939 my ($self, $conn, $auth, $po_id) = @_;
1941 my $e = new_editor("xact" => 1, "authtoken" => $auth);
1942 return $e->die_event unless $e->checkauth;
1944 my $po = $e->retrieve_acq_purchase_order([
1947 "flesh_fields" => {"acqpo" => [qw/lineitems notes/]}
1949 ]) or return $e->die_event;
1951 return $e->die_event
1952 unless $e->allowed("CREATE_PURCHASE_ORDER", $po->ordering_agency);
1954 unless ($po->state eq "pending") {
1956 return new OpenILS::Event("ACQ_PURCHASE_ORDER_TOO_LATE");
1959 unless (@{$po->lineitems} > 1) {
1961 return new OpenILS::Event("ACQ_PURCHASE_ORDER_TOO_SHORT");
1964 # To split an existing PO into many, it seems unwise to just delete the
1965 # original PO, so we'll instead detach all of the original POs' lineitems
1966 # but the first, then create new POs for each of the remaining LIs, and
1967 # then attach the LIs to their new POs.
1969 my @po_ids = ($po->id);
1970 my @moving_li = @{$po->lineitems};
1971 shift @moving_li; # discard first LI
1973 foreach my $li (@moving_li) {
1974 my $new_po = $po->clone;
1976 $new_po->clear_name;
1977 $new_po->creator($e->requestor->id);
1978 $new_po->editor($e->requestor->id);
1979 $new_po->owner($e->requestor->id);
1980 $new_po->edit_time("now");
1981 $new_po->create_time("now");
1983 $new_po = $e->create_acq_purchase_order($new_po);
1985 # Clone any notes attached to the old PO and attach to the new one.
1986 foreach my $note (@{$po->notes}) {
1987 my $new_note = $note->clone;
1988 $new_note->clear_id;
1989 $new_note->edit_time("now");
1990 $new_note->purchase_order($new_po->id);
1991 $e->create_acq_po_note($new_note);
1994 $li->edit_time("now");
1995 $li->purchase_order($new_po->id);
1996 $e->update_acq_lineitem($li);
1998 push @po_ids, $new_po->id;
2001 $po->edit_time("now");
2002 $e->update_acq_purchase_order($po);
2004 return \@po_ids if $e->commit;
2005 return $e->die_event;