]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm
respond after li update, may not be any lid's
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Acq / Order.pm
1 package OpenILS::Application::Acq::BatchManager;
2 use OpenSRF::AppSession;
3 use OpenSRF::EX qw/:try/;
4 use strict; use warnings;
5
6 sub new {
7     my($class, %args) = @_;
8     my $self = bless(\%args, $class);
9     $self->{args} = {
10         lid => 0,
11         li => 0,
12         copies => 0,
13         bibs => 0,
14         progress => 0,
15         debits_accrued => 0,
16         purchase_order => undef,
17         picklist => undef,
18         complete => 0,
19         indexed => 0,
20         total => 0
21     };
22     $self->{ingest_queue} = [];
23     $self->{cache} = {};
24     $self->throttle(5) unless $self->throttle;
25     return $self;
26 }
27
28 sub conn {
29     my($self, $val) = @_;
30     $self->{conn} = $val if $val;
31     return $self->{conn};
32 }
33 sub throttle {
34     my($self, $val) = @_;
35     $self->{throttle} = $val if $val;
36     return $self->{throttle};
37 }
38 sub respond {
39     my($self, %other_args) = @_;
40     if($self->throttle and not %other_args) {
41         return unless (
42             ($self->{args}->{progress} - $self->{last_respond_progress}) >= $self->throttle
43         );
44     }
45     $self->conn->respond({ %{$self->{args}}, %other_args });
46     $self->{last_respond_progress} = $self->{args}->{progress};
47 }
48 sub respond_complete {
49     my($self, %other_args) = @_;
50     $self->complete;
51     $self->conn->respond_complete({ %{$self->{args}}, %other_args });
52     return undef;
53 }
54 sub total {
55     my($self, $val) = @_;
56     $self->{args}->{total} = $val if defined $val;
57     $self->{args}->{maximum} = $self->{args}->{total};
58     return $self->{args}->{total};
59 }
60 sub purchase_order {
61     my($self, $val) = @_;
62     $self->{args}->{purchase_order} = $val if $val;
63     return $self;
64 }
65 sub picklist {
66     my($self, $val) = @_;
67     $self->{args}->{picklist} = $val if $val;
68     return $self;
69 }
70 sub add_lid {
71     my $self = shift;
72     $self->{args}->{lid} += 1;
73     $self->{args}->{progress} += 1;
74     return $self;
75 }
76 sub add_li {
77     my $self = shift;
78     $self->{args}->{li} += 1;
79     $self->{args}->{progress} += 1;
80     return $self;
81 }
82 sub add_copy {
83     my $self = shift;
84     $self->{args}->{copies} += 1;
85     $self->{args}->{progress} += 1;
86     return $self;
87 }
88 sub add_bib {
89     my $self = shift;
90     $self->{args}->{bibs} += 1;
91     $self->{args}->{progress} += 1;
92     return $self;
93 }
94 sub add_debit {
95     my($self, $amount) = @_;
96     $self->{args}->{debits_accrued} += $amount;
97     $self->{args}->{progress} += 1;
98     return $self;
99 }
100 sub editor {
101     my($self, $editor) = @_;
102     $self->{editor} = $editor if defined $editor;
103     return $self->{editor};
104 }
105 sub complete {
106     my $self = shift;
107     $self->{args}->{complete} = 1;
108     return $self;
109 }
110
111 sub ingest_ses {
112     my($self, $val) = @_;
113     $self->{ingest_ses} = $val if $val;
114     return $self->{ingest_ses};
115 }
116
117 sub push_ingest_queue {
118     my($self, $rec_id) = @_;
119
120     $self->ingest_ses(OpenSRF::AppSession->connect('open-ils.ingest'))
121         unless $self->ingest_ses;
122
123     my $req = $self->ingest_ses->request('open-ils.ingest.full.biblio.record', $rec_id);
124
125     push(@{$self->{ingest_queue}}, $req);
126 }
127
128 sub process_ingest_records {
129     my $self = shift;
130     return unless @{$self->{ingest_queue}};
131
132     for my $req (@{$self->{ingest_queue}}) {
133
134         try { 
135             $req->gather(1); 
136             $self->{args}->{indexed} += 1;
137             $self->{args}->{progress} += 1;
138         } otherwise {};
139
140         $self->respond;
141     }
142     $self->ingest_ses->disconnect;
143 }
144
145
146 sub cache {
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};
151 }
152
153
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 # ----------------------------------------------------------------------------
161 use OpenILS::Event;
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;
171 use MARC::Record;
172 use MARC::Batch;
173 use MARC::File::XML;
174 my $U = 'OpenILS::Application::AppUtils';
175
176
177 # ----------------------------------------------------------------------------
178 # Lineitem
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');
188     $li->state('new');
189     $li->$_($args{$_}) for keys %args;
190     $li->clear_id;
191     $mgr->add_li;
192     return $mgr->editor->create_acq_lineitem($li);
193 }
194
195 sub update_lineitem {
196     my($mgr, $li) = @_;
197     $li->edit_time('now');
198     $li->editor($mgr->editor->requestor->id);
199     $mgr->add_li;
200     return $li if $mgr->editor->update_acq_lineitem($li);
201     return undef;
202 }
203
204 sub delete_lineitem {
205     my($mgr, $li) = @_;
206     $li = $mgr->editor->retrieve_acq_lineitem($li) unless ref $li;
207
208     # delete the attached lineitem_details
209     my $lid_ids = $mgr->editor->search_acq_lineitem_detail({lineitem => $li->id}, {idlist=>1});
210     for my $lid_id (@$lid_ids) {
211         return 0 unless delete_lineitem_detail($mgr, $lid_id);
212     }
213
214     $mgr->add_li;
215     return $mgr->editor->delete_acq_lineitem($li);
216 }
217
218 # begins and commit transactions as it goes
219 sub create_lineitem_list_assets {
220     my($mgr, $li_ids) = @_;
221     # create the bibs/volumes/copies and ingest the records
222     for my $li_id (@$li_ids) {
223         $mgr->editor->xact_begin;
224         my $data = create_lineitem_assets($mgr, $li_id) or return undef;
225         $mgr->editor->xact_commit;
226         $mgr->push_ingest_queue($data->{li}->eg_bib_id) if $data->{new_bib};
227         $mgr->respond;
228     }
229     $mgr->process_ingest_records;
230     return 1;
231 }
232
233 # ----------------------------------------------------------------------------
234 # if all of the lineitem details for this lineitem have 
235 # been received, mark the lineitem as received
236 # returns 1 on non-received, li on received, 0 on error
237 # ----------------------------------------------------------------------------
238 sub check_lineitem_received {
239     my($mgr, $li_id) = @_;
240
241     my $non_recv = $mgr->editor->search_acq_lineitem_detail(
242         {recv_time => undef, lineitem => $li_id}, {idlist=>1});
243
244     return 1 if @$non_recv;
245
246     my $li = $mgr->editor->retrieve_acq_lineitem($li_id);
247     $li->state('received');
248     return update_lineitem($mgr, $li);
249 }
250
251 sub receive_lineitem {
252     my($mgr, $li_id, $skip_complete_check) = @_;
253     my $li = $mgr->editor->retrieve_acq_lineitem($li_id) or return 0;
254
255     my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
256         {lineitem => $li_id, recv_time => undef}, {idlist => 1});
257
258     for my $lid_id (@$lid_ids) {
259        receive_lineitem_detail($mgr, $lid_id, 1) or return 0; 
260     }
261
262     $mgr->add_li;
263     $li->state('received');
264     update_lineitem($mgr, $li) or return 0;
265     return 1 if $skip_complete_check;
266
267     return check_purchase_order_received($mgr, $li->purchase_order);
268 }
269
270 sub rollback_receive_lineitem {
271     my($mgr, $li_id) = @_;
272     my $li = $mgr->editor->retrieve_acq_lineitem($li_id) or return 0;
273
274     my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
275         {lineitem => $li_id, recv_time => {'!=' => undef}}, {idlist => 1});
276
277     for my $lid_id (@$lid_ids) {
278        rollback_receive_lineitem_detail($mgr, $lid_id, 1) or return 0; 
279     }
280
281     $mgr->add_li;
282     $li->state('on-order');
283     return update_lineitem($mgr, $li);
284 }
285
286 # ----------------------------------------------------------------------------
287 # Lineitem Detail
288 # ----------------------------------------------------------------------------
289 sub create_lineitem_detail {
290     my($mgr, %args) = @_;
291     my $lid = Fieldmapper::acq::lineitem_detail->new;
292     $lid->$_($args{$_}) for keys %args;
293     $lid->clear_id;
294     $mgr->editor->create_acq_lineitem_detail($lid) or return 0;
295     $mgr->add_lid;
296
297     # create some default values
298     unless($lid->barcode) {
299         my $pfx = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.tmp_barcode_prefix') || 'ACQ';
300         $lid->barcode($pfx.$lid->id);
301     }
302
303     unless($lid->cn_label) {
304         my $pfx = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.tmp_callnumber_prefix') || 'ACQ';
305         $lid->cn_label($pfx.$lid->id);
306     }
307
308     if(!$lid->location and my $loc = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.default_copy_location')) {
309         $lid->location($loc);
310     }
311
312     if(!$lid->circ_modifier and my $mod = get_default_circ_modifier($mgr, $lid->owning_lib)) {
313         $lid->circ_modifier($mod);
314     }
315
316     $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
317     return $lid;
318 }
319
320 sub get_default_circ_modifier {
321     my($mgr, $org) = @_;
322     my $mod = $mgr->cache($org, 'def_circ_mod');
323     return $mod if $mod;
324     $mod = $U->ou_ancestor_setting_value($org, 'acq.default_circ_modifier');
325     return $mgr->cache($org, 'def_circ_mod', $mod) if $mod;
326     return undef;
327 }
328
329 sub delete_lineitem_detail {
330     my($mgr, $lid) = @_;
331     $lid = $mgr->editor->retrieve_acq_lineitem_detail($lid) unless ref $lid;
332     return $mgr->editor->delete_acq_lineitem_detail($lid);
333 }
334
335
336 sub receive_lineitem_detail {
337     my($mgr, $lid_id, $skip_complete_check) = @_;
338     my $e = $mgr->editor;
339
340     my $lid = $e->retrieve_acq_lineitem_detail([
341         $lid_id,
342         {   flesh => 1,
343             flesh_fields => {
344                 acqlid => ['fund_debit']
345             }
346         }
347     ]) or return 0;
348
349     return 1 if $lid->recv_time;
350
351     $lid->recv_time('now');
352     $e->update_acq_lineitem_detail($lid) or return 0;
353
354     my $copy = $e->retrieve_asset_copy($lid->eg_copy_id) or return 0;
355     $copy->status(OILS_COPY_STATUS_IN_PROCESS);
356     $copy->edit_date('now');
357     $copy->editor($e->requestor->id);
358     $e->update_asset_copy($copy) or return 0;
359
360     if($lid->fund_debit) {
361         $lid->fund_debit->encumbrance('f');
362         $e->update_acq_fund_debit($lid->fund_debit) or return 0;
363     }
364
365     $mgr->add_lid;
366
367     return 1 if $skip_complete_check;
368
369     my $li = check_lineitem_received($mgr, $lid->lineitem) or return 0;
370     return 1 if $li == 1; # li not received
371
372     return check_purchase_order_received($mgr, $li->purchase_order);
373 }
374
375
376 sub rollback_receive_lineitem_detail {
377     my($mgr, $lid_id) = @_;
378     my $e = $mgr->editor;
379
380     my $lid = $e->retrieve_acq_lineitem_detail([
381         $lid_id,
382         {   flesh => 1,
383             flesh_fields => {
384                 acqlid => ['fund_debit']
385             }
386         }
387     ]) or return 0;
388
389     return 1 unless $lid->recv_time;
390
391     $lid->clear_recv_time;
392     $e->update_acq_lineitem_detail($lid) or return 0;
393
394     my $copy = $e->retrieve_asset_copy($lid->eg_copy_id) or return 0;
395     $copy->status(OILS_COPY_STATUS_ON_ORDER);
396     $copy->edit_date('now');
397     $copy->editor($e->requestor->id);
398     $e->update_asset_copy($copy) or return 0;
399
400     if($lid->fund_debit) {
401         $lid->fund_debit->encumbrance('t');
402         $e->update_acq_fund_debit($lid->fund_debit) or return 0;
403     }
404
405     $mgr->add_lid;
406     return $lid;
407 }
408
409 # ----------------------------------------------------------------------------
410 # Lineitem Attr
411 # ----------------------------------------------------------------------------
412 sub set_lineitem_attr {
413     my($mgr, %args) = @_;
414     my $attr_type = $args{attr_type};
415
416     # first, see if it's already set.  May just need to overwrite it
417     my $attr = $mgr->editor->search_acq_lineitem_attr({
418         lineitem => $args{lineitem},
419         attr_type => $args{attr_type},
420         attr_name => $args{attr_name}
421     })->[0];
422
423     if($attr) {
424         $attr->attr_value($args{attr_value});
425         return $attr if $mgr->editor->update_acq_lineitem_attr($attr);
426         return undef;
427
428     } else {
429
430         $attr = Fieldmapper::acq::lineitem_attr->new;
431         $attr->$_($args{$_}) for keys %args;
432         
433         unless($attr->definition) {
434             my $find = "search_acq_$attr_type";
435             my $attr_def_id = $mgr->editor->$find({code => $attr->attr_name}, {idlist=>1})->[0] or return 0;
436             $attr->definition($attr_def_id);
437         }
438         return $mgr->editor->create_acq_lineitem_attr($attr);
439     }
440 }
441
442 sub get_li_price {
443     my $li = shift;
444     my $attrs = $li->attributes;
445     my ($marc_estimated, $local_estimated, $local_actual, $prov_estimated, $prov_actual);
446
447     for my $attr (@$attrs) {
448         if($attr->attr_name eq 'estimated_price') {
449             $local_estimated = $attr->attr_value 
450                 if $attr->attr_type eq 'lineitem_local_attr_definition';
451             $prov_estimated = $attr->attr_value 
452                 if $attr->attr_type eq 'lineitem_prov_attr_definition';
453             $marc_estimated = $attr->attr_value
454                 if $attr->attr_type eq 'lineitem_marc_attr_definition';
455
456         } elsif($attr->attr_name eq 'actual_price') {
457             $local_actual = $attr->attr_value     
458                 if $attr->attr_type eq 'lineitem_local_attr_definition';
459             $prov_actual = $attr->attr_value 
460                 if $attr->attr_type eq 'lineitem_prov_attr_definition';
461         }
462     }
463
464     return ($local_actual, 1) if $local_actual;
465     return ($prov_actual, 2) if $prov_actual;
466     return ($local_estimated, 1) if $local_estimated;
467     return ($prov_estimated, 2) if $prov_estimated;
468     return ($marc_estimated, 3);
469 }
470
471
472 # ----------------------------------------------------------------------------
473 # Lineitem Debits
474 # ----------------------------------------------------------------------------
475 sub create_lineitem_debits {
476     my($mgr, $li, $price, $ptype) = @_; 
477
478     ($price, $ptype) = get_li_price($li) unless $price;
479
480     unless($price) {
481         $mgr->editor->event(OpenILS::Event->new('ACQ_LINEITEM_NO_PRICE', payload => $li->id));
482         $mgr->editor->rollback;
483         return 0;
484     }
485
486     unless($li->provider) {
487         $mgr->editor->event(OpenILS::Event->new('ACQ_LINEITEM_NO_PROVIDER', payload => $li->id));
488         $mgr->editor->rollback;
489         return 0;
490     }
491
492     my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
493         {lineitem => $li->id}, 
494         {idlist=>1}
495     );
496
497     for my $lid_id (@$lid_ids) {
498
499         my $lid = $mgr->editor->retrieve_acq_lineitem_detail([
500             $lid_id,
501             {   flesh => 1, 
502                 flesh_fields => {acqlid => ['fund']}
503             }
504         ]);
505
506         create_lineitem_detail_debit($mgr, $li, $lid, $price, $ptype) or return 0;
507     }
508
509     return 1;
510 }
511
512
513 # flesh li->provider
514 # flesh lid->fund
515 # ptype 1=local, 2=provider, 3=marc
516 sub create_lineitem_detail_debit {
517     my($mgr, $li, $lid, $price, $ptype) = @_;
518
519     unless(ref $li and ref $li->provider) {
520        $li = $mgr->editor->retrieve_acq_lineitem([
521             $li,
522             {   flesh => 1,
523                 flesh_fields => {jub => ['provider']},
524             }
525         ]);
526     }
527
528     unless(ref $lid and ref $lid->fund) {
529         $lid = $mgr->editor->retrieve_acq_lineitem_detail([
530             $lid,
531             {   flesh => 1, 
532                 flesh_fields => {acqlid => ['fund']}
533             }
534         ]);
535     }
536
537     my $ctype = $lid->fund->currency_type;
538     my $amount = $price;
539
540     if($ptype == 2) { # price from vendor
541         $ctype = $li->provider->currency_type;
542         $amount = currency_conversion($mgr, $ctype, $lid->fund->currency_type, $price);
543     }
544
545     my $debit = create_fund_debit(
546         $mgr, 
547         fund => $lid->fund->id,
548         origin_amount => $price,
549         origin_currency_type => $ctype,
550         amount => $amount
551     ) or return 0;
552
553     $lid->fund_debit($debit->id);
554     $lid->fund($lid->fund->id);
555     $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
556     return $debit;
557 }
558
559
560 # ----------------------------------------------------------------------------
561 # Fund Debit
562 # ----------------------------------------------------------------------------
563 sub create_fund_debit {
564     my($mgr, %args) = @_;
565     my $debit = Fieldmapper::acq::fund_debit->new;
566     $debit->debit_type('purchase');
567     $debit->encumbrance('t');
568     $debit->$_($args{$_}) for keys %args;
569     $debit->clear_id;
570     $mgr->add_debit($debit->amount);
571     return $mgr->editor->create_acq_fund_debit($debit);
572 }
573
574 sub currency_conversion {
575     my($mgr, $src_currency, $dest_currency, $amount) = @_;
576     my $result = $mgr->editor->json_query(
577         {from => ['acq.exchange_ratio', $src_currency, $dest_currency, $amount]});
578     return $result->[0]->{'acq.exchange_ratio'};
579 }
580
581
582 # ----------------------------------------------------------------------------
583 # Picklist
584 # ----------------------------------------------------------------------------
585 sub create_picklist {
586     my($mgr, %args) = @_;
587     my $picklist = Fieldmapper::acq::picklist->new;
588     $picklist->creator($mgr->editor->requestor->id);
589     $picklist->owner($picklist->creator);
590     $picklist->editor($picklist->creator);
591     $picklist->create_time('now');
592     $picklist->edit_time('now');
593     $picklist->org_unit($mgr->editor->requestor->ws_ou);
594     $picklist->owner($mgr->editor->requestor->id);
595     $picklist->$_($args{$_}) for keys %args;
596     $picklist->clear_id;
597     $mgr->picklist($picklist);
598     return $mgr->editor->create_acq_picklist($picklist);
599 }
600
601 sub update_picklist {
602     my($mgr, $picklist) = @_;
603     $picklist = $mgr->editor->retrieve_acq_picklist($picklist) unless ref $picklist;
604     $picklist->edit_time('now');
605     $picklist->editor($mgr->editor->requestor->id);
606     $mgr->picklist($picklist);
607     return $picklist if $mgr->editor->update_acq_picklist($picklist);
608     return undef;
609 }
610
611 sub delete_picklist {
612     my($mgr, $picklist) = @_;
613     $picklist = $mgr->editor->retrieve_acq_picklist($picklist) unless ref $picklist;
614
615     # delete all 'new' lineitems
616     my $lis = $mgr->editor->search_acq_lineitem({picklist => $picklist->id, state => 'new'});
617     for my $li (@$lis) {
618         return 0 unless delete_lineitem($mgr, $li);
619     }
620
621     # detach all non-'new' lineitems
622     $lis = $mgr->editor->search_acq_lineitem({picklist => $picklist->id, state => {'!=' => 'new'}});
623     for my $li (@$lis) {
624         $li->clear_picklist;
625         return 0 unless update_lineitem($mgr, $li);
626     }
627
628     # remove any picklist-specific object perms
629     my $ops = $mgr->editor->search_permission_usr_object_perm_map({object_type => 'acqpl', object_id => ''.$picklist->id});
630     for my $op (@$ops) {
631         return 0 unless $mgr->editor->delete_usr_object_perm_map($op);
632     }
633
634     return $mgr->editor->delete_acq_picklist($picklist);
635 }
636
637 # ----------------------------------------------------------------------------
638 # Purchase Order
639 # ----------------------------------------------------------------------------
640 sub update_purchase_order {
641     my($mgr, $po) = @_;
642     $po = $mgr->editor->retrieve_acq_purchase_order($po) unless ref $po;
643     $po->editor($mgr->editor->requestor->id);
644     $po->edit_time('now');
645     $mgr->purchase_order($po);
646     return $po if $mgr->editor->update_acq_purchase_order($po);
647     return undef;
648 }
649
650 sub create_purchase_order {
651     my($mgr, %args) = @_;
652     my $po = Fieldmapper::acq::purchase_order->new;
653     $po->creator($mgr->editor->requestor->id);
654     $po->editor($mgr->editor->requestor->id);
655     $po->owner($mgr->editor->requestor->id);
656     $po->edit_time('now');
657     $po->create_time('now');
658     $po->ordering_agency($mgr->editor->requestor->ws_ou);
659     $po->$_($args{$_}) for keys %args;
660     $po->clear_id;
661     $mgr->purchase_order($po);
662     return $mgr->editor->create_acq_purchase_order($po);
663 }
664
665 # ----------------------------------------------------------------------------
666 # if all of the lineitems for this PO are received,
667 # mark the PO as received
668 # ----------------------------------------------------------------------------
669 sub check_purchase_order_received {
670     my($mgr, $po_id) = @_;
671
672     my $non_recv_li = $mgr->editor->search_acq_lineitem(
673         {   purchase_order => $po_id,
674             state => {'!=' => 'received'}
675         }, {idlist=>1});
676
677     return 1 if @$non_recv_li;
678
679     my $po = $mgr->editor->retrieve_acq_purchase_order($po_id);
680     $po->state('received');
681     return update_purchase_order($mgr, $po);
682 }
683
684
685 # ----------------------------------------------------------------------------
686 # Bib, Callnumber, and Copy data
687 # ----------------------------------------------------------------------------
688
689 sub create_lineitem_assets {
690     my($mgr, $li_id) = @_;
691     my $evt;
692
693     my $li = $mgr->editor->retrieve_acq_lineitem([
694         $li_id,
695         {   flesh => 1,
696             flesh_fields => {jub => ['purchase_order', 'attributes']}
697         }
698     ]) or return 0;
699
700     # -----------------------------------------------------------------
701     # first, create the bib record if necessary
702     # -----------------------------------------------------------------
703     my $new_bib = 0;
704     unless($li->eg_bib_id) {
705         create_bib($mgr, $li) or return 0;
706         $new_bib = 1;
707     }
708
709     my $li_details = $mgr->editor->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
710
711     # -----------------------------------------------------------------
712     # for each lineitem_detail, create the volume if necessary, create 
713     # a copy, and link them all together.
714     # -----------------------------------------------------------------
715     for my $lid_id (@{$li_details}) {
716
717         my $lid = $mgr->editor->retrieve_acq_lineitem_detail($lid_id) or return 0;
718         next if $lid->eg_copy_id;
719
720         my $org = $lid->owning_lib;
721         my $label = $lid->cn_label;
722         my $bibid = $li->eg_bib_id;
723
724         my $volume = $mgr->cache($org, "cn.$bibid.$label");
725         unless($volume) {
726             $volume = create_volume($mgr, $li, $lid) or return 0;
727             $mgr->cache($org, "cn.$bibid.$label", $volume);
728         }
729         create_copy($mgr, $volume, $lid) or return 0;
730     }
731
732     return { li => $li, new_bib => $new_bib };
733 }
734
735 sub create_bib {
736     my($mgr, $li) = @_;
737
738     my $record = OpenILS::Application::Cat::BibCommon->biblio_record_xml_import(
739         $mgr->editor, 
740         $li->marc, 
741         undef, 
742         undef, 
743         1, # override tcn collisions
744         1, # no-ingest
745         undef # $rec->bib_source
746     ); 
747
748     if($U->event_code($record)) {
749         $mgr->editor->event($record);
750         $mgr->editor->rollback;
751         return 0;
752     }
753
754     $li->eg_bib_id($record->id);
755     $mgr->add_bib;
756     return update_lineitem($mgr, $li);
757 }
758
759 sub create_volume {
760     my($mgr, $li, $lid) = @_;
761
762     my ($volume, $evt) = 
763         OpenILS::Application::Cat::AssetCommon->find_or_create_volume(
764             $mgr->editor, 
765             $lid->cn_label, 
766             $li->eg_bib_id, 
767             $lid->owning_lib
768         );
769
770     if($evt) {
771         $mgr->editor->event($evt);
772         return 0;
773     }
774
775     return $volume;
776 }
777
778 sub create_copy {
779     my($mgr, $volume, $lid) = @_;
780     my $copy = Fieldmapper::asset::copy->new;
781     $copy->isnew(1);
782     $copy->loan_duration(2);
783     $copy->fine_level(2);
784     $copy->status(OILS_COPY_STATUS_ON_ORDER);
785     $copy->barcode($lid->barcode);
786     $copy->location($lid->location);
787     $copy->call_number($volume->id);
788     $copy->circ_lib($volume->owning_lib);
789     $copy->circ_modifier($lid->circ_modifier);
790
791     my $evt = OpenILS::Application::Cat::AssetCommon->create_copy($mgr->editor, $volume, $copy);
792     if($evt) {
793         $mgr->editor->event($evt);
794         return 0;
795     }
796
797     $mgr->add_copy;
798     $lid->eg_copy_id($copy->id);
799     $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
800 }
801
802
803
804
805
806
807 # ----------------------------------------------------------------------------
808 # Workflow: Build a selection list from a Z39.50 search
809 # ----------------------------------------------------------------------------
810
811 __PACKAGE__->register_method(
812         method => 'zsearch',
813         api_name => 'open-ils.acq.picklist.search.z3950',
814     stream => 1,
815         signature => {
816         desc => 'Performs a z3950 federated search and creates a picklist and associated lineitems',
817         params => [
818             {desc => 'Authentication token', type => 'string'},
819             {desc => 'Search definition', type => 'object'},
820             {desc => 'Picklist name, optional', type => 'string'},
821         ]
822     }
823 );
824
825 sub zsearch {
826     my($self, $conn, $auth, $search, $name, $options) = @_;
827     my $e = new_editor(authtoken=>$auth);
828     return $e->event unless $e->checkauth;
829     return $e->event unless $e->allowed('CREATE_PICKLIST');
830
831     $search->{limit} ||= 10;
832     $options ||= {};
833
834     my $ses = OpenSRF::AppSession->create('open-ils.search');
835     my $req = $ses->request('open-ils.search.z3950.search_class', $auth, $search);
836
837     my $first = 1;
838     my $picklist;
839     my $mgr;
840     while(my $resp = $req->recv(timeout=>60)) {
841
842         if($first) {
843             my $e = new_editor(requestor=>$e->requestor, xact=>1);
844             $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
845             $picklist = zsearch_build_pl($mgr, $name);
846             $first = 0;
847         }
848
849         my $result = $resp->content;
850         my $count = $result->{count};
851         $mgr->total( (($count < $search->{limit}) ? $count : $search->{limit})+1 );
852
853         for my $rec (@{$result->{records}}) {
854
855             my $li = create_lineitem($mgr, 
856                 picklist => $picklist->id,
857                 source_label => $result->{service},
858                 marc => $rec->{marcxml},
859                 eg_bib_id => $rec->{bibid}
860             );
861
862             if($$options{respond_li}) {
863                 $li->attributes($mgr->editor->search_acq_lineitem_attr({lineitem => $li->id}))
864                     if $$options{flesh_attrs};
865                 $li->clear_marc if $$options{clear_marc};
866                 $mgr->respond(lineitem => $li);
867             } else {
868                 $mgr->respond;
869             }
870         }
871     }
872
873     $mgr->editor->commit;
874     return $mgr->respond_complete;
875 }
876
877 sub zsearch_build_pl {
878     my($mgr, $name) = @_;
879     $name ||= '';
880
881     my $picklist = $mgr->editor->search_acq_picklist({
882         owner => $mgr->editor->requestor->id, 
883         name => $name
884     })->[0];
885
886     if($name eq '' and $picklist) {
887         return 0 unless delete_picklist($mgr, $picklist);
888         $picklist = undef;
889     }
890
891     return update_picklist($mgr, $picklist) if $picklist;
892     return create_picklist($mgr, name => $name);
893 }
894
895
896 # ----------------------------------------------------------------------------
897 # Workflow: Build a selection list / PO by importing a batch of MARC records
898 # ----------------------------------------------------------------------------
899
900 __PACKAGE__->register_method(
901     method => 'upload_records',
902     api_name => 'open-ils.acq.process_upload_records',
903     stream => 1,
904 );
905
906 sub upload_records {
907     my($self, $conn, $auth, $key) = @_;
908
909         my $e = new_editor(authtoken => $auth, xact => 1);
910     return $e->die_event unless $e->checkauth;
911     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
912
913     my $cache = OpenSRF::Utils::Cache->new;
914
915     my $data = $cache->get_cache("vandelay_import_spool_$key");
916         my $purpose = $data->{purpose};
917     my $filename = $data->{path};
918     my $provider = $data->{provider};
919     my $picklist = $data->{picklist};
920     my $create_po = $data->{create_po};
921     my $ordering_agency = $data->{ordering_agency};
922     my $create_assets = $data->{create_assets};
923     my $po;
924     my $evt;
925
926     unless(-r $filename) {
927         $logger->error("unable to read MARC file $filename");
928         $e->rollback;
929         return OpenILS::Event->new('FILE_UPLOAD_ERROR', payload => {filename => $filename});
930     }
931
932     $provider = $e->retrieve_acq_provider($provider) or return $e->die_event;
933
934     if($picklist) {
935         $picklist = $e->retrieve_acq_picklist($picklist) or return $e->die_event;
936         if($picklist->owner != $e->requestor->id) {
937             return $e->die_event unless 
938                 $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
939         }
940     }
941
942     if($create_po) {
943         $po = create_purchase_order($mgr, 
944             ordering_agency => $ordering_agency,
945             provider => $provider->id
946         ) or return $mgr->editor->die_event;
947     }
948
949     $logger->info("acq processing MARC file=$filename");
950
951     my $marctype = 'USMARC'; # ?
952         my $batch = new MARC::Batch ($marctype, $filename);
953         $batch->strict_off;
954
955         my $count = 0;
956     my @li_list;
957
958         while(1) {
959
960             my $err;
961         my $xml;
962                 $count++;
963         my $r;
964
965                 try {
966             $r = $batch->next;
967         } catch Error with {
968             $err = shift;
969                         $logger->warn("Proccessing of record $count in set $key failed with error $err.  Skipping this record");
970         };
971
972         next if $err;
973         last unless $r;
974
975                 try {
976             ($xml = $r->as_xml_record()) =~ s/\n//sog;
977             $xml =~ s/^<\?xml.+\?\s*>//go;
978             $xml =~ s/>\s+</></go;
979             $xml =~ s/\p{Cc}//go;
980             $xml = $U->entityize($xml);
981             $xml =~ s/[\x00-\x1f]//go;
982
983                 } catch Error with {
984                         $err = shift;
985                         $logger->warn("Proccessing XML of record $count in set $key failed with error $err.  Skipping this record");
986                 };
987
988         next if $err or not $xml;
989
990         my %args = (
991             source_label => $provider->code,
992             provider => $provider->id,
993             marc => $xml,
994         );
995
996         $args{picklist} = $picklist->id if $picklist;
997         if($po) {
998             $args{purchase_order} = $po->id;
999             $args{state} = 'on-order';
1000         }
1001
1002         my $li = create_lineitem($mgr, %args) or return $mgr->editor->die_event;
1003         $mgr->respond;
1004         $li->provider($provider); # flesh it, we'll need it later
1005
1006         import_lineitem_details($mgr, $ordering_agency, $li) or return $mgr->editor->die_event;
1007         $mgr->respond;
1008
1009         push(@li_list, $li->id);
1010         $mgr->respond;
1011         }
1012
1013         $e->commit;
1014     unlink($filename);
1015     $cache->delete_cache('vandelay_import_spool_' . $key);
1016
1017     if($create_assets) {
1018         create_lineitem_list_assets($mgr, \@li_list) or return $e->die_event;
1019     }
1020
1021     return $mgr->respond_complete;
1022 }
1023
1024 sub import_lineitem_details {
1025     my($mgr, $ordering_agency, $li) = @_;
1026
1027     my $holdings = $mgr->editor->json_query({from => ['acq.extract_provider_holding_data', $li->id]});
1028     return 1 unless @$holdings;
1029     my $org_path = $U->get_org_ancestors($ordering_agency);
1030     $org_path = [ reverse (@$org_path) ];
1031     my $price;
1032
1033     my $idx = 1;
1034     while(1) {
1035         # create a lineitem detail for each copy in the data
1036
1037         my $compiled = extract_lineitem_detail_data($mgr, $org_path, $holdings, $idx);
1038         last unless defined $compiled;
1039         return 0 unless $compiled;
1040
1041         # this takes the price of the last copy and uses it as the lineitem price
1042         # need to determine if a given record would include different prices for the same item
1043         $price = $$compiled{price};
1044
1045         for(1..$$compiled{quantity}) {
1046             my $lid = create_lineitem_detail($mgr, 
1047                 lineitem => $li->id,
1048                 owning_lib => $$compiled{owning_lib},
1049                 cn_label => $$compiled{call_number},
1050                 fund => $$compiled{fund},
1051                 circ_modifier => $$compiled{circ_modifier},
1052                 note => $$compiled{note},
1053                 location => $$compiled{copy_location}
1054             ) or return 0;
1055         }
1056
1057         $mgr->respond;
1058         $idx++;
1059     }
1060
1061     # set the price attr so we'll know the source of the price
1062     set_lineitem_attr(
1063         $mgr, 
1064         attr_name => 'estimated_price',
1065         attr_type => 'lineitem_local_attr_definition',
1066         attr_value => $price,
1067         lineitem => $li->id
1068     ) or return 0;
1069
1070     # if we're creating a purchase order, create the debits
1071     if($li->purchase_order) {
1072         create_lineitem_debits($mgr, $li, $price, 2) or return 0;
1073         $mgr->respond;
1074     }
1075
1076     return 1;
1077 }
1078
1079 # return hash on success, 0 on error, undef on no more holdings
1080 sub extract_lineitem_detail_data {
1081     my($mgr, $org_path, $holdings, $index) = @_;
1082
1083     my @data_list = grep { $_->{holding} eq $index } @$holdings;
1084     return undef unless @data_list;
1085
1086     my %compiled = map { $_->{attr} => $_->{data} } @data_list;
1087     my $base_org = $$org_path[0];
1088
1089     my $killme = sub {
1090         my $msg = shift;
1091         $logger->error("Item import extraction error: $msg");
1092         $logger->error('Holdings Data: ' . OpenSRF::Utils::JSON->perl2JSON(\%compiled));
1093         $mgr->editor->rollback;
1094         $mgr->editor->event(OpenILS::Event->new('ACQ_IMPORT_ERROR', payload => $msg));
1095         return 0;
1096     };
1097
1098     $compiled{quantity} ||= 1;
1099
1100     # ---------------------------------------------------------------------
1101     # Fund
1102     my $code = $compiled{fund_code};
1103     return $killme->('no fund code provided') unless $code;
1104
1105     my $fund = $mgr->cache($base_org, "fund.$code");
1106     unless($fund) {
1107         # search up the org tree for the most appropriate fund
1108         for my $org (@$org_path) {
1109             $fund = $mgr->editor->search_acq_fund(
1110                 {org => $org, code => $code, year => DateTime->now->year}, {idlist => 1})->[0];
1111             last if $fund;
1112         }
1113     }
1114     return $killme->("no fund with code $code at orgs [@$org_path]") unless $fund;
1115     $compiled{fund} = $fund;
1116     $mgr->cache($base_org, "fund.$code", $fund);
1117
1118
1119     # ---------------------------------------------------------------------
1120     # Owning lib
1121     my $sn = $compiled{owning_lib};
1122     return $killme->('no owning_lib defined') unless $sn;
1123     my $org_id = 
1124         $mgr->cache($base_org, "orgsn.$sn") ||
1125             $mgr->editor->search_actor_org_unit({shortname => $sn}, {idlist => 1})->[0];
1126     return $killme->("invalid owning_lib defined: $sn") unless $org_id;
1127     $compiled{owning_lib} = $org_id;
1128     $mgr->cache($$org_path[0], "orgsn.$sn", $org_id);
1129
1130
1131     # ---------------------------------------------------------------------
1132     # Circ Modifier
1133     my $mod;
1134     $code = $compiled{circ_modifier};
1135
1136     if($code) {
1137
1138         $mod = $mgr->cache($base_org, "mod.$code") ||
1139             $mgr->editor->retrieve_config_circ_modifier($code);
1140         return $killme->("invlalid circ_modifier $code") unless $mod;
1141         $mgr->cache($base_org, "mod.$code", $mod);
1142
1143     } else {
1144         # try the default
1145         $mod = get_default_circ_modifier($mgr, $base_org)
1146             or return $killme->('no circ_modifier defined');
1147     }
1148
1149     $compiled{circ_modifier} = $mod;
1150
1151
1152     # ---------------------------------------------------------------------
1153     # Shelving Location
1154     my $name = $compiled{copy_location};
1155     return $killme->('no copy_location defined') unless $name;
1156     my $loc = $mgr->cache($base_org, "copy_loc.$name");
1157     unless($loc) {
1158         for my $org (@$org_path) {
1159             $loc = $mgr->editor->search_asset_copy_location(
1160                 {owning_lib => $org, name => $name}, {idlist => 1})->[0];
1161             last if $loc;
1162         }
1163     }
1164     return $killme->("Invalid copy location $name") unless $loc;
1165     $compiled{copy_location} = $loc;
1166     $mgr->cache($base_org, "copy_loc.$name", $loc);
1167
1168     return \%compiled;
1169 }
1170
1171
1172
1173 # ----------------------------------------------------------------------------
1174 # Workflow: Given an existing purchase order, import/create the bibs, 
1175 # callnumber and copy objects
1176 # ----------------------------------------------------------------------------
1177
1178 __PACKAGE__->register_method(
1179         method => 'create_po_assets',
1180         api_name        => 'open-ils.acq.purchase_order.assets.create',
1181         signature => {
1182         desc => q/Creates assets for each lineitem in the purchase order/,
1183         params => [
1184             {desc => 'Authentication token', type => 'string'},
1185             {desc => 'The purchase order id', type => 'number'},
1186         ],
1187         return => {desc => 'Streams a total versus completed counts object, event on error'}
1188     }
1189 );
1190
1191 sub create_po_assets {
1192     my($self, $conn, $auth, $po_id) = @_;
1193
1194     my $e = new_editor(authtoken=>$auth, xact=>1);
1195     return $e->die_event unless $e->checkauth;
1196     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1197
1198     my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1199     return $e->die_event unless $e->allowed('IMPORT_PURCHASE_ORDER_ASSETS', $po->ordering_agency);
1200
1201     my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
1202
1203     # it's ugly, but it's fast.  Get the total count of lineitem detail objects to process
1204     my $lid_total = $e->json_query({
1205         select => { acqlid => [{aggregate => 1, transform => 'count', column => 'id'}] }, 
1206         from => {
1207             acqlid => {
1208                 jub => {
1209                     fkey => 'lineitem', 
1210                     field => 'id', 
1211                     join => {acqpo => {fkey => 'purchase_order', field => 'id'}}
1212                 }
1213             }
1214         }, 
1215         where => {'+acqpo' => {id => $po_id}}
1216     })->[0]->{id};
1217
1218     $mgr->total(scalar(@$li_ids) + $lid_total);
1219
1220     create_lineitem_list_assets($mgr, $li_ids) or return $e->die_event;
1221
1222     $e->xact_begin;
1223     update_purchase_order($mgr, $po) or return $e->die_event;
1224     $e->commit;
1225
1226     return $mgr->respond_complete;
1227 }
1228
1229
1230
1231 __PACKAGE__->register_method(
1232         method => 'create_purchase_order_api',
1233         api_name        => 'open-ils.acq.purchase_order.create',
1234         signature => {
1235         desc => 'Creates a new purchase order',
1236         params => [
1237             {desc => 'Authentication token', type => 'string'},
1238             {desc => 'purchase_order to create', type => 'object'}
1239         ],
1240         return => {desc => 'The purchase order id, Event on failure'}
1241     }
1242 );
1243
1244 sub create_purchase_order_api {
1245     my($self, $conn, $auth, $po, $args) = @_;
1246     $args ||= {};
1247
1248     my $e = new_editor(xact=>1, authtoken=>$auth);
1249     return $e->die_event unless $e->checkauth;
1250     return $e->die_event unless $e->allowed('CREATE_PURCHASE_ORDER', $po->ordering_agency);
1251     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1252
1253     # create the PO
1254     my %pargs = (ordering_agency => $e->requestor->ws_ou);
1255     $pargs{provider} = $po->provider if $po->provider;
1256     $po = create_purchase_order($mgr, %pargs) or return $e->die_event;
1257
1258     my $li_ids = $$args{lineitems};
1259
1260     if($li_ids) {
1261
1262         for my $li_id (@$li_ids) { 
1263
1264             my $li = $e->retrieve_acq_lineitem([
1265                 $li_id,
1266                 {flesh => 1, flesh_fields => {jub => ['attributes']}}
1267             ]) or return $e->die_event;
1268
1269             $li->provider($po->provider);
1270             $li->purchase_order($po->id);
1271             update_lineitem($mgr, $li) or return $e->die_event;
1272             $mgr->respond;
1273
1274             create_lineitem_debits($mgr, $li) or return $e->die_event;
1275         }
1276     }
1277
1278     # commit before starting the asset creation
1279     $e->xact_commit;
1280
1281     if($li_ids and $$args{create_assets}) {
1282         create_lineitem_list_assets($mgr, $li_ids) or return $e->die_event;
1283     }
1284
1285     return $mgr->respond_complete;
1286 }
1287
1288
1289 __PACKAGE__->register_method(
1290         method => 'lineitem_detail_CUD_batch',
1291         api_name => 'open-ils.acq.lineitem_detail.cud.batch',
1292     stream => 1,
1293         signature => {
1294         desc => q/Creates a new purchase order line item detail.  
1295             Additionally creates the associated fund_debit/,
1296         params => [
1297             {desc => 'Authentication token', type => 'string'},
1298             {desc => 'List of lineitem_details to create', type => 'array'},
1299         ],
1300         return => {desc => 'Streaming response of current position in the array'}
1301     }
1302 );
1303
1304 sub lineitem_detail_CUD_batch {
1305     my($self, $conn, $auth, $li_details) = @_;
1306
1307     my $e = new_editor(xact=>1, authtoken=>$auth);
1308     return $e->die_event unless $e->checkauth;
1309     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1310
1311     # XXX perms
1312
1313     $mgr->total(scalar(@$li_details));
1314     
1315     my %li_cache;
1316
1317     for my $lid (@$li_details) {
1318
1319         my $li = $li_cache{$lid->lineitem} || $e->retrieve_acq_lineitem($lid->lineitem);
1320
1321         if($lid->isnew) {
1322             create_lineitem_detail($mgr, %{$lid->to_bare_hash}) or return $e->die_event;
1323
1324         } elsif($lid->ischanged) {
1325             $e->update_acq_lineitem_detail($lid) or return $e->die_event;
1326
1327         } elsif($lid->isdeleted) {
1328             delete_lineitem_detail($mgr, $lid) or return $e->die_event;
1329         }
1330
1331         $mgr->respond(li => $li);
1332         $li_cache{$lid->lineitem} = $li;
1333     }
1334
1335     $e->commit;
1336     return $mgr->respond_complete;
1337 }
1338
1339
1340 __PACKAGE__->register_method(
1341         method => 'receive_po_api',
1342         api_name        => 'open-ils.acq.purchase_order.receive'
1343 );
1344
1345 sub receive_po_api {
1346     my($self, $conn, $auth, $po_id) = @_;
1347     my $e = new_editor(xact => 1, authtoken => $auth);
1348     return $e->die_event unless $e->checkauth;
1349     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1350
1351     my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1352     return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1353
1354     my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
1355
1356     for my $li_id (@$li_ids) {
1357         receive_lineitem($mgr, $li_id) or return $e->die_event;
1358         $mgr->respond;
1359     }
1360
1361     $po->state('received');
1362     update_purchase_order($mgr, $po) or return $e->die_event;
1363
1364     $e->commit;
1365     return $mgr->respond_complete;
1366 }
1367
1368
1369 __PACKAGE__->register_method(
1370         method => 'receive_lineitem_detail_api',
1371         api_name        => 'open-ils.acq.lineitem_detail.receive',
1372         signature => {
1373         desc => 'Mark a lineitem_detail as received',
1374         params => [
1375             {desc => 'Authentication token', type => 'string'},
1376             {desc => 'lineitem detail ID', type => 'number'}
1377         ],
1378         return => {desc => '1 on success, Event on error'}
1379     }
1380 );
1381
1382 sub receive_lineitem_detail_api {
1383     my($self, $conn, $auth, $lid_id) = @_;
1384
1385     my $e = new_editor(xact=>1, authtoken=>$auth);
1386     return $e->die_event unless $e->checkauth;
1387     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1388
1389     my $lid = $e->retrieve_acq_lineitem_detail([
1390         $lid_id, {
1391             flesh => 2,
1392             flesh_fields => {
1393                 acqlid => ['lineitem'],
1394                 jub => ['purchase_order']
1395             }
1396         }
1397     ]);
1398
1399     return $e->die_event unless $e->allowed(
1400         'RECEIVE_PURCHASE_ORDER', $lid->lineitem->purchase_order->ordering_agency);
1401
1402     receive_lineitem_detail($mgr, $lid_id) or return $e->die_event;
1403     $e->commit;
1404     return 1;
1405 }
1406
1407 __PACKAGE__->register_method(
1408         method => 'receive_lineitem_api',
1409         api_name        => 'open-ils.acq.lineitem.receive',
1410         signature => {
1411         desc => 'Mark a lineitem as received',
1412         params => [
1413             {desc => 'Authentication token', type => 'string'},
1414             {desc => 'lineitem detail ID', type => 'number'}
1415         ],
1416         return => {desc => '1 on success, Event on error'}
1417     }
1418 );
1419
1420 sub receive_lineitem_api {
1421     my($self, $conn, $auth, $li_id) = @_;
1422
1423     my $e = new_editor(xact=>1, authtoken=>$auth);
1424     return $e->die_event unless $e->checkauth;
1425     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1426
1427     my $li = $e->retrieve_acq_lineitem([
1428         $li_id, {
1429             flesh => 1,
1430             flesh_fields => {
1431                 jub => ['purchase_order']
1432             }
1433         }
1434     ]) or return $e->die_event;
1435
1436     return $e->die_event unless $e->allowed(
1437         'RECEIVE_PURCHASE_ORDER', $li->purchase_order->ordering_agency);
1438
1439     receive_lineitem($mgr, $li_id) or return $e->die_event;
1440     $e->commit;
1441     return 1;
1442 }
1443
1444
1445 __PACKAGE__->register_method(
1446         method => 'rollback_receive_po_api',
1447         api_name        => 'open-ils.acq.purchase_order.receive.rollback'
1448 );
1449
1450 sub rollback_receive_po_api {
1451     my($self, $conn, $auth, $po_id) = @_;
1452     my $e = new_editor(xact => 1, authtoken => $auth);
1453     return $e->die_event unless $e->checkauth;
1454     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1455
1456     my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
1457     return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1458
1459     my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
1460
1461     for my $li_id (@$li_ids) {
1462         rollback_receive_lineitem($mgr, $li_id) or return $e->die_event;
1463         $mgr->respond;
1464     }
1465
1466     $po->state('on-order');
1467     update_purchase_order($mgr, $po) or return $e->die_event;
1468
1469     $e->commit;
1470     return $mgr->respond_complete;
1471 }
1472
1473
1474 __PACKAGE__->register_method(
1475         method => 'rollback_receive_lineitem_detail_api',
1476         api_name        => 'open-ils.acq.lineitem_detail.receive.rollback',
1477         signature => {
1478         desc => 'Mark a lineitem_detail as received',
1479         params => [
1480             {desc => 'Authentication token', type => 'string'},
1481             {desc => 'lineitem detail ID', type => 'number'}
1482         ],
1483         return => {desc => '1 on success, Event on error'}
1484     }
1485 );
1486
1487 sub rollback_receive_lineitem_detail_api {
1488     my($self, $conn, $auth, $lid_id) = @_;
1489
1490     my $e = new_editor(xact=>1, authtoken=>$auth);
1491     return $e->die_event unless $e->checkauth;
1492     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1493
1494     my $lid = $e->retrieve_acq_lineitem_detail([
1495         $lid_id, {
1496             flesh => 2,
1497             flesh_fields => {
1498                 acqlid => ['lineitem'],
1499                 jub => ['purchase_order']
1500             }
1501         }
1502     ]);
1503     my $li = $lid->lineitem;
1504     my $po = $li->purchase_order;
1505
1506     return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1507     rollback_receive_lineitem_detail($mgr, $lid_id) or return $e->die_event;
1508
1509     $li->state('on-order');
1510     $po->state('on-order');
1511     udpate_lineitem($mgr, $li) or return $e->die_event;
1512     udpate_purchase_order($mgr, $po) or return $e->die_event;
1513
1514     $e->commit;
1515     return 1;
1516 }
1517
1518 __PACKAGE__->register_method(
1519         method => 'rollback_receive_lineitem_api',
1520         api_name        => 'open-ils.acq.lineitem.receive.rollback',
1521         signature => {
1522         desc => 'Mark a lineitem as received',
1523         params => [
1524             {desc => 'Authentication token', type => 'string'},
1525             {desc => 'lineitem detail ID', type => 'number'}
1526         ],
1527         return => {desc => '1 on success, Event on error'}
1528     }
1529 );
1530
1531 sub rollback_receive_lineitem_api {
1532     my($self, $conn, $auth, $li_id) = @_;
1533
1534     my $e = new_editor(xact=>1, authtoken=>$auth);
1535     return $e->die_event unless $e->checkauth;
1536     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1537
1538     my $li = $e->retrieve_acq_lineitem_detail([
1539         $li_id, {
1540             flesh => 1,
1541             flesh_fields => {
1542                 jub => ['purchase_order']
1543             }
1544         }
1545     ]);
1546     my $po = $li->purchase_order;
1547
1548     return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
1549
1550     rollback_receive_lineitem($mgr, $li_id) or return $e->die_event;
1551
1552     $po->state('on-order');
1553     update_purchase_order($mgr, $po) or return $e->die_event;
1554
1555     $e->commit;
1556     return 1;
1557 }
1558
1559
1560 __PACKAGE__->register_method(
1561         method => 'set_lineitem_price_api',
1562         api_name        => 'open-ils.acq.lineitem.price.set',
1563         signature => {
1564         desc => 'Set lineitem price.  If debits already exist, update them as well',
1565         params => [
1566             {desc => 'Authentication token', type => 'string'},
1567             {desc => 'lineitem ID', type => 'number'}
1568         ],
1569         return => {desc => 'status blob, Event on error'}
1570     }
1571 );
1572
1573 sub set_lineitem_price_api {
1574     my($self, $conn, $auth, $li_id, $price, $currency) = @_;
1575
1576     my $e = new_editor(xact=>1, authtoken=>$auth);
1577     return $e->die_event unless $e->checkauth;
1578     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1579
1580     # XXX perms
1581
1582     my $li = $e->retrieve_acq_lineitem($li_id) or return $e->die_event;
1583
1584     # update the local attr for estimated price
1585     set_lineitem_attr(
1586         $mgr, 
1587         attr_name => 'estimated_price',
1588         attr_type => 'lineitem_local_attr_definition',
1589         attr_value => $price,
1590         lineitem => $li_id
1591     ) or return $e->die_event;
1592
1593     my $lid_ids = $e->search_acq_lineitem_detail(
1594         {lineitem => $li_id, fund_debit => {'!=' => undef}}, 
1595         {idlist => 1}
1596     );
1597
1598     for my $lid_id (@$lid_ids) {
1599
1600         my $lid = $e->retrieve_acq_lineitem_detail([
1601             $lid_id, {
1602             flesh => 1, flesh_fields => {acqlid => ['fund', 'fund_debit']}}
1603         ]);
1604
1605         # onless otherwise specified, assume currency of new price is same as currency type of the fund
1606         $currency ||= $lid->fund->currency_type;
1607         my $amount = $price;
1608
1609         if($lid->fund->currency_type ne $currency) {
1610             $amount = currency_conversion($mgr, $currency, $lid->fund->currency_type, $price);
1611         }
1612         
1613         $lid->fund_debit->origin_currency_type($currency);
1614         $lid->fund_debit->origin_amount($price);
1615         $lid->fund_debit->amount($amount);
1616
1617         $e->update_acq_fund_debit($lid->fund_debit) or return $e->die_event;
1618         $mgr->add_lid;
1619         $mgr->respond;
1620     }
1621
1622     $e->commit;
1623     return $mgr->respond_complete;
1624 }
1625
1626
1627 __PACKAGE__->register_method(
1628         method => 'clone_picklist_api',
1629         api_name        => 'open-ils.acq.picklist.clone',
1630         signature => {
1631         desc => 'Clones a picklist, including lineitem and lineitem details',
1632         params => [
1633             {desc => 'Authentication token', type => 'string'},
1634             {desc => 'Picklist ID', type => 'number'},
1635             {desc => 'New Picklist Name', type => 'string'}
1636         ],
1637         return => {desc => 'status blob, Event on error'}
1638     }
1639 );
1640
1641 sub clone_picklist_api {
1642     my($self, $conn, $auth, $pl_id, $name) = @_;
1643
1644     my $e = new_editor(xact=>1, authtoken=>$auth);
1645     return $e->die_event unless $e->checkauth;
1646     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1647
1648     my $old_pl = $e->retrieve_acq_picklist($pl_id);
1649     my $new_pl = create_picklist($mgr, %{$old_pl->to_bare_hash}, name => $name) or return $e->die_event;
1650
1651     my $li_ids = $e->search_acq_lineitem({picklist => $pl_id}, {idlist => 1});
1652
1653     for my $li_id (@$li_ids) {
1654
1655         # copy the lineitems
1656         my $li = $e->retrieve_acq_lineitem($li_id);
1657         my $new_li = create_lineitem($mgr, %{$li->to_bare_hash}, picklist => $new_pl->id) or return $e->die_event;
1658
1659         my $lid_ids = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist => 1});
1660         for my $lid_id (@$lid_ids) {
1661
1662             # copy the lineitem details
1663             my $lid = $e->retrieve_acq_lineitem_detail($lid_id);
1664             create_lineitem_detail($mgr, %{$lid->to_bare_hash}, lineitem => $new_li->id) or return $e->die_event;
1665         }
1666
1667         $mgr->respond;
1668     }
1669
1670     $e->commit;
1671     return $mgr->respond_complete;
1672 }
1673
1674
1675 __PACKAGE__->register_method(
1676         method => 'merge_picklist_api',
1677         api_name        => 'open-ils.acq.picklist.merge',
1678         signature => {
1679         desc => 'Merges 2 or more picklists into a single list',
1680         params => [
1681             {desc => 'Authentication token', type => 'string'},
1682             {desc => 'Lead Picklist ID', type => 'number'},
1683             {desc => 'List of subordinate picklist IDs', type => 'array'}
1684         ],
1685         return => {desc => 'status blob, Event on error'}
1686     }
1687 );
1688
1689 sub merge_picklist_api {
1690     my($self, $conn, $auth, $lead_pl, $pl_list) = @_;
1691
1692     my $e = new_editor(xact=>1, authtoken=>$auth);
1693     return $e->die_event unless $e->checkauth;
1694     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
1695
1696     # XXX perms on each picklist modified
1697
1698     # point all of the lineitems at the lead picklist
1699     my $li_ids = $e->search_acq_lineitem({picklist => $pl_list}, {idlist => 1});
1700
1701     for my $li_id (@$li_ids) {
1702         my $li = $e->retrieve_acq_lineitem($li_id);
1703         $li->picklist($lead_pl);
1704         update_lineitem($mgr, $li) or return $e->die_event;
1705         $mgr->respond;
1706     }
1707
1708     # now delete the subordinate lists
1709     for my $pl_id (@$pl_list) {
1710         my $pl = $e->retrieve_acq_picklist($pl_id);
1711         $e->delete_acq_picklist($pl) or return $e->die_event;
1712     }
1713
1714     $e->commit;
1715     return $mgr->respond_complete;
1716 }
1717
1718
1719
1720 1;