From: Bill Erickson Date: Wed, 3 Oct 2012 14:29:01 +0000 (-0400) Subject: Port INVOIC to EDIReader X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=32a7318d6cff1adb8a34faffc5be19950b4a88fc;p=evergreen%2Fequinox.git Port INVOIC to EDIReader Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/EDI.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/EDI.pm index 09cdd87269..ee50f41123 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/EDI.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/EDI.pm @@ -177,7 +177,7 @@ sub process_retrieval { $incoming->account($account->id); $incoming->edi($content); $incoming->message_type($msg_hash->{message_type}); - $incoming->jedi(OpenSRF::Utils::JSON->perl2JSON($msg_hash)); # meh + $incoming->jedi(OpenSRF::Utils::JSON->perl2JSON($msg_hash)); # jedi-2.0 if ($msg_hash->{purchase_order}) { $logger->info("EDI: processing message for PO " . $msg_hash->{purchase_order}); @@ -189,6 +189,8 @@ sub process_retrieval { $logger->error("EDI: unable to create edi_message " . $e->die_event); next; } + # refresh to pickup create_date, etc. + $incoming = $e->retrieve_acq_edi_message($incoming->id); $e->xact_commit; # since there's a fair chance of unhandled problems @@ -432,10 +434,11 @@ sub process_parsed_msg { my ($class, $account, $incoming, $msg_hash) = @_; if ($incoming->message_type eq 'INVOIC') { - # handle invoice - return; + return $class->create_acq_invoice_from_edi( + $msg_hash, $account->provider, $incoming); } + # ORDRSP for my $li_hash (@{$msg_hash->{lineitems}}) { my $e = new_editor(xact => 1); @@ -573,11 +576,13 @@ sub process_parsed_msg { # process_jedi(). # Return boolean success indicator. sub create_acq_invoice_from_edi { - my ($class, $e, $invoice, $provider, $message) = @_; - # $invoice is O::U::LooseEDI::Message, representing the EDI invoice message. + my ($class, $invoice, $provider, $message) = @_; + # $invoice is O::U::EDIReader hash # $provider is only a pkey # $message is Fieldmapper::acq::edi_message + my $e = new_editor(); + my $log_prefix = "create_acq_invoice_from_edi(..., id . ">): "; @@ -588,20 +593,16 @@ sub create_acq_invoice_from_edi { # distinguish provider and shipper? $eg_inv->recv_method("EDI"); - # Find the buyer's identifier in the invoice. - my $buyer_san; - foreach (@{$invoice->{SG2}}) { - my $nad = $_->{NAD}[0]; - if ($nad->{3035} eq 'BY' and $nad->{C082}{3055} eq '91') { - $buyer_san = $nad->{C082}{3039}; - } - } + my $buyer_san = $invoice->{buyer_san}; if (not $buyer_san) { $logger->error($log_prefix . "could not find buyer SAN in INVOIC"); return 0; } + # some vendors encode the SAN as "$SAN $vendcode" + $buyer_san =~ s/\s.*//g; + # Find the matching org unit based on SAN via 'aoa' table. my $addrs = $e->search_actor_org_address({valid => "t", san => $buyer_san}); @@ -617,130 +618,61 @@ sub create_acq_invoice_from_edi { # XXX Should we verify that this matches PO ordering agency later? $eg_inv->receiver($addrs->[0]->org_unit); - try { - $eg_inv->inv_ident($invoice->{BGM}[0]{1004}); - } catch Error with { + $eg_inv->inv_ident($invoice->{invoice_ident}); + + if (!$eg_inv->inv_ident) { $logger->error( $log_prefix . "no invoice ID # in INVOIC message; " . shift ); - }; - return 0 unless $eg_inv->inv_ident; + return 0; + } my @eg_inv_entries; - # The invoice message will have once instance of segment group 25 - # per lineitem. - foreach my $sg25 (@{ $invoice->{SG25} }) { - # quantity - my $c186 = $sg25->{QTY}[0]{C186}; - my $quantity = $c186->{6060}; - # $c186->{6411} will probably say 'PCE', but need we check it? - - # identifiers (typically ISBN for us, and we may not need these) - my @identifiers = (); - # from LIN... - try { - my $c212 = $sg25->{LIN}[0]{C212}; - push @identifiers, [$c212->{7143}, $c212->{7140}] if - $c212 and ref $c212 eq 'HASH'; - } catch Error with { - # move on - }; - - # from PIA... - try { - foreach my $pia (@{ $sg25->{PIA} }) { - foreach my $h (@{$pia->{C212}}) { - push @identifiers, [$h->{7143}, $h->{7140}]; - } - } - } catch Error with { - # move on - }; - - # @identifiers now contains lists of, say, - # ['IB', '0786222735'], # ISBN 10 - # ['EN','9780786222735'] # ISBN 13 - - # Segment Group 26-47 are all descendants of SG25. + $message->purchase_order($invoice->{purchase_order}); - # Segment Group 26 concerns *lineitem* price (i.e, total for all copies - # on this lineitem). + for my $lineitem (@{$invoice->{lineitems}}) { + my $li_id = $lineitem->{id}; - my $lineitem_price = $sg25->{SG26}[0]{MOA}[0]{C516}{5004}; + if (!$li_id) { + $logger->warn($log_prefix . "no lineitem ID"); + next; + } - # Segment Group 28 concerns *unit* (lineitem detail) price. We may - # not actually use this. TBD. - my $per_unit_price; - foreach my $sg28 (@{$sg25->{SG28}}) { - my $c509 = $sg28->{PRI}[0]{C509}; - my ($price_qualifier, $price_qualifier_type); - ($per_unit_price, $price_qualifier, $price_qualifier_type) = ( - $c509->{5118}, $c509->{5125}, $c509->{5387} - ); + my $li = $e->retrieve_acq_lineitem($li_id); - # price_qualifier=AAA seems to be the price to use. Otherwise, - # take what we can get. - last if $price_qualifier eq 'AAA'; + if (!$li) { + $logger->warn($log_prefix . + "no LI found with ID: $li_id : " . $e->event); + return 0; } - # Segment Group 29 will have references to LI and PO numbers - my $acq_identifiers = {}; - foreach my $sg29 (@{$sg25->{SG29}}) { - foreach my $rff (@{$sg29->{RFF}}) { - my $c506 = $rff->{C506}; - if ($c506->{1153} eq 'ON') { - $acq_identifiers->{po} = $c506->{1154}; - } elsif ($c506->{1153} eq 'LI') { - my ($po, $li) = split m./., $c506->{1154}; - if ($po and $li) { - if ($acq_identifiers->{po}) { - $logger->warn( - $log_prefix . - "RFFs within lineitem disagree on PO # ?" - ) unless $acq_identifiers->{po} eq $po; - } - $acq_identifiers->{li} = $li; - $acq_identifiers->{po} = $po; - } else { - $logger->warn( - $log_prefix . - "RFF 1154 doesn't match expectations (.+/.+) " . - "where 1153 is 'LI'" - ); - } - } - } + my ($quant) = grep {$_->{code} eq '47'} @{$lineitem->{quantities}}; + my $quantity = ($quant) ? $quant->{quantity} : 0; + + if (!$quantity) { + $logger->warn($log_prefix . + "no invoice quantity specified for LI $li_id"); + next; } - if ($acq_identifiers->{po}) { - # First PO number seen in INVOIC sets the purchase_order field for - # the entry in acq.edi_message (which model may need a rethink). + # NOTE: if needed, we also have $lineitem->{net_unit_price} + # and $lineitem->{gross_unit_price} + my $lineitem_price = $lineitem->{amount_billed}; - $message->purchase_order($acq_identifiers->{po}) unless - $message->purchase_order; - } else { - $logger->warn( - $log_prefix . - "SG29 missing or refers to no purchase order that we can tell" - ); - } - if (not $acq_identifiers->{li}) { - $logger->warn( - $log_prefix . - "SG29 missing or refers to no lineitem that we can tell" - ); - } + # if the top-level PO value is unset, get it from the first LI + $message->purchase_order($li->purchase_order) + unless $message->purchase_order; my $eg_inv_entry = Fieldmapper::acq::invoice_entry->new; $eg_inv_entry->inv_item_count($quantity); # XXX Validate by making sure the LI is on-order and belongs to # the right provider and ordering agency and all that. - $eg_inv_entry->lineitem($acq_identifiers->{li}); + $eg_inv_entry->lineitem($li_id); # XXX Do we actually need to link to PO directly here? - $eg_inv_entry->purchase_order($acq_identifiers->{po}); + $eg_inv_entry->purchase_order($li->purchase_order); # This is the total price for all units billed, not per-unit. $eg_inv_entry->cost_billed($lineitem_price); @@ -750,26 +682,41 @@ sub create_acq_invoice_from_edi { my @eg_inv_items; - # Find any taxes applied to the whole invoice. - try { - if ($invoice->{SG50}) { - foreach my $sg50 (@{ $invoice->{SG50} }) { - if ($sg50->{TAX} and $sg50->{MOA}) { - my $tax_amount = $sg50->{MOA}[0]{C516}{5004}; + my %charge_type_map = ( + 'TX' => ['TAX', 'Tax from electronic invoice'], + 'CA' => ['PRO', 'Cataloging services'], + 'DL' => ['SHP', 'Delivery'] + ); - my $eg_inv_item = Fieldmapper::acq::invoice_item->new; - $eg_inv_item->inv_item_type('TAX'); - $eg_inv_item->cost_billed($tax_amount); - # XXX i18n somehow? or maybe omit the note. - $eg_inv_item->note('Tax from electronic invoice'); + for my $charge (@{$invoice->{misc_charges}}) { + my $eg_inv_item = Fieldmapper::acq::invoice_item->new; - push @eg_inv_items, $eg_inv_item; - } - } + my $amount = $charge->{charge_amount}; + + if (!$amount) { + $logger->warn($log_prefix . "charge with no amount"); + next; } - } catch Error with { - # move on - }; + + my $map = $charge_type_map{$charge->{charge_type}}; + + if (!$map) { + $map = [ + 'PRO', + 'Unknown charge type ' . $charge->{charge_type} + ]; + } + + $eg_inv_item->inv_item_type($$map[0]); + $eg_inv_item->note($$map[1]); + $eg_inv_item->cost_billed($amount); + + push @eg_inv_items, $eg_inv_item; + } + + $logger->info($log_prefix . + sprintf("creating invoice with %d entries and %d items.", + scalar(@eg_inv_entries), scalar(@eg_inv_items))); $e->xact_begin;