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