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