1 package OpenILS::Application::Acq::Picklist;
2 use base qw/OpenILS::Application/;
3 use strict; use warnings;
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::Cat::BibCommon;
13 use OpenILS::Application::Cat::AssetCommon;
14 my $U = 'OpenILS::Application::AppUtils';
17 __PACKAGE__->register_method(
18 method => 'create_lineitem',
19 api_name => 'open-ils.acq.lineitem.create',
21 desc => 'Creates a lineitem',
23 {desc => 'Authentication token', type => 'string'},
24 {desc => 'The lineitem object to create', type => 'object'},
26 return => {desc => 'ID of newly created lineitem on success, Event on error'}
31 my($self, $conn, $auth, $li) = @_;
32 my $e = new_editor(xact=>1, authtoken=>$auth);
33 return $e->die_event unless $e->checkauth;
37 my $picklist = $e->retrieve_acq_picklist($li->picklist)
38 or return $e->die_event;
40 if($picklist->owner != $e->requestor->id) {
41 return $e->die_event unless
42 $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
45 # indicate the picklist was updated
46 $picklist->edit_time('now');
47 $picklist->editor($e->requestor->id);
48 $e->update_acq_picklist($picklist) or return $e->die_event;
51 if($li->purchase_order) {
52 my $po = $e->retrieve_acq_purchase_order($li->purchase_order)
53 or return $e->die_event;
54 return $e->die_event unless
55 $e->allowed('MANAGE_PROVIDER', $po->ordering_agency, $po);
58 $li->selector($e->requestor->id);
59 $e->create_acq_lineitem($li) or return $e->die_event;
66 __PACKAGE__->register_method(
67 method => 'retrieve_lineitem',
68 api_name => 'open-ils.acq.lineitem.retrieve',
70 desc => 'Retrieves a lineitem',
72 {desc => 'Authentication token', type => 'string'},
73 {desc => 'lineitem ID to retrieve', type => 'number'},
74 {options => q/Hash of options, including
75 "flesh_attrs", which fleshes the attributes;
76 "flesh_li_details", which fleshes the order details objects/, type => 'hash'},
78 return => {desc => 'lineitem object on success, Event on error'}
83 sub retrieve_lineitem {
84 my($self, $conn, $auth, $li_id, $options) = @_;
85 my $e = new_editor(authtoken=>$auth);
86 return $e->die_event unless $e->checkauth;
89 # XXX finer grained perms...
93 if($$options{flesh_attrs}) {
94 $li = $e->retrieve_acq_lineitem([
95 $li_id, {flesh => 1, flesh_fields => {jub => ['attributes']}}])
98 $li = $e->retrieve_acq_lineitem($li_id) or return $e->event;
101 if($$options{flesh_li_details}) {
104 flesh_fields => {acqlid => []}
106 push(@{$ops->{flesh_fields}->{acqlid}}, 'fund') if $$options{flesh_fund};
107 push(@{$ops->{flesh_fields}->{acqlid}}, 'fund_debit') if $$options{flesh_fund_debit};
108 my $details = $e->search_acq_lineitem_detail([{lineitem => $li_id}, $ops]);
109 $li->lineitem_details($details);
110 $li->item_count(scalar(@$details));
112 my $details = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
113 $li->item_count(scalar(@$details));
117 my $picklist = $e->retrieve_acq_picklist($li->picklist)
120 if($picklist->owner != $e->requestor->id) {
121 return $e->event unless
122 $e->allowed('VIEW_PICKLIST', undef, $picklist);
126 $li->clear_marc if $$options{clear_marc};
133 __PACKAGE__->register_method(
134 method => 'delete_lineitem',
135 api_name => 'open-ils.acq.lineitem.delete',
137 desc => 'Deletes a lineitem',
139 {desc => 'Authentication token', type => 'string'},
140 {desc => 'lineitem ID to delete', type => 'number'},
142 return => {desc => '1 on success, Event on error'}
146 sub delete_lineitem {
147 my($self, $conn, $auth, $li_id) = @_;
148 my $e = new_editor(xact=>1, authtoken=>$auth);
149 return $e->die_event unless $e->checkauth;
151 my $li = $e->retrieve_acq_lineitem($li_id)
152 or return $e->die_event;
157 my $picklist = $e->retrieve_acq_picklist($li->picklist)
158 or return $e->die_event;
159 return OpenILS::Event->new('BAD_PARAMS')
160 if $picklist->owner != $e->requestor->id;
165 # delete the attached lineitem_details
166 my $lid_ids = $e->search_acq_lineitem_detail(
167 {lineitem => $li_id}, {idlist=>1});
169 for my $lid_id (@$lid_ids) {
170 $e->delete_acq_lineitem_detail(
171 $e->retrieve_acq_lineitem_detail($lid_id))
172 or return $e->die_event;
175 $e->delete_acq_lineitem($li) or return $e->die_event;
181 __PACKAGE__->register_method(
182 method => 'update_lineitem',
183 api_name => 'open-ils.acq.lineitem.update',
185 desc => 'Update a lineitem',
187 {desc => 'Authentication token', type => 'string'},
188 {desc => 'lineitem object update', type => 'object'}
190 return => {desc => '1 on success, Event on error'}
194 sub update_lineitem {
195 my($self, $conn, $auth, $li) = @_;
196 my $e = new_editor(xact=>1, authtoken=>$auth);
197 return $e->die_event unless $e->checkauth;
198 my $evt = update_lineitem_impl($e, $li);
204 sub update_lineitem_impl {
207 my $orig_li = $e->retrieve_acq_lineitem([
209 { flesh => 1, # grab the lineitem with picklist attached
210 flesh_fields => {jub => ['picklist', 'purchase_order']}
212 ]) or return $e->die_event;
214 # the marc may have been cleared on retrieval...
215 $li->marc($e->retrieve_acq_lineitem($li->id)->marc)
218 $li->editor($e->requestor->id);
219 $li->edit_time('now');
220 $e->update_acq_lineitem($li) or return $e->die_event;
224 __PACKAGE__->register_method(
225 method => 'lineitem_search',
226 api_name => 'open-ils.acq.lineitem.search',
229 desc => 'Searches lineitems',
231 {desc => 'Authentication token', type => 'string'},
232 {desc => 'Search definition', type => 'object'},
233 {desc => 'Optoins hash. idlist=true', type => 'object'},
234 {desc => 'List of lineitems', type => 'object/number'},
239 sub lineitem_search {
240 my($self, $conn, $auth, $search, $options) = @_;
241 my $e = new_editor(authtoken=>$auth, xact=>1);
242 return $e->event unless $e->checkauth;
243 return $e->event unless $e->allowed('CREATE_PICKLIST');
244 # XXX needs permissions consideration
245 my $lis = $e->search_acq_lineitem($search, {idlist=>1});
246 for my $li_id (@$lis) {
247 if($$options{idlist}) {
248 $conn->respond($li_id);
250 my $res = retrieve_lineitem($self, $conn, $auth, $li_id, $options);
251 $conn->respond($res) unless $U->event_code($res);
258 __PACKAGE__->register_method(
259 method => 'lineitem_search_ident',
260 api_name => 'open-ils.acq.lineitem.search.ident',
263 desc => 'Performs a search against lineitem_attrs where ident is true',
265 {desc => 'Authentication token', type => 'string'},
266 { desc => q/Search definition. Options are:
267 attr_values : list of attribute values (required)
268 li_states : list of lineitem states
269 po_agencies : list of purchase order ordering agencies (org) ids
274 Options hash. Options are:
275 idlist : if set, only return lineitem IDs
276 clear_marc : if set, strip the MARC xml from the lineitem before delivery
277 flesh_attrs : flesh lineitem attributes;
285 my $LI_ATTR_SEARCH = {
286 select => {acqlia => ['lineitem']},
299 fkey => 'purchase_order'
307 sub lineitem_search_ident {
308 my($self, $conn, $auth, $search, $options) = @_;
309 my $e = new_editor(authtoken=>$auth, xact=>1);
310 return $e->event unless $e->checkauth;
311 # XXX needs permissions consideration
313 return [] unless $search;
314 my $attr_values = $search->{attr_values};
315 my $li_states = $search->{li_states};
316 my $po_agencies = $search->{po_agencies}; # XXX if none, base it on perms
321 '+acqliad' => {ident => 't'},
325 push(@{$where_clause->{'-or'}}, {attr_value => {ilike => "%$_%"}}) for @$attr_values;
327 $where_clause->{'+jub'} = {state => {in => $li_states}}
328 if $li_states and @$li_states;
330 $where_clause->{'+acqpo'} = {ordering_agency => $po_agencies}
331 if $po_agencies and @$po_agencies;
333 $LI_ATTR_SEARCH->{where} = $where_clause;
335 my $lis = $e->json_query($LI_ATTR_SEARCH);
337 for my $li_id_obj (@$lis) {
338 my $li_id = $li_id_obj->{lineitem};
339 if($$options{idlist}) {
340 $conn->respond($li_id);
343 if($$options{flesh_attrs}) {
344 $li = $e->retrieve_acq_lineitem([
345 $li_id, {flesh => 1, flesh_fields => {jub => ['attributes']}}])
347 $li = $e->retrieve_acq_lineitem($li_id);
349 $li->clear_marc if $$options{clear_marc};
357 __PACKAGE__->register_method(
358 method => 'create_lineitem_detail',
359 api_name => 'open-ils.acq.lineitem_detail.create',
361 desc => q/Creates a new purchase order line item detail.
362 Additionally creates the associated fund_debit/,
364 {desc => 'Authentication token', type => 'string'},
365 {desc => 'lineitem_detail to create', type => 'object'},
367 return => {desc => 'The purchase order line item detail id, Event on failure'}
371 sub create_lineitem_detail {
372 my($self, $conn, $auth, $li_detail, $options) = @_;
373 my $e = new_editor(xact=>1, authtoken=>$auth);
374 return $e->die_event unless $e->checkauth;
375 my $res = create_lineitem_detail_impl($self, $conn, $e, $li_detail, $options);
376 return $e->event if $e->died;
382 __PACKAGE__->register_method(
383 method => 'lineitem_detail_CUD_batch',
384 api_name => 'open-ils.acq.lineitem_detail.cud.batch',
387 desc => q/Creates a new purchase order line item detail.
388 Additionally creates the associated fund_debit/,
390 {desc => 'Authentication token', type => 'string'},
391 {desc => 'List of lineitem_details to create', type => 'array'},
393 return => {desc => 'Streaming response of current position in the array'}
397 sub lineitem_detail_CUD_batch {
398 my($self, $conn, $auth, $li_details, $options) = @_;
399 my $e = new_editor(xact=>1, authtoken=>$auth);
400 return $e->die_event unless $e->checkauth;
402 my $total = scalar(@$li_details);
403 for my $li_detail (@$li_details) {
407 $logger->info(Dumper($li_detail));
408 $logger->info('lid id ' . $li_detail->id);
409 $logger->info('lineitem ' . $li_detail->lineitem);
411 if($li_detail->isnew) {
412 $res = create_lineitem_detail_impl($self, $conn, $e, $li_detail, $options);
413 } elsif($li_detail->ischanged) {
414 $res = update_lineitem_detail_impl($self, $conn, $e, $li_detail);
415 } elsif($li_detail->isdeleted) {
416 $res = delete_lineitem_detail_impl($self, $conn, $e, $li_detail->id);
418 return $e->event if $e->died;
419 $conn->respond({maximum => $total, progress => $pos++, li => $res});
422 return {complete => 1};
426 sub create_lineitem_detail_impl {
427 my($self, $conn, $e, $li_detail, $options) = @_;
430 my $li = $e->retrieve_acq_lineitem($li_detail->lineitem)
431 or return $e->die_event;
433 my $evt = update_li_edit_time($e, $li);
436 # XXX check lineitem provider perms
438 if($li_detail->fund) {
439 my $fund = $e->retrieve_acq_fund($li_detail->fund) or return $e->die_event;
440 return $e->die_event unless
441 $e->allowed('MANAGE_FUND', $fund->org, $fund);
444 $e->create_acq_lineitem_detail($li_detail) or return $e->die_event;
446 unless($li_detail->barcode) {
447 my $pfx = $U->ou_ancestor_setting_value($li_detail->owning_lib, 'acq.tmp_barcode_prefix') || 'ACQ';
448 $li_detail->barcode($pfx.$li_detail->id);
450 unless($li_detail->cn_label) {
451 my $pfx = $U->ou_ancestor_setting_value($li_detail->owning_lib, 'acq.tmp_callnumber_prefix') || 'ACQ';
452 $li_detail->cn_label($pfx.$li_detail->id);
455 if(my $loc = $U->ou_ancestor_setting_value($li_detail->owning_lib, 'acq.default_copy_location')) {
456 $li_detail->location($loc);
459 $e->update_acq_lineitem_detail($li_detail) or return $e->die_event;
461 return $li_detail if $$options{return_obj};
462 return $li_detail->id
467 __PACKAGE__->register_method(
468 method => 'update_lineitem_detail',
469 api_name => 'open-ils.acq.lineitem_detail.update',
471 desc => q/Updates a lineitem detail/,
473 {desc => 'Authentication token', type => 'string'},
474 {desc => 'lineitem_detail to update', type => 'object'},
476 return => {desc => '1 on success, Event on failure'}
480 sub update_lineitem_detail {
481 my($self, $conn, $auth, $li_detail) = @_;
482 my $e = new_editor(xact=>1, authtoken=>$auth);
483 return $e->die_event unless $e->checkauth;
484 my $res = update_lineitem_detail_impl($self, $conn, $e, $li_detail);
485 return $e->event if $e->died;
490 sub update_lineitem_detail_impl {
491 my($self, $conn, $e, $li_detail) = @_;
493 if($li_detail->fund) {
494 my $fund = $e->retrieve_acq_fund($li_detail->fund) or return $e->die_event;
495 return $e->die_event unless
496 $e->allowed('MANAGE_FUND', $fund->org, $fund);
499 # XXX check lineitem perms
501 my $li = $e->retrieve_acq_lineitem($li_detail->lineitem)
502 or return $e->die_event;
503 my $evt = update_li_edit_time($e, $li);
506 $e->update_acq_lineitem_detail($li_detail) or return $e->die_event;
510 sub update_li_edit_time {
512 # some lineitem edits are allowed after approval time...
513 # return OpenILS::Event->new('ACQ_LINEITEM_APPROVED', payload => $li->id)
514 # if $li->state eq 'approved';
515 $li->edit_time('now');
516 $li->editor($e->requestor->id);
517 $e->update_acq_lineitem($li) or return $e->die_event;
522 __PACKAGE__->register_method(
523 method => 'delete_lineitem_detail',
524 api_name => 'open-ils.acq.lineitem_detail.delete',
526 desc => q/Deletes a lineitem detail/,
528 {desc => 'Authentication token', type => 'string'},
529 {desc => 'lineitem_detail ID to delete', type => 'number'},
531 return => {desc => '1 on success, Event on failure'}
535 sub delete_lineitem_detail {
536 my($self, $conn, $auth, $li_detail_id) = @_;
537 my $e = new_editor(xact=>1, authtoken=>$auth);
538 return $e->die_event unless $e->checkauth;
539 my $res = delete_lineitem_detail_impl($self, $conn, $e, $li_detail_id);
540 return $e->event if $e->died;
545 sub delete_lineitem_detail_impl {
546 my($self, $conn, $e, $li_detail_id) = @_;
548 my $li_detail = $e->retrieve_acq_lineitem_detail([
551 flesh_fields => {acqlid => ['lineitem']}
553 ]) or return $e->die_event;
555 my $li = $li_detail->lineitem;
557 my $evt = update_li_edit_time($e, $li);
560 return OpenILS::Event->new('BAD_PARAMS') unless
561 $li->state =~ /new|approved/;
563 # XXX check lineitem perms
565 $e->delete_acq_lineitem_detail($li_detail) or return $e->die_event;
570 __PACKAGE__->register_method(
571 method => 'retrieve_lineitem_detail',
572 api_name => 'open-ils.acq.lineitem_detail.retrieve',
574 desc => q/Updates a lineitem detail/,
576 {desc => 'Authentication token', type => 'string'},
577 {desc => 'id of lineitem_detail to retrieve', type => 'number'},
579 return => {desc => 'object on success, Event on failure'}
582 sub retrieve_lineitem_detail {
583 my($self, $conn, $auth, $li_detail_id) = @_;
584 my $e = new_editor(authtoken=>$auth);
585 return $e->event unless $e->checkauth;
587 my $li_detail = $e->retrieve_acq_lineitem_detail($li_detail_id)
590 if($li_detail->fund) {
591 my $fund = $e->retrieve_acq_fund($li_detail->fund) or return $e->event;
592 return $e->event unless
593 $e->allowed('MANAGE_FUND', $fund->org, $fund);
596 # XXX check lineitem perms
602 __PACKAGE__->register_method(
603 method => 'approve_lineitem',
604 api_name => 'open-ils.acq.lineitem.approve',
606 desc => 'Mark a lineitem as approved',
608 {desc => 'Authentication token', type => 'string'},
609 {desc => 'lineitem ID', type => 'number'}
611 return => {desc => '1 on success, Event on error'}
614 sub approve_lineitem {
615 my($self, $conn, $auth, $li_id) = @_;
616 my $e = new_editor(xact=>1, authtoken=>$auth);
617 return $e->die_event unless $e->checkauth;
619 # XXX perm checks for each lineitem detail
621 my $li = $e->retrieve_acq_lineitem($li_id)
622 or return $e->die_event;
624 return OpenILS::Event->new('ACQ_LINEITEM_APPROVED', payload => $li_id)
625 if $li->state eq 'approved';
627 my $details = $e->search_acq_lineitem_detail({lineitem => $li_id});
628 return OpenILS::Event->new('ACQ_LINEITEM_NO_COPIES', payload => $li_id)
629 unless scalar(@$details) > 0;
631 for my $detail (@$details) {
632 return OpenILS::Event->new('ACQ_LINEITEM_DETAIL_NO_FUND', payload => $detail->id)
633 unless $detail->fund;
635 return OpenILS::Event->new('ACQ_LINEITEM_DETAIL_NO_ORG', payload => $detail->id)
636 unless $detail->owning_lib;
639 $li->state('approved');
640 $li->edit_time('now');
641 $e->update_acq_lineitem($li) or return $e->die_event;
648 __PACKAGE__->register_method(
649 method => 'receive_lineitem_detail',
650 api_name => 'open-ils.acq.lineitem_detail.receive',
652 desc => 'Mark a lineitem_detail as received',
654 {desc => 'Authentication token', type => 'string'},
655 {desc => 'lineitem detail ID', type => 'number'}
657 return => {desc => '1 on success, Event on error'}
660 sub receive_lineitem_detail {
661 my($self, $conn, $auth, $lid_id) = @_;
662 my $e = new_editor(xact=>1, authtoken=>$auth);
663 return $e->die_event unless $e->checkauth;
664 my $resp = receive_lineitem_detail_impl($e, $lid_id);
665 if($resp) {$e->rollback; return $resp;}
670 sub receive_lineitem_detail_impl {
671 my($e, $lid_id) = @_;
673 my $lid = $e->retrieve_acq_lineitem_detail([
677 acqlid => ['fund_debit']
680 ]) or return $e->die_event;
682 return OpenILS::Event->new(
683 'ACQ_LINEITEM_DETAIL_RECEIVED') if $lid->recv_time;
685 $lid->recv_time('now');
686 $e->update_acq_lineitem_detail($lid) or return $e->die_event;
688 my $copy = $e->retrieve_asset_copy($lid->eg_copy_id)
689 or return $e->die_event;
691 $copy->status(OILS_COPY_STATUS_IN_PROCESS);
692 $copy->edit_date('now');
693 $copy->editor($e->requestor->id);
694 $e->update_asset_copy($copy) or return $e->die_event;
696 if($lid->fund_debit) {
697 $lid->fund_debit->encumbrance('f');
698 $e->update_acq_fund_debit($lid->fund_debit) or return $e->die_event;
701 # -------------------------------------------------------------
702 # if all of the lineitem details for this lineitem have
703 # been received, mark the lineitem as received
704 # -------------------------------------------------------------
705 my $non_recv = $e->search_acq_lineitem_detail(
706 {recv_time => undef, lineitem => $lid->lineitem}, {idlist=>1});
708 return undef if @$non_recv;
710 my $li = $e->retrieve_acq_lineitem($lid->lineitem);
711 $li->state('received');
712 $li->edit_time('now');
713 $e->update_acq_lineitem($li) or return $e->die_event;
715 # -------------------------------------------------------------
716 # if all of the lineitems for this PO are received,
717 # mark the PO as received
718 # -------------------------------------------------------------
719 my $non_recv_li = $e->search_acq_lineitem(
720 { purchase_order => $li->purchase_order,
721 state => {'!=' => 'received'}
724 return undef if @$non_recv_li;
726 my $po = $e->retrieve_acq_purchase_order($li->purchase_order);
727 $po->state('received');
728 $po->edit_time('now');
729 $e->update_acq_purchase_order($po) or return $e->die_event;
735 __PACKAGE__->register_method(
736 method => 'set_lineitem_attr',
737 api_name => 'open-ils.acq.lineitem_usr_attr.set',
739 desc => 'Sets a lineitem_usr_attr value',
741 {desc => 'Authentication token', type => 'string'},
742 {desc => 'Lineitem ID', type => 'number'},
743 {desc => 'Attr name', type => 'string'},
744 {desc => 'Attr value', type => 'string'}
746 return => {desc => '1 on success, Event on error'}
750 __PACKAGE__->register_method(
751 method => 'set_lineitem_attr',
752 api_name => 'open-ils.acq.lineitem_local_attr.set',
754 desc => 'Sets a lineitem_local_attr value',
756 {desc => 'Authentication token', type => 'string'},
757 {desc => 'Lineitem ID', type => 'number'},
758 {desc => 'Attr name', type => 'string'},
759 {desc => 'Attr value', type => 'string'}
761 return => {desc => 'ID of the attr object on success, Event on error'}
766 sub set_lineitem_attr {
767 my($self, $conn, $auth, $li_id, $attr_name, $attr_value) = @_;
768 my $e = new_editor(xact=>1, authtoken=>$auth);
769 return $e->die_event unless $e->checkauth;
773 my $attr_type = $self->api_name =~ /local_attr/ ?
774 'lineitem_local_attr_definition' : 'lineitem_usr_attr_definition';
776 my $attr = $e->search_acq_lineitem_attr({
778 attr_type => $attr_type,
779 attr_name => $attr_name})->[0];
781 my $find = "search_acq_$attr_type";
784 $attr->attr_value($attr_value);
785 $e->update_acq_lineitem_attr($attr) or return $e->die_event;
787 $attr = Fieldmapper::acq::lineitem_attr->new;
788 $attr->lineitem($li_id);
789 $attr->attr_type($attr_type);
790 $attr->attr_name($attr_name);
791 $attr->attr_value($attr_value);
793 my $attr_def_id = $e->$find({code => $attr_name}, {idlist=>1})->[0]
794 or return $e->die_event;
795 $attr->definition($attr_def_id);
796 $e->create_acq_lineitem_attr($attr) or return $e->die_event;
803 __PACKAGE__->register_method(
804 method => 'get_lineitem_attr_defs',
805 api_name => 'open-ils.acq.lineitem_attr_definition.retrieve.all',
807 desc => 'Retrieve lineitem attr definitions',
809 {desc => 'Authentication token', type => 'string'},
811 return => {desc => 'List of attr definitions'}
815 sub get_lineitem_attr_defs {
816 my($self, $conn, $auth) = @_;
817 my $e = new_editor(authtoken=>$auth);
818 return $e->event unless $e->checkauth;
820 for my $type (qw/generated marc local usr provider/) {
821 my $call = "retrieve_all_acq_lineitem_${type}_attr_definition";
822 $results{$type} = $e->$call;