]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm
related items now returns LI's that may be attached to a selection lists only (no po)
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Acq / Lineitem.pm
1 package OpenILS::Application::Acq::Lineitem;
2 use base qw/OpenILS::Application/;
3 use strict; use warnings;
4
5 use OpenILS::Event;
6 use OpenSRF::Utils::Logger qw(:logger);
7 use OpenILS::Utils::Fieldmapper;
8 use OpenILS::Utils::CStoreEditor q/:funcs/;
9 use OpenILS::Const qw/:const/;
10 use OpenSRF::Utils::SettingsClient;
11 use OpenILS::Application::AppUtils;
12 use OpenILS::Application::Acq::Financials;
13 use OpenILS::Application::Cat::BibCommon;
14 use OpenILS::Application::Cat::AssetCommon;
15 my $U = 'OpenILS::Application::AppUtils';
16
17
18 __PACKAGE__->register_method(
19         method => 'create_lineitem',
20         api_name        => 'open-ils.acq.lineitem.create',
21         signature => {
22         desc => 'Creates a lineitem',
23         params => [
24             {desc => 'Authentication token', type => 'string'},
25             {desc => 'The lineitem object to create', type => 'object'},
26         ],
27         return => {desc => 'ID of newly created lineitem on success, Event on error'}
28     }
29 );
30
31 sub create_lineitem {
32     my($self, $conn, $auth, $li) = @_;
33     my $e = new_editor(xact=>1, authtoken=>$auth);
34     return $e->die_event unless $e->checkauth;
35
36
37     if($li->picklist) {
38         my $picklist = $e->retrieve_acq_picklist($li->picklist)
39             or return $e->die_event;
40
41         if($picklist->owner != $e->requestor->id) {
42             return $e->die_event unless 
43                 $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
44         }
45     
46         # indicate the picklist was updated
47         $picklist->edit_time('now');
48         $picklist->editor($e->requestor->id);
49         $e->update_acq_picklist($picklist) or return $e->die_event;
50     }
51
52     if($li->purchase_order) {
53         my $po = $e->retrieve_acq_purchase_order($li->purchase_order)
54             or return $e->die_event;
55         return $e->die_event unless 
56             $e->allowed('MANAGE_PROVIDER', $po->ordering_agency, $po);
57     }
58
59     $li->selector($e->requestor->id);
60     $e->create_acq_lineitem($li) or return $e->die_event;
61
62     $e->commit;
63     return $li->id;
64 }
65
66
67 __PACKAGE__->register_method(
68         method => 'retrieve_lineitem',
69         api_name        => 'open-ils.acq.lineitem.retrieve',
70         signature => {
71         desc => 'Retrieves a lineitem',
72         params => [
73             {desc => 'Authentication token', type => 'string'},
74             {desc => 'lineitem ID to retrieve', type => 'number'},
75             {options => q/Hash of options, including 
76                 "flesh_attrs", which fleshes the attributes; 
77                 "flesh_li_details", which fleshes the order details objects/, type => 'hash'},
78         ],
79         return => {desc => 'lineitem object on success, Event on error'}
80     }
81 );
82
83
84 sub retrieve_lineitem {
85     my($self, $conn, $auth, $li_id, $options) = @_;
86     my $e = new_editor(authtoken=>$auth);
87     return $e->die_event unless $e->checkauth;
88     return retrieve_lineitem_impl($e, $li_id, $options);
89 }
90
91 sub retrieve_lineitem_impl {
92     my ($e, $li_id, $options) = @_;
93     $options ||= {};
94
95     # XXX finer grained perms...
96
97     my $flesh = {};
98     if($$options{flesh_attrs} or $$options{flesh_notes}) {
99         $flesh = {flesh => 2, flesh_fields => {jub => []}};
100         if($$options{flesh_notes}) {
101             push(@{$flesh->{flesh_fields}->{jub}}, 'lineitem_notes');
102             $flesh->{flesh_fields}->{acqlin} = ['alert_text'];
103         }
104         if($$options{"flesh_cancel_reason"}) {
105             push @{$flesh->{flesh_fields}->{jub}}, "cancel_reason";
106         }
107         push(@{$flesh->{flesh_fields}->{jub}}, 'attributes') if $$options{flesh_attrs};
108     }
109
110     my $li = $e->retrieve_acq_lineitem([$li_id, $flesh]) or return $e->die_event;
111
112     if($$options{flesh_li_details}) {
113         my $ops = {
114             flesh => 1,
115             flesh_fields => {acqlid => ["cancel_reason"]} #XXX cancel_reason? always? really?
116         };
117         push(@{$ops->{flesh_fields}->{acqlid}}, 'fund') if $$options{flesh_fund};
118         push(@{$ops->{flesh_fields}->{acqlid}}, 'fund_debit') if $$options{flesh_fund_debit};
119         if (my $details = $e->search_acq_lineitem_detail([{lineitem => $li_id}, $ops])) {
120             $li->lineitem_details($details);
121             $li->item_count(scalar(@$details));
122         } else {
123             $li->lineitem_count(0);
124         }
125     } else {
126         my $details = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
127         $li->item_count(scalar(@$details));
128     }
129
130     if($li->purchase_order) {
131         my $purchase_order =
132             $e->retrieve_acq_purchase_order($li->purchase_order)
133                 or return $e->event;
134
135         if($purchase_order->owner != $e->requestor->id) {
136             return $e->event unless
137                 $e->allowed('VIEW_PURCHASE_ORDER', undef, $purchase_order);
138         }
139     } elsif($li->picklist) {
140         my $picklist = $e->retrieve_acq_picklist($li->picklist)
141             or return $e->event;
142     
143         if($picklist->owner != $e->requestor->id) {
144             return $e->event unless 
145                 $e->allowed('VIEW_PICKLIST', undef, $picklist);
146         }
147     }
148
149     $li->clear_marc if $$options{clear_marc};
150
151     return $li;
152 }
153
154 __PACKAGE__->register_method(
155         method => 'delete_lineitem',
156         api_name        => 'open-ils.acq.lineitem.delete',
157         signature => {
158         desc => 'Deletes a lineitem',
159         params => [
160             {desc => 'Authentication token', type => 'string'},
161             {desc => 'lineitem ID to delete', type => 'number'},
162         ],
163         return => {desc => '1 on success, Event on error'}
164     }
165 );
166
167 sub delete_lineitem {
168     my($self, $conn, $auth, $li_id) = @_;
169     my $e = new_editor(xact=>1, authtoken=>$auth);
170     return $e->die_event unless $e->checkauth;
171
172     my $li = $e->retrieve_acq_lineitem($li_id)
173         or return $e->die_event;
174
175     # XXX check state
176
177     if($li->picklist) {
178         my $picklist = $e->retrieve_acq_picklist($li->picklist)
179             or return $e->die_event;
180         return OpenILS::Event->new('BAD_PARAMS') 
181             if $picklist->owner != $e->requestor->id;
182     } else {
183         # check PO perms
184     }
185
186     # delete the attached lineitem_details
187     my $lid_ids = $e->search_acq_lineitem_detail(
188         {lineitem => $li_id}, {idlist=>1});
189
190     for my $lid_id (@$lid_ids) {
191         $e->delete_acq_lineitem_detail(
192             $e->retrieve_acq_lineitem_detail($lid_id))
193             or return $e->die_event;
194     }
195
196     $e->delete_acq_lineitem($li) or return $e->die_event;
197     $e->commit;
198     return 1;
199 }
200
201
202 __PACKAGE__->register_method(
203         method => 'update_lineitem',
204         api_name        => 'open-ils.acq.lineitem.update',
205         signature => {
206         desc => 'Update a lineitem',
207         params => [
208             {desc => 'Authentication token', type => 'string'},
209             {desc => 'lineitem object update', type => 'object'}
210         ],
211         return => {desc => '1 on success, Event on error'}
212     }
213 );
214
215 sub update_lineitem {
216     my($self, $conn, $auth, $li) = @_;
217     my $e = new_editor(xact=>1, authtoken=>$auth);
218     return $e->die_event unless $e->checkauth;
219     my $evt = update_lineitem_impl($e, $li);
220     return $evt if $evt;
221     $e->commit;
222     return 1;
223 }
224
225 sub update_lineitem_impl {
226     my($e, $li) = @_;
227
228     my $orig_li = $e->retrieve_acq_lineitem([
229         $li->id,
230         {   flesh => 1, # grab the lineitem with picklist attached
231             flesh_fields => {jub => ['picklist', 'purchase_order']}
232         }
233     ]) or return $e->die_event;
234
235     # the marc may have been cleared on retrieval...
236     $li->marc($e->retrieve_acq_lineitem($li->id)->marc)
237         unless $li->marc;
238
239     $li->editor($e->requestor->id);
240     $li->edit_time('now');
241     $e->update_acq_lineitem($li) or return $e->die_event;
242     return undef;
243 }
244
245 __PACKAGE__->register_method(
246         method => 'lineitem_search',
247         api_name => 'open-ils.acq.lineitem.search',
248     stream => 1,
249         signature => {
250         desc => 'Searches lineitems',
251         params => [
252             {desc => 'Authentication token', type => 'string'},
253             {desc => 'Search definition', type => 'object'},
254             {desc => 'Options hash.  idlist=true', type => 'object'},
255             {desc => 'List of lineitems', type => 'object/number'},
256         ]
257     }
258 );
259
260 sub lineitem_search {
261     my($self, $conn, $auth, $search, $options) = @_;
262     my $e = new_editor(authtoken=>$auth, xact=>1);
263     return $e->event unless $e->checkauth;
264     return $e->event unless $e->allowed('CREATE_PICKLIST');
265     # XXX needs permissions consideration
266     my $lis = $e->search_acq_lineitem($search, {idlist=>1});
267     for my $li_id (@$lis) {
268         if($$options{idlist}) {
269             $conn->respond($li_id);
270         } else {
271             my $res = retrieve_lineitem($self, $conn, $auth, $li_id, $options);
272             $conn->respond($res) unless $U->event_code($res);
273         }
274     }
275     return undef;
276 }
277
278 __PACKAGE__->register_method (
279     method        => 'lineitems_related_by_bib',
280     api_name    => 'open-ils.acq.lineitems_for_bib.by_bib_id',
281     stream      => 1,
282     signature => q/
283         Retrieves lineitems attached to same bib record, subject to the PO ordering agency.  This variant takes the bib id.
284         @param authtoken Login session key
285         @param bib_id Id for the pertinent bib record.
286         @param options Object for tweaking the selection criteria and fleshing options.
287     /
288 );
289
290 __PACKAGE__->register_method (
291     method        => 'lineitems_related_by_bib',
292     api_name    => 'open-ils.acq.lineitems_for_bib.by_lineitem_id',
293     stream      => 1,
294     signature => q/
295         Retrieves lineitems attached to same bib record, subject to the PO ordering agency.  This variant takes the id for any of the pertinent lineitems.
296         @param authtoken Login session key
297         @param bib_id Id for a pertinent lineitem.
298         @param options Object for tweaking the selection criteria and fleshing options.
299     /
300 );
301
302 __PACKAGE__->register_method (
303     method        => 'lineitems_related_by_bib',
304     api_name    => 'open-ils.acq.lineitems_for_bib.by_lineitem_id.count',
305     stream      => 1,
306     signature => q/See open-ils.acq.lineitems_for_bib.by_lineitem_id. This version returns numbers of lineitems only (XXX may count lineitems we don't actually have permission to retrieve)/
307 );
308
309 sub lineitems_related_by_bib {
310     my($self, $conn, $auth, $test_value, $options) = @_;
311     my $e = new_editor(authtoken => $auth);
312     return $e->event unless $e->checkauth;
313
314     my $perm_orgs = $U->user_has_work_perm_at($e, 'VIEW_PURCHASE_ORDER', {descendants =>1}, $e->requestor->id);
315
316     my $query = {
317         "select"=>{"jub"=>["id"]},
318         "from"=>{"jub" => {"acqpo" => {type => 'left'}, "acqpl" => {type => 'left'}}}, 
319         "where"=>{
320             '-or' => [
321                 { "+acqpo"=>{ "ordering_agency" => $perm_orgs } },
322                 { '+acqpl' => { org_unit => $perm_orgs } }
323             ]
324         },
325         "order_by"=>[{"class"=>"jub", "field"=>"create_time", "direction"=>"desc"}]
326     };
327
328     # Be sure we just return the original LI if no related bibs
329     if ($self->api_name =~ /by_lineitem_id/) {
330         my $orig = retrieve_lineitem($self, $conn, $auth, $test_value) or
331             return $e->die_event;
332         if ($test_value = $orig->eg_bib_id) {
333             $query->{"where"}->{"eg_bib_id"} = $test_value;
334         } else {
335             $query->{"where"}->{"id"} = $orig->id;
336         }
337     } elsif ($test_value) {
338         $query->{"where"}->{"eg_bib_id"} = $test_value;
339     } else {
340         $e->disconnect;
341         return new OpenILS::Event("BAD_PARAMS", "Null bib id");
342     }
343
344     if ($options && defined $options->{lineitem_state}) {
345         $query->{'where'}{'jub'}{'state'} = $options->{lineitem_state};
346     }
347
348     if ($options && defined $options->{po_state}) {
349         $query->{'where'}{'+acqpo'}{'state'} = $options->{po_state};
350     }
351
352     if ($options && defined $options->{order_by}) {
353         $query->{'order_by'} = $options->{order_by};
354     }
355
356     my $results = $e->json_query($query);
357     if ($self->api_name =~ /count$/) {
358         return scalar(@$results);
359     } else {
360         for my $result (@$results) {
361             # retrieve_lineitem takes care of POs and PLs and also handles
362             # options like flesh_notes and permissions checking.
363             $conn->respond(
364                 retrieve_lineitem($self, $conn, $auth, $result->{"id"}, $options)
365             );
366         }
367     }
368
369     return undef;
370 }
371
372
373 __PACKAGE__->register_method(
374     method => "lineitem_search_by_attributes",
375     api_name => "open-ils.acq.lineitem.search.by_attributes",
376     stream => 1,
377     signature => {
378         desc => "Performs a search against lineitem_attrs",
379         params => [
380             {desc => "Authentication token", type => "string"},
381             {   desc => q/
382 Search definition:
383     attr_value_pairs : list of pairs of (attr definition ID, attr value) where value can be scalar (fuzzy match) or array (exact match)
384     li_states        : list of lineitem states
385     po_agencies      : list of purchase order ordering agencies (org) ids
386
387 At least one of these search terms is required.
388                 /,
389                 type => "object"},
390             {   desc => q/
391 Options hash:
392     idlist      : if set, only return lineitem IDs
393     clear_marc  : if set, strip the MARC xml from the lineitem before delivery
394     flesh_attrs : flesh lineitem attributes;
395                 /,
396                 type => "object"}
397         ]
398     }
399 );
400
401 __PACKAGE__->register_method(
402     method => "lineitem_search_by_attributes",
403     api_name => "open-ils.acq.lineitem.search.by_attributes.ident",
404     stream => 1,
405     signature => {
406         desc => "Performs a search against lineitem_attrs where ident is true.".
407             "See open-ils.acq.lineitem.search.by_attributes for params."
408     }
409 );
410
411 sub lineitem_search_by_attributes {
412     my ($self, $conn, $auth, $search, $options) = @_;
413
414     my $e = new_editor(authtoken => $auth, xact => 1);
415     return $e->die_event unless $e->checkauth;
416     # XXX needs permissions consideration
417
418     return [] unless $search;
419     my $attr_value_pairs = $search->{attr_value_pairs};
420     my $li_states = $search->{li_states};
421     my $po_agencies = $search->{po_agencies}; # XXX if none, base it on perms
422
423     my $query = {
424         "select" => {"acqlia" =>
425             [{"column" => "lineitem", "transform" => "distinct"}]
426         },
427         "from" => {
428             "acqlia" => {
429                 "acqliad" => {"field" => "id", "fkey" => "definition"},
430                 "jub" => {
431                     "field" => "id",
432                     "fkey" => "lineitem",
433                     "join" => {
434                         "acqpo" => {
435                             "type" => "left",
436                             "field" => "id",
437                             "fkey" => "purchase_order"
438                         }
439                     }
440                 }
441             }
442         }
443     };
444
445     my $where = {};
446     $where->{"+acqliad"} = {"ident" => "t"}
447         if $self->api_name =~ /\.ident/;
448
449     my $searched_for_something = 0;
450
451     if (ref $attr_value_pairs eq "ARRAY") {
452         $where->{"-or"} = [];
453         foreach (@$attr_value_pairs) {
454             next if @$_ != 2;
455             my ($def, $value) = @$_;
456             push @{$where->{"-or"}}, {
457                 "-and" => {
458                     "attr_value" => (ref $value) ?
459                         $value : {"ilike" => "%" . $value . "%"},
460                     "definition" => $def
461                 }
462             };
463         }
464         $searched_for_something = 1;
465     }
466
467     if ($li_states and @$li_states) {
468         $where->{"+jub"} = {"state" => $li_states};
469         $searched_for_something = 1;
470     }
471
472     if ($po_agencies and @$po_agencies) {
473         $where->{"+acqpo"} = {"ordering_agency" => $po_agencies};
474         $searched_for_something = 1;
475     }
476
477     if (not $searched_for_something) {
478         $e->rollback;
479         return new OpenILS::Event(
480             "BAD_PARAMS", note => "You have provided no search terms."
481         );
482     }
483
484     $query->{"where"} = $where;
485     my $lis = $e->json_query($query);
486
487     for my $li_id_obj (@$lis) {
488         my $li_id = $li_id_obj->{"lineitem"};
489         if($options->{"idlist"}) {
490             $conn->respond($li_id);
491         } else {
492             $conn->respond(
493                 retrieve_lineitem($self, $conn, $auth, $li_id, $options)
494             );
495         }
496     }
497     undef;
498 }
499
500
501 __PACKAGE__->register_method(
502         method => 'lineitem_search_ident',
503         api_name => 'open-ils.acq.lineitem.search.ident',
504     stream => 1,
505         signature => {
506         desc => 'Performs a search against lineitem_attrs where ident is true',
507         params => [
508             {desc => 'Authentication token', type => 'string'},
509             {   desc => q/Search definition. Options are:
510                    attr_values : list of attribute values (required)
511                    li_states : list of lineitem states
512                    po_agencies : list of purchase order ordering agencies (org) ids
513                 /,
514                 type => 'object',
515             },
516             {   desc => q/
517                     Options hash.  Options are:
518                         idlist : if set, only return lineitem IDs
519                         clear_marc : if set, strip the MARC xml from the lineitem before delivery
520                         flesh_attrs : flesh lineitem attributes; 
521                 /,
522                 type => 'object',
523             }
524         ]
525     }
526 );
527
528 my $LI_ATTR_SEARCH = {
529     select => {acqlia => ['lineitem']},
530     from => {
531         acqlia => {
532             acqliad => {
533                 field => 'id',
534                 fkey => 'definition'
535             },
536             jub => {
537                 field => 'id',
538                 fkey => 'lineitem',
539                 join => {
540                     acqpo => {
541                         field => 'id',
542                         fkey => 'purchase_order'
543                     }
544                 }
545             }
546         }
547     }
548 };
549
550 sub lineitem_search_ident {
551     my($self, $conn, $auth, $search, $options) = @_;
552     my $e = new_editor(authtoken=>$auth, xact=>1);
553     return $e->event unless $e->checkauth;
554     # XXX needs permissions consideration
555
556     return [] unless $search;
557     my $attr_values = $search->{attr_values};
558     my $li_states = $search->{li_states};
559     my $po_agencies = $search->{po_agencies}; # XXX if none, base it on perms
560
561     my $where_clause = {
562         '-or' => [],
563         '+acqlia' => {
564             '+acqliad' => {ident => 't'},
565         }
566     };
567
568     push(@{$where_clause->{'-or'}}, {attr_value => {ilike => "%$_%"}}) for @$attr_values;
569
570     $where_clause->{'+jub'} = {state => {in => $li_states}}
571         if $li_states and @$li_states;
572
573     $where_clause->{'+acqpo'} = {ordering_agency => $po_agencies} 
574         if $po_agencies and @$po_agencies;
575
576     $LI_ATTR_SEARCH->{where} = $where_clause;
577
578     my $lis = $e->json_query($LI_ATTR_SEARCH);
579
580     for my $li_id_obj (@$lis) {
581         my $li_id = $li_id_obj->{lineitem};
582         if($$options{idlist}) {
583             $conn->respond($li_id);
584         } else {
585             my $li;
586             if($$options{flesh_attrs}) {
587                 $li = $e->retrieve_acq_lineitem([
588                     $li_id, {flesh => 1, flesh_fields => {jub => ['attributes']}}])
589             } else {
590                 $li = $e->retrieve_acq_lineitem($li_id);
591             }
592             $li->clear_marc if $$options{clear_marc};
593             $conn->respond($li);
594         }
595     }
596     return undef;
597 }
598
599
600
601 __PACKAGE__->register_method(
602         method => 'lineitem_detail_CUD_batch',
603         api_name => 'open-ils.acq.lineitem_detail.cud.batch',
604     stream => 1,
605         signature => {
606         desc => q/Creates a new purchase order line item detail.  
607             Additionally creates the associated fund_debit/,
608         params => [
609             {desc => 'Authentication token', type => 'string'},
610             {desc => 'List of lineitem_details to create', type => 'array'},
611         ],
612         return => {desc => 'Streaming response of current position in the array'}
613     }
614 );
615
616 sub lineitem_detail_CUD_batch {
617     my($self, $conn, $auth, $li_details, $options) = @_;
618     my $e = new_editor(xact=>1, authtoken=>$auth);
619     return $e->die_event unless $e->checkauth;
620     my $pos = 0;
621     my $total = scalar(@$li_details);
622     for my $li_detail (@$li_details) {
623         my $res;
624
625         use Data::Dumper;
626         $logger->info(Dumper($li_detail));
627         $logger->info('lid id ' . $li_detail->id);
628         $logger->info('lineitem ' . $li_detail->lineitem);
629
630         if($li_detail->isnew) {
631             $res = create_lineitem_detail_impl($self, $conn, $e, $li_detail, $options);
632         } elsif($li_detail->ischanged) {
633             $res = update_lineitem_detail_impl($self, $conn, $e, $li_detail);
634         } elsif($li_detail->isdeleted) {
635             $res = delete_lineitem_detail_impl($self, $conn, $e, $li_detail->id);
636         }
637         return $e->event if $e->died;
638         $conn->respond({maximum => $total, progress => $pos++, li => $res});
639     }
640     $e->commit;
641     return {complete => 1};
642 }
643
644
645 sub create_lineitem_detail_impl {
646     my($self, $conn, $e, $li_detail, $options) = @_;
647     $options ||= {};
648
649     my $li = $e->retrieve_acq_lineitem($li_detail->lineitem)
650         or return $e->die_event;
651
652     my $evt = update_li_edit_time($e, $li);
653     return $evt if $evt;
654
655     # XXX check lineitem provider perms
656
657     if($li_detail->fund) {
658         my $fund = $e->retrieve_acq_fund($li_detail->fund) or return $e->die_event;
659         return $e->die_event unless 
660             $e->allowed('MANAGE_FUND', $fund->org, $fund);
661     }
662
663     $e->create_acq_lineitem_detail($li_detail) or return $e->die_event;
664
665     unless($li_detail->barcode) {
666         my $pfx = $U->ou_ancestor_setting_value($li_detail->owning_lib, 'acq.tmp_barcode_prefix') || 'ACQ';
667         $li_detail->barcode($pfx.$li_detail->id);
668     }
669     unless($li_detail->cn_label) {
670         my $pfx = $U->ou_ancestor_setting_value($li_detail->owning_lib, 'acq.tmp_callnumber_prefix') || 'ACQ';
671         $li_detail->cn_label($pfx.$li_detail->id);
672     }
673
674     if(my $loc = $U->ou_ancestor_setting_value($li_detail->owning_lib, 'acq.default_copy_location')) {
675         $li_detail->location($loc);
676     }
677
678     $e->update_acq_lineitem_detail($li_detail) or return $e->die_event;
679
680     return $li_detail if $$options{return_obj};
681     return $li_detail->id
682 }
683
684
685 sub update_li_edit_time {
686     my ($e, $li) = @_;
687     # some lineitem edits are allowed after approval time...
688 #    return OpenILS::Event->new('ACQ_LINEITEM_APPROVED', payload => $li->id)
689 #        if $li->state eq 'approved';
690     $li->edit_time('now');
691     $li->editor($e->requestor->id);
692     $e->update_acq_lineitem($li) or return $e->die_event;
693     return undef;
694 }
695
696
697 __PACKAGE__->register_method(
698         method => 'retrieve_lineitem_detail',
699         api_name        => 'open-ils.acq.lineitem_detail.retrieve',
700         signature => {
701         desc => q/Updates a lineitem detail/,
702         params => [
703             {desc => 'Authentication token', type => 'string'},
704             {desc => 'id of lineitem_detail to retrieve', type => 'number'},
705         ],
706         return => {desc => 'object on success, Event on failure'}
707     }
708 );
709 sub retrieve_lineitem_detail {
710     my($self, $conn, $auth, $li_detail_id) = @_;
711     my $e = new_editor(authtoken=>$auth);
712     return $e->event unless $e->checkauth;
713
714     my $li_detail = $e->retrieve_acq_lineitem_detail($li_detail_id)
715         or return $e->event;
716
717     if($li_detail->fund) {
718         my $fund = $e->retrieve_acq_fund($li_detail->fund) or return $e->event;
719         return $e->event unless 
720             $e->allowed('MANAGE_FUND', $fund->org, $fund);
721     }
722
723     # XXX check lineitem perms
724     return $li_detail;
725 }
726
727
728
729 __PACKAGE__->register_method(
730         method => 'approve_lineitem',
731         api_name        => 'open-ils.acq.lineitem.approve',
732         signature => {
733         desc => 'Mark a lineitem as approved',
734         params => [
735             {desc => 'Authentication token', type => 'string'},
736             {desc => 'lineitem ID', type => 'number'}
737         ],
738         return => {desc => '1 on success, Event on error'}
739     }
740 );
741 sub approve_lineitem {
742     my($self, $conn, $auth, $li_id) = @_;
743     my $e = new_editor(xact=>1, authtoken=>$auth);
744     return $e->die_event unless $e->checkauth;
745
746     # XXX perm checks for each lineitem detail
747
748     my $li = $e->retrieve_acq_lineitem($li_id)
749         or return $e->die_event;
750
751     return OpenILS::Event->new('ACQ_LINEITEM_APPROVED', payload => $li_id)
752         if $li->state eq 'approved';
753
754     my $details = $e->search_acq_lineitem_detail({lineitem => $li_id});
755     return OpenILS::Event->new('ACQ_LINEITEM_NO_COPIES', payload => $li_id)
756         unless scalar(@$details) > 0;
757
758     for my $detail (@$details) {
759         return OpenILS::Event->new('ACQ_LINEITEM_DETAIL_NO_FUND', payload => $detail->id)
760             unless $detail->fund;
761
762         return OpenILS::Event->new('ACQ_LINEITEM_DETAIL_NO_ORG', payload => $detail->id)
763             unless $detail->owning_lib;
764     }
765     
766     $li->state('approved');
767     $li->edit_time('now');
768     $e->update_acq_lineitem($li) or return $e->die_event;
769
770     $e->commit;
771     return 1;
772 }
773
774
775
776 __PACKAGE__->register_method(
777         method => 'set_lineitem_attr',
778         api_name        => 'open-ils.acq.lineitem_usr_attr.set',
779         signature => {
780         desc => 'Sets a lineitem_usr_attr value',
781         params => [
782             {desc => 'Authentication token', type => 'string'},
783             {desc => 'Lineitem ID', type => 'number'},
784             {desc => 'Attr name', type => 'string'},
785             {desc => 'Attr value', type => 'string'}
786         ],
787         return => {desc => '1 on success, Event on error'}
788     }
789 );
790
791 __PACKAGE__->register_method(
792         method => 'set_lineitem_attr',
793         api_name        => 'open-ils.acq.lineitem_local_attr.set',
794         signature => {
795         desc => 'Sets a lineitem_local_attr value',
796         params => [
797             {desc => 'Authentication token', type => 'string'},
798             {desc => 'Lineitem ID', type => 'number'},
799             {desc => 'Attr name', type => 'string'},
800             {desc => 'Attr value', type => 'string'}
801         ],
802         return => {desc => 'ID of the attr object on success, Event on error'}
803     }
804 );
805
806
807 sub set_lineitem_attr {
808     my($self, $conn, $auth, $li_id, $attr_name, $attr_value) = @_;
809     my $e = new_editor(xact=>1, authtoken=>$auth);
810     return $e->die_event unless $e->checkauth;
811
812     # XXX perm
813
814     my $attr_type = $self->api_name =~ /local_attr/ ?
815         'lineitem_local_attr_definition' : 'lineitem_usr_attr_definition';
816
817     my $attr = $e->search_acq_lineitem_attr({
818         lineitem => $li_id, 
819         attr_type => $attr_type,
820         attr_name => $attr_name})->[0];
821
822     my $find = "search_acq_$attr_type";
823
824     if($attr) {
825         $attr->attr_value($attr_value);
826         $e->update_acq_lineitem_attr($attr) or return $e->die_event;
827     } else {
828         $attr = Fieldmapper::acq::lineitem_attr->new;
829         $attr->lineitem($li_id);
830         $attr->attr_type($attr_type);
831         $attr->attr_name($attr_name);
832         $attr->attr_value($attr_value);
833
834         my $attr_def_id = $e->$find({code => $attr_name}, {idlist=>1})->[0] 
835             or return $e->die_event;
836         $attr->definition($attr_def_id);
837         $e->create_acq_lineitem_attr($attr) or return $e->die_event;
838     }
839
840     $e->commit;
841     return $attr->id;
842 }
843
844 __PACKAGE__->register_method(
845         method => 'get_lineitem_attr_defs',
846         api_name        => 'open-ils.acq.lineitem_attr_definition.retrieve.all',
847         signature => {
848         desc => 'Retrieve lineitem attr definitions',
849         params => [
850             {desc => 'Authentication token', type => 'string'},
851         ],
852         return => {desc => 'List of attr definitions'}
853     }
854 );
855
856 sub get_lineitem_attr_defs {
857     my($self, $conn, $auth) = @_;
858     my $e = new_editor(authtoken=>$auth);
859     return $e->event unless $e->checkauth;
860     my %results;
861     for my $type (qw/generated marc local usr provider/) {
862         my $call = "retrieve_all_acq_lineitem_${type}_attr_definition";
863         $results{$type} = $e->$call;
864     }
865     return \%results;
866 }
867
868
869 __PACKAGE__->register_method(
870         method => 'lineitem_note_CUD_batch',
871         api_name => 'open-ils.acq.lineitem_note.cud.batch',
872     stream => 1,
873         signature => {
874         desc => q/Manage lineitem notes/,
875         params => [
876             {desc => 'Authentication token', type => 'string'},
877             {desc => 'List of lineitem_notes to manage', type => 'array'},
878         ],
879         return => {desc => 'Streaming response of current position in the array'}
880     }
881 );
882
883 sub lineitem_note_CUD_batch {
884     my($self, $conn, $auth, $li_notes) = @_;
885
886     my $e = new_editor(xact=>1, authtoken=>$auth);
887     return $e->die_event unless $e->checkauth;
888     # XXX perms
889
890     my $total = @$li_notes;
891     my $count = 0;
892
893     for my $note (@$li_notes) {
894
895         $note->editor($e->requestor->id);
896         $note->edit_time('now');
897
898         if($note->isnew) {
899             $note->creator($e->requestor->id);
900             $note = $e->create_acq_lineitem_note($note) or return $e->die_event;
901
902         } elsif($note->isdeleted) {
903             $e->delete_acq_lineitem_note($note) or return $e->die_event;
904
905         } elsif($note->ischanged) {
906             $e->update_acq_lineitem_note($note) or return $e->die_event;
907         }
908
909         if(!$note->isdeleted) {
910             $note = $e->retrieve_acq_lineitem_note([
911                 $note->id, {
912                     "flesh" => 1, "flesh_fields" => {"acqlin" => ["alert_text"]}
913                 }
914             ]);
915         }
916
917         $conn->respond({maximum => $total, progress => ++$count, note => $note});
918     }
919
920     $e->commit;
921     return {complete => 1};
922 }
923
924 __PACKAGE__->register_method(
925     method => 'ranged_line_item_alert_text',
926     api_name => 'open-ils.acq.line_item_alert_text.ranged.retrieve.all');
927
928 sub ranged_line_item_alert_text {
929     my($self, $conn, $auth, $org_id, $depth) = @_;
930     my $e = new_editor(authtoken => $auth);
931     return $e->event unless $e->checkauth;
932     return $e->event unless $e->allowed('ADMIN_ACQ_LINEITEM_ALERT_TEXT', $org_id);
933     return $e->search_acq_lineitem_alert_text(
934         {owning_lib => $U->get_org_full_path($org_id, $depth)});
935 }
936
937
938 1;