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} = [];
29 $self->{conn} = $val if $val;
34 $self->{throttle} = $val if $val;
35 return $self->{throttle};
38 my($self, %other_args) = @_;
39 if($self->throttle and not %other_args) {
41 ($self->{args}->{progress} - $self->{last_respond_progress}) >= $self->throttle
44 $self->conn->respond({ %{$self->{args}}, %other_args });
45 $self->{last_respond_progress} = $self->{args}->{progress};
47 sub respond_complete {
48 my($self, %other_args) = @_;
50 $self->conn->respond_complete({ %{$self->{args}}, %other_args });
55 $self->{args}->{total} = $val if defined $val;
56 $self->{args}->{maximum} = $self->{args}->{total};
57 return $self->{args}->{total};
61 $self->{args}->{purchase_order} = $val if $val;
66 $self->{args}->{picklist} = $val if $val;
71 $self->{args}->{lid} += 1;
72 $self->{args}->{progress} += 1;
77 $self->{args}->{li} += 1;
78 $self->{args}->{progress} += 1;
83 $self->{args}->{copies} += 1;
84 $self->{args}->{progress} += 1;
89 $self->{args}->{bibs} += 1;
90 $self->{args}->{progress} += 1;
94 my($self, $amount) = @_;
95 $self->{args}->{debits_accrued} += $amount;
96 $self->{args}->{progress} += 1;
100 my($self, $editor) = @_;
101 $self->{editor} = $editor if defined $editor;
102 return $self->{editor};
106 $self->{args}->{complete} = 1;
111 my($self, $val) = @_;
112 $self->{ingest_ses} = $val if $val;
113 return $self->{ingest_ses};
116 sub push_ingest_queue {
117 my($self, $rec_id) = @_;
119 $self->ingest_ses(OpenSRF::AppSession->connect('open-ils.ingest'))
120 unless $self->ingest_ses;
122 my $req = $self->ingest_ses->request('open-ils.ingest.full.biblio.record', $rec_id);
124 push(@{$self->{ingest_queue}}, $req);
127 sub process_ingest_records {
129 return unless @{$self->{ingest_queue}};
131 for my $req (@{$self->{ingest_queue}}) {
135 $self->{args}->{indexed} += 1;
136 $self->{args}->{progress} += 1;
141 $self->ingest_ses->disconnect;
146 my($self, $org, $key, $val) = @_;
147 $self->{cache}->{$org} = {} unless $self->{cache}->{org};
148 $self->{cache}->{$org}->{$key} = $val if defined $val;
149 return $self->{cache}->{$org}->{$key};
153 package OpenILS::Application::Acq::Order;
154 use base qw/OpenILS::Application/;
155 use strict; use warnings;
156 # ----------------------------------------------------------------------------
157 # Break up each component of the order process and pieces into managable
158 # actions that can be shared across different workflows
159 # ----------------------------------------------------------------------------
161 use OpenSRF::Utils::Logger qw(:logger);
162 use OpenSRF::Utils::JSON;
163 use OpenILS::Utils::Fieldmapper;
164 use OpenILS::Utils::CStoreEditor q/:funcs/;
165 use OpenILS::Const qw/:const/;
166 use OpenSRF::EX q/:try/;
167 use OpenILS::Application::AppUtils;
168 use OpenILS::Application::Cat::BibCommon;
169 use OpenILS::Application::Cat::AssetCommon;
173 my $U = 'OpenILS::Application::AppUtils';
176 # ----------------------------------------------------------------------------
178 # ----------------------------------------------------------------------------
179 sub create_lineitem {
180 my($mgr, %args) = @_;
181 my $li = Fieldmapper::acq::lineitem->new;
182 $li->creator($mgr->editor->requestor->id);
183 $li->selector($li->creator);
184 $li->editor($li->creator);
185 $li->create_time('now');
186 $li->edit_time('now');
188 $li->$_($args{$_}) for keys %args;
190 return 0 unless update_picklist($mgr, $li->picklist);
193 return $mgr->editor->create_acq_lineitem($li);
196 sub update_lineitem {
198 $li->edit_time('now');
199 $li->editor($mgr->editor->requestor->id);
200 return $li if $mgr->editor->update_acq_lineitem($li);
205 sub delete_lineitem {
207 $li = $mgr->editor->retrieve_acq_lineitem($li) unless ref $li;
210 return 0 unless update_picklist($mgr, $li->picklist);
213 if($li->purchase_order) {
214 return 0 unless update_purchase_order($mgr, $li->purchase_order);
217 # delete the attached lineitem_details
218 my $lid_ids = $mgr->editor->search_acq_lineitem_detail({lineitem => $li->id}, {idlist=>1});
219 for my $lid_id (@$lid_ids) {
220 return 0 unless delete_lineitem_detail($mgr, undef, $lid_id);
223 return $mgr->editor->delete_acq_lineitem($li);
226 # begins and commit transactions as it goes
227 sub create_lineitem_list_assets {
228 my($mgr, $li_ids) = @_;
229 # create the bibs/volumes/copies and ingest the records
230 for my $li_id (@$li_ids) {
231 $mgr->editor->xact_begin;
232 my $data = create_lineitem_assets($mgr, $li_id) or return undef;
233 $mgr->editor->xact_commit;
234 $mgr->push_ingest_queue($data->{li}->eg_bib_id) if $data->{new_bib};
237 $mgr->process_ingest_records;
242 # ----------------------------------------------------------------------------
244 # ----------------------------------------------------------------------------
245 sub create_lineitem_detail {
246 my($mgr, %args) = @_;
247 my $lid = Fieldmapper::acq::lineitem_detail->new;
248 $lid->$_($args{$_}) for keys %args;
249 $mgr->editor->create_acq_lineitem_detail($lid) or return 0;
252 # create some default values
253 unless($lid->barcode) {
254 my $pfx = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.tmp_barcode_prefix') || 'ACQ';
255 $lid->barcode($pfx.$lid->id);
258 unless($lid->cn_label) {
259 my $pfx = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.tmp_callnumber_prefix') || 'ACQ';
260 $lid->cn_label($pfx.$lid->id);
263 if(!$lid->location and my $loc = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.default_copy_location')) {
264 $lid->location($loc);
267 if(!$lid->circ_modifier and my $mod = get_default_circ_modifier($mgr, $lid->owning_lib)) {
268 $lid->circ_modifier($mod);
271 $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
272 my $li = $mgr->editor->retrieve_acq_lineitem($lid->lineitem) or return 0;
273 update_lineitem($mgr, $li) or return 0;
277 sub get_default_circ_modifier {
279 my $mod = $mgr->cache($org, 'def_circ_mod');
281 $mod = $U->ou_ancestor_setting_value($org, 'acq.default_circ_modifier');
282 return $mgr->cache($org, 'def_circ_mod', $mod) if $mod;
286 sub delete_lineitem_detail {
288 $lid = $mgr->editor->retrieve_acq_lineitem_detail($lid) unless ref $lid;
289 return $mgr->editor->delete_acq_lineitem_detail($lid);
293 # ----------------------------------------------------------------------------
295 # ----------------------------------------------------------------------------
296 sub set_lineitem_attr {
297 my($mgr, %args) = @_;
298 my $attr_type = $args{attr_type};
300 # first, see if it's already set. May just need to overwrite it
301 my $attr = $mgr->editor->search_acq_lineitem_attr({
302 lineitem => $args{lineitem},
303 attr_type => $args{attr_type},
304 attr_name => $args{attr_name}
308 $attr->attr_value($args{attr_value});
309 return $attr if $mgr->editor->update_acq_lineitem_attr($attr);
314 $attr = Fieldmapper::acq::lineitem_attr->new;
315 $attr->$_($args{$_}) for keys %args;
317 unless($attr->definition) {
318 my $find = "search_acq_$attr_type";
319 my $attr_def_id = $mgr->editor->$find({code => $attr->attr_name}, {idlist=>1})->[0] or return 0;
320 $attr->definition($attr_def_id);
322 return $mgr->editor->create_acq_lineitem_attr($attr);
328 my $attrs = $li->attributes;
329 my ($marc_estimated, $local_estimated, $local_actual, $prov_estimated, $prov_actual);
331 for my $attr (@$attrs) {
332 if($attr->attr_name eq 'estimated_price') {
333 $local_estimated = $attr->attr_value
334 if $attr->attr_type eq 'lineitem_local_attr_definition';
335 $prov_estimated = $attr->attr_value
336 if $attr->attr_type eq 'lineitem_prov_attr_definition';
337 $marc_estimated = $attr->attr_value
338 if $attr->attr_type eq 'lineitem_marc_attr_definition';
340 } elsif($attr->attr_name eq 'actual_price') {
341 $local_actual = $attr->attr_value
342 if $attr->attr_type eq 'lineitem_local_attr_definition';
343 $prov_actual = $attr->attr_value
344 if $attr->attr_type eq 'lineitem_prov_attr_definition';
348 return ($local_actual, 1) if $local_actual;
349 return ($prov_actual, 2) if $prov_actual;
350 return ($local_estimated, 1) if $local_estimated;
351 return ($prov_estimated, 2) if $prov_estimated;
352 return ($marc_estimated, 3);
356 # ----------------------------------------------------------------------------
358 # ----------------------------------------------------------------------------
359 sub create_lineitem_debits {
360 my($mgr, $li, $price, $ptype) = @_;
362 ($price, $ptype) = get_li_price($li) unless $price;
365 $mgr->editor->event(OpenILS::Event->new('ACQ_LINEITEM_NO_PRICE', payload => $li->id));
366 $mgr->editor->rollback;
370 unless($li->provider) {
371 $mgr->editor->event(OpenILS::Event->new('ACQ_LINEITEM_NO_PROVIDER', payload => $li->id));
372 $mgr->editor->rollback;
376 my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
377 {lineitem => $li->id},
381 for my $lid_id (@$lid_ids) {
383 my $lid = $mgr->editor->retrieve_acq_lineitem_detail([
386 flesh_fields => {acqlid => ['fund']}
390 create_lineitem_detail_debit($mgr, $li, $lid, $price, $ptype) or return 0;
399 # ptype 1=local, 2=provider, 3=marc
400 sub create_lineitem_detail_debit {
401 my($mgr, $li, $lid, $price, $ptype) = @_;
403 unless(ref $li and ref $li->provider) {
404 $li = $mgr->editor->retrieve_acq_lineitem([
407 flesh_fields => {jub => ['provider']},
412 unless(ref $lid and ref $lid->fund) {
413 $lid = $mgr->editor->retrieve_acq_lineitem_detail([
416 flesh_fields => {acqlid => ['fund']}
421 my $ctype = $lid->fund->currency_type;
424 if($ptype == 2) { # price from vendor
425 $ctype = $li->provider->currency_type;
426 $amount = currency_conversion($mgr, $ctype, $lid->fund->currency_type, $price);
429 my $debit = create_fund_debit(
431 fund => $lid->fund->id,
432 origin_amount => $price,
433 origin_currency_type => $ctype,
437 $lid->fund_debit($debit->id);
438 $lid->fund($lid->fund->id);
439 $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
444 # ----------------------------------------------------------------------------
446 # ----------------------------------------------------------------------------
447 sub create_fund_debit {
448 my($mgr, %args) = @_;
449 my $debit = Fieldmapper::acq::fund_debit->new;
450 $debit->debit_type('purchase');
451 $debit->encumbrance('t');
452 $debit->$_($args{$_}) for keys %args;
453 $mgr->add_debit($debit->amount);
454 return $mgr->editor->create_acq_fund_debit($debit);
457 sub currency_conversion {
458 my($mgr, $src_currency, $dest_currency, $amount) = @_;
459 my $result = $mgr->editor->json_query(
460 {from => ['acq.exchange_ratio', $src_currency, $dest_currency, $amount]});
461 return $result->[0]->{'acq.exchange_ratio'};
465 # ----------------------------------------------------------------------------
467 # ----------------------------------------------------------------------------
468 sub create_picklist {
469 my($mgr, %args) = @_;
470 my $picklist = Fieldmapper::acq::picklist->new;
471 $picklist->creator($mgr->editor->requestor->id);
472 $picklist->owner($picklist->creator);
473 $picklist->editor($picklist->creator);
474 $picklist->create_time('now');
475 $picklist->edit_time('now');
476 $picklist->org_unit($mgr->editor->requestor->ws_ou);
477 $picklist->owner($mgr->editor->requestor->id);
478 $picklist->$_($args{$_}) for keys %args;
479 $mgr->picklist($picklist);
480 return $mgr->editor->create_acq_picklist($picklist);
483 sub update_picklist {
484 my($mgr, $picklist) = @_;
485 $picklist = $mgr->editor->retrieve_acq_picklist($picklist) unless ref $picklist;
486 $picklist->edit_time('now');
487 $picklist->editor($mgr->editor->requestor->id);
488 $mgr->picklist($picklist);
489 return $picklist if $mgr->editor->update_acq_picklist($picklist);
493 sub delete_picklist {
494 my($mgr, $picklist) = @_;
495 $picklist = $mgr->editor->retrieve_acq_picklist($picklist) unless ref $picklist;
497 # delete all 'new' lineitems
498 my $lis = $mgr->editor->search_acq_lineitem({picklist => $picklist->id, state => 'new'});
500 return 0 unless delete_lineitem($mgr, $li);
503 # detach all non-'new' lineitems
504 $lis = $mgr->editor->search_acq_lineitem({picklist => $picklist->id, state => {'!=' => 'new'}});
507 return 0 unless update_lineitem($li);
510 # remove any picklist-specific object perms
511 my $ops = $mgr->editor->search_permission_usr_object_perm_map({object_type => 'acqpl', object_id => ''.$picklist->id});
513 return 0 unless $mgr->editor->delete_usr_object_perm_map($op);
516 return $mgr->editor->delete_acq_picklist($picklist);
519 # ----------------------------------------------------------------------------
521 # ----------------------------------------------------------------------------
522 sub update_purchase_order {
524 $po = $mgr->editor->retrieve_acq_purchase_order($po) unless ref $po;
525 $po->editor($mgr->editor->requestor->id);
526 $po->edit_time('now');
527 $mgr->purchase_order($po);
528 return $po if $mgr->editor->update_acq_purchase_order($po);
532 sub create_purchase_order {
533 my($mgr, %args) = @_;
534 my $po = Fieldmapper::acq::purchase_order->new;
535 $po->creator($mgr->editor->requestor->id);
536 $po->editor($mgr->editor->requestor->id);
537 $po->owner($mgr->editor->requestor->id);
538 $po->edit_time('now');
539 $po->create_time('now');
540 $po->ordering_agency($mgr->editor->requestor->ws_ou);
541 $po->$_($args{$_}) for keys %args;
542 $mgr->purchase_order($po);
543 return $mgr->editor->create_acq_purchase_order($po);
547 # ----------------------------------------------------------------------------
548 # Bib, Callnumber, and Copy data
549 # ----------------------------------------------------------------------------
551 sub create_lineitem_assets {
552 my($mgr, $li_id) = @_;
555 my $li = $mgr->editor->retrieve_acq_lineitem([
558 flesh_fields => {jub => ['purchase_order', 'attributes']}
562 # -----------------------------------------------------------------
563 # first, create the bib record if necessary
564 # -----------------------------------------------------------------
566 unless($li->eg_bib_id) {
567 create_bib($mgr, $li) or return 0;
571 my $li_details = $mgr->editor->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
573 # -----------------------------------------------------------------
574 # for each lineitem_detail, create the volume if necessary, create
575 # a copy, and link them all together.
576 # -----------------------------------------------------------------
577 for my $lid_id (@{$li_details}) {
579 my $lid = $mgr->editor->retrieve_acq_lineitem_detail($lid_id) or return 0;
580 next if $lid->eg_copy_id;
582 my $org = $lid->owning_lib;
583 my $label = $lid->cn_label;
584 my $bibid = $li->eg_bib_id;
586 my $volume = $mgr->cache($org, "cn.$bibid.$label");
588 $volume = create_volume($mgr, $li, $lid) or return 0;
589 $mgr->cache($org, "cn.$bibid.$label", $volume);
591 create_copy($mgr, $volume, $lid) or return 0;
594 return { li => $li, new_bib => $new_bib };
600 my $record = OpenILS::Application::Cat::BibCommon->biblio_record_xml_import(
605 1, # override tcn collisions
607 undef # $rec->bib_source
610 if($U->event_code($record)) {
611 $mgr->editor->event($record);
612 $mgr->editor->rollback;
616 $li->eg_bib_id($record->id);
618 return update_lineitem($mgr, $li);
622 my($mgr, $li, $lid) = @_;
625 OpenILS::Application::Cat::AssetCommon->find_or_create_volume(
633 $mgr->editor->event($evt);
641 my($mgr, $volume, $lid) = @_;
642 my $copy = Fieldmapper::asset::copy->new;
644 $copy->loan_duration(2);
645 $copy->fine_level(2);
646 $copy->status(OILS_COPY_STATUS_ON_ORDER);
647 $copy->barcode($lid->barcode);
648 $copy->location($lid->location);
649 $copy->call_number($volume->id);
650 $copy->circ_lib($volume->owning_lib);
651 $copy->circ_modifier($lid->circ_modifier);
653 my $evt = OpenILS::Application::Cat::AssetCommon->create_copy($mgr->editor, $volume, $copy);
655 $mgr->editor->event($evt);
660 $lid->eg_copy_id($copy->id);
661 $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
669 # ----------------------------------------------------------------------------
670 # Workflow: Build a selection list from a Z39.50 search
671 # ----------------------------------------------------------------------------
673 __PACKAGE__->register_method(
675 api_name => 'open-ils.acq.picklist.search.z3950',
678 desc => 'Performs a z3950 federated search and creates a picklist and associated lineitems',
680 {desc => 'Authentication token', type => 'string'},
681 {desc => 'Search definition', type => 'object'},
682 {desc => 'Picklist name, optional', type => 'string'},
688 my($self, $conn, $auth, $search, $name, $options) = @_;
689 my $e = new_editor(authtoken=>$auth);
690 return $e->event unless $e->checkauth;
691 return $e->event unless $e->allowed('CREATE_PICKLIST');
693 $search->{limit} ||= 10;
696 my $ses = OpenSRF::AppSession->create('open-ils.search');
697 my $req = $ses->request('open-ils.search.z3950.search_class', $auth, $search);
702 while(my $resp = $req->recv(timeout=>60)) {
705 my $e = new_editor(requestor=>$e->requestor, xact=>1);
706 $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
707 $picklist = zsearch_build_pl($mgr, $name);
711 my $result = $resp->content;
712 my $count = $result->{count};
713 $mgr->total( (($count < $search->{limit}) ? $count : $search->{limit})+1 );
715 for my $rec (@{$result->{records}}) {
717 my $li = create_lineitem($mgr,
718 picklist => $picklist->id,
719 source_label => $result->{service},
720 marc => $rec->{marcxml},
721 eg_bib_id => $rec->{bibid}
724 if($$options{respond_li}) {
725 $li->attributes($mgr->editor->search_acq_lineitem_attr({lineitem => $li->id}))
726 if $$options{flesh_attrs};
727 $li->clear_marc if $$options{clear_marc};
728 $mgr->respond(lineitem => $li);
735 $mgr->editor->commit;
736 return $mgr->respond_complete;
739 sub zsearch_build_pl {
740 my($mgr, $name) = @_;
743 my $picklist = $mgr->editor->search_acq_picklist({
744 owner => $mgr->editor->requestor->id,
748 if($name eq '' and $picklist) {
749 return 0 unless delete_picklist($mgr, $picklist);
753 return update_picklist($mgr, $picklist) if $picklist;
754 return create_picklist($mgr, name => $name);
758 # ----------------------------------------------------------------------------
759 # Workflow: Build a selection list / PO by importing a batch of MARC records
760 # ----------------------------------------------------------------------------
762 __PACKAGE__->register_method(
763 method => 'upload_records',
764 api_name => 'open-ils.acq.process_upload_records',
769 my($self, $conn, $auth, $key) = @_;
771 my $e = new_editor(authtoken => $auth, xact => 1);
772 return $e->die_event unless $e->checkauth;
774 my $mgr = OpenILS::Application::Acq::BatchManager->new(
780 my $cache = OpenSRF::Utils::Cache->new;
782 my $data = $cache->get_cache("vandelay_import_spool_$key");
783 my $purpose = $data->{purpose};
784 my $filename = $data->{path};
785 my $provider = $data->{provider};
786 my $picklist = $data->{picklist};
787 my $create_po = $data->{create_po};
788 my $ordering_agency = $data->{ordering_agency};
789 my $create_assets = $data->{create_assets};
793 unless(-r $filename) {
794 $logger->error("unable to read MARC file $filename");
796 return OpenILS::Event->new('FILE_UPLOAD_ERROR', payload => {filename => $filename});
799 $provider = $e->retrieve_acq_provider($provider) or return $e->die_event;
802 $picklist = $e->retrieve_acq_picklist($picklist) or return $e->die_event;
803 if($picklist->owner != $e->requestor->id) {
804 return $e->die_event unless
805 $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
810 $po = create_purchase_order($mgr,
811 ordering_agency => $ordering_agency,
812 provider => $provider->id
813 ) or return $mgr->editor->die_event;
816 $logger->info("acq processing MARC file=$filename");
818 my $marctype = 'USMARC'; # ?
819 my $batch = new MARC::Batch ($marctype, $filename);
836 $logger->warn("Proccessing of record $count in set $key failed with error $err. Skipping this record");
843 ($xml = $r->as_xml_record()) =~ s/\n//sog;
844 $xml =~ s/^<\?xml.+\?\s*>//go;
845 $xml =~ s/>\s+</></go;
846 $xml =~ s/\p{Cc}//go;
847 $xml = $U->entityize($xml);
848 $xml =~ s/[\x00-\x1f]//go;
852 $logger->warn("Proccessing XML of record $count in set $key failed with error $err. Skipping this record");
855 next if $err or not $xml;
858 source_label => $provider->code,
859 provider => $provider->id,
863 $args{picklist} = $picklist->id if $picklist;
865 $args{purchase_order} = $po->id;
866 $args{state} = 'on-order';
869 my $li = create_lineitem($mgr, %args) or return $mgr->editor->die_event;
871 $li->provider($provider); # flesh it, we'll need it later
873 import_lineitem_details($mgr, $ordering_agency, $li) or return $mgr->editor->die_event;
876 push(@li_list, $li->id);
882 $cache->delete_cache('vandelay_import_spool_' . $key);
885 create_lineitem_list_assets($mgr, \@li_list) or return $e->die_event;
888 return $mgr->respond_complete;
891 sub import_lineitem_details {
892 my($mgr, $ordering_agency, $li) = @_;
894 my $holdings = $mgr->editor->json_query({from => ['acq.extract_provider_holding_data', $li->id]});
895 return 1 unless @$holdings;
896 my $org_path = $U->get_org_ancestors($ordering_agency);
897 $org_path = [ reverse (@$org_path) ];
902 # create a lineitem detail for each copy in the data
904 my $compiled = extract_lineitem_detail_data($mgr, $org_path, $holdings, $idx);
905 last unless defined $compiled;
906 return 0 unless $compiled;
908 # this takes the price of the last copy and uses it as the lineitem price
909 # need to determine if a given record would include different prices for the same item
910 $price = $$compiled{price};
912 for(1..$$compiled{quantity}) {
913 my $lid = create_lineitem_detail($mgr,
915 owning_lib => $$compiled{owning_lib},
916 cn_label => $$compiled{call_number},
917 fund => $$compiled{fund},
918 circ_modifier => $$compiled{circ_modifier},
919 note => $$compiled{note},
920 location => $$compiled{copy_location}
928 # set the price attr so we'll know the source of the price
931 attr_name => 'estimated_price',
932 attr_type => 'lineitem_local_attr_definition',
933 attr_value => $price,
937 # if we're creating a purchase order, create the debits
938 if($li->purchase_order) {
939 create_lineitem_debits($mgr, $li, $price, 2) or return 0;
946 # return hash on success, 0 on error, undef on no more holdings
947 sub extract_lineitem_detail_data {
948 my($mgr, $org_path, $holdings, $index) = @_;
950 my @data_list = grep { $_->{holding} eq $index } @$holdings;
951 return undef unless @data_list;
953 my %compiled = map { $_->{attr} => $_->{data} } @data_list;
954 my $base_org = $$org_path[0];
958 $logger->error("Item import extraction error: $msg");
959 $logger->error('Holdings Data: ' . OpenSRF::Utils::JSON->perl2JSON(\%compiled));
960 $mgr->editor->rollback;
961 $mgr->editor->event(OpenILS::Event->new('ACQ_IMPORT_ERROR', payload => $msg));
965 $compiled{quantity} ||= 1;
967 # ---------------------------------------------------------------------
969 my $code = $compiled{fund_code};
970 return $killme->('no fund code provided') unless $code;
972 my $fund = $mgr->cache($base_org, "fund.$code");
974 # search up the org tree for the most appropriate fund
975 for my $org (@$org_path) {
976 $fund = $mgr->editor->search_acq_fund(
977 {org => $org, code => $code, year => DateTime->now->year}, {idlist => 1})->[0];
981 return $killme->("no fund with code $code at orgs [@$org_path]") unless $fund;
982 $compiled{fund} = $fund;
983 $mgr->cache($base_org, "fund.$code", $fund);
986 # ---------------------------------------------------------------------
988 my $sn = $compiled{owning_lib};
989 return $killme->('no owning_lib defined') unless $sn;
991 $mgr->cache($base_org, "orgsn.$sn") ||
992 $mgr->editor->search_actor_org_unit({shortname => $sn}, {idlist => 1})->[0];
993 return $killme->("invalid owning_lib defined: $sn") unless $org_id;
994 $compiled{owning_lib} = $org_id;
995 $mgr->cache($$org_path[0], "orgsn.$sn", $org_id);
998 # ---------------------------------------------------------------------
1001 $code = $compiled{circ_modifier};
1005 $mod = $mgr->cache($base_org, "mod.$code") ||
1006 $mgr->editor->retrieve_config_circ_modifier($code);
1007 return $killme->("invlalid circ_modifier $code") unless $mod;
1008 $mgr->cache($base_org, "mod.$code", $mod);
1012 $mod = get_default_circ_modifier($mgr, $base_org)
1013 or return $killme->('no circ_modifier defined');
1016 $compiled{circ_modifier} = $mod;
1019 # ---------------------------------------------------------------------
1021 my $name = $compiled{copy_location};
1022 return $killme->('no copy_location defined') unless $name;
1023 my $loc = $mgr->cache($base_org, "copy_loc.$name");
1025 for my $org (@$org_path) {
1026 $loc = $mgr->editor->search_asset_copy_location(
1027 {owning_lib => $org, name => $name}, {idlist => 1})->[0];
1031 return $killme->("Invalid copy location $name") unless $loc;
1032 $compiled{copy_location} = $loc;
1033 $mgr->cache($base_org, "copy_loc.$name", $loc);
1040 # ----------------------------------------------------------------------------
1041 # Workflow: Given an existing purchase order, import/create the bibs,
1042 # callnumber and copy objects
1043 # ----------------------------------------------------------------------------
1045 __PACKAGE__->register_method(
1046 method => 'create_po_assets',
1047 api_name => 'open-ils.acq.purchase_order.assets.create',
1049 desc => q/Creates assets for each lineitem in the purchase order/,
1051 {desc => 'Authentication token', type => 'string'},
1052 {desc => 'The purchase order id', type => 'number'},
1054 return => {desc => 'Streams a total versus completed counts object, event on error'}
1058 sub create_po_assets {
1059 my($self, $conn, $auth, $po_id) = @_;
1060 my $e = new_editor(authtoken=>$auth, xact=>1);
1061 return $e->die_event unless $e->checkauth;
1063 my $mgr = OpenILS::Application::Acq::BatchManager->new(
1069 my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1070 return $e->die_event unless $e->allowed('IMPORT_PURCHASE_ORDER_ASSETS', $po->ordering_agency);
1072 my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
1074 # it's ugly, but it's fast. Get the total count of lineitem detail objects to process
1075 my $lid_total = $e->json_query({
1076 select => { acqlid => [{aggregate => 1, transform => 'count', column => 'id'}] },
1082 join => {acqpo => {fkey => 'purchase_order', field => 'id'}}
1086 where => {'+acqpo' => {id => $po_id}}
1089 $mgr->total(scalar(@$li_ids) + $lid_total);
1091 create_lineitem_list_assets($mgr, $li_ids) or return $e->die_event;
1094 update_purchase_order($mgr, $po) or return $e->die_event;
1097 return $mgr->respond_complete;
1102 __PACKAGE__->register_method(
1103 method => 'create_purchase_order_api',
1104 api_name => 'open-ils.acq.purchase_order.create',
1106 desc => 'Creates a new purchase order',
1108 {desc => 'Authentication token', type => 'string'},
1109 {desc => 'purchase_order to create', type => 'object'}
1111 return => {desc => 'The purchase order id, Event on failure'}
1115 sub create_purchase_order_api {
1116 my($self, $conn, $auth, $po, $args) = @_;
1119 my $e = new_editor(xact=>1, authtoken=>$auth);
1120 return $e->die_event unless $e->checkauth;
1121 return $e->die_event unless $e->allowed('CREATE_PURCHASE_ORDER', $po->ordering_agency);
1123 my $mgr = OpenILS::Application::Acq::BatchManager->new(
1130 my %pargs = (ordering_agency => $e->requestor->ws_ou);
1131 $pargs{provider} = $po->provider if $po->provider;
1132 $po = create_purchase_order($mgr, %pargs) or return $e->die_event;
1134 my $li_ids = $$args{lineitems};
1138 for my $li_id (@$li_ids) {
1140 my $li = $e->retrieve_acq_lineitem([
1142 {flesh => 1, flesh_fields => {jub => ['attributes']}}
1143 ]) or return $e->die_event;
1145 $li->provider($po->provider);
1146 $li->purchase_order($po->id);
1147 update_lineitem($mgr, $li) or return $e->die_event;
1150 create_lineitem_debits($mgr, $li) or return $e->die_event;
1154 # commit before starting the asset creation
1157 if($li_ids and $$args{create_assets}) {
1158 create_lineitem_list_assets($mgr, $li_ids) or return $e->die_event;
1161 return $mgr->respond_complete;
1165 __PACKAGE__->register_method(
1166 method => 'lineitem_detail_CUD_batch',
1167 api_name => 'open-ils.acq.lineitem_detail.cud.batch',
1170 desc => q/Creates a new purchase order line item detail.
1171 Additionally creates the associated fund_debit/,
1173 {desc => 'Authentication token', type => 'string'},
1174 {desc => 'List of lineitem_details to create', type => 'array'},
1176 return => {desc => 'Streaming response of current position in the array'}
1180 sub lineitem_detail_CUD_batch {
1181 my($self, $conn, $auth, $li_details) = @_;
1183 my $e = new_editor(xact=>1, authtoken=>$auth);
1184 return $e->die_event unless $e->checkauth;
1186 my $mgr = OpenILS::Application::Acq::BatchManager->new(
1194 $mgr->total(scalar(@$li_details));
1198 for my $lid (@$li_details) {
1200 my $li = $li_cache{$lid->lineitem} || $e->retrieve_acq_lineitem($lid->lineitem);
1203 create_lineitem_detail($mgr, %{$lid->to_bare_hash}) or return $e->die_event;
1205 } elsif($lid->ischanged) {
1206 $e->update_acq_lineitem_detail($lid) or return $e->die_event;
1208 } elsif($lid->isdeleted) {
1209 delete_lineitem_detail($mgr, $lid) or return $e->die_event;
1212 $mgr->respond(li => $li);
1213 $li_cache{$lid->lineitem} = $li;
1217 return $mgr->respond_complete;