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