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