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