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