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