From 541392127ead2c796e7de02634e2066772fb4d19 Mon Sep 17 00:00:00 2001 From: Lebbeous Fogle-Weekley Date: Tue, 5 Feb 2013 17:14:16 -0500 Subject: [PATCH] Acq: Fix EDI invoices for ULS, improve troubleshootability 1) Taxes appear in different, but still valid way in ULS invoices than in invoices from other vendors observed to date. 2) Invoices from ULS use MOA 203 to indicate unit price instead of the usual meaning of whole-lineitem price. 3) Now abuse acq.invoice.note to leave better troubleshooting breadcrumbs. Signed-off-by: Lebbeous Fogle-Weekley --- .../perlmods/lib/OpenILS/Application/Acq/EDI.pm | 61 +++++++++++++++++++--- .../src/perlmods/lib/OpenILS/Utils/EDIReader.pm | 46 ++++++++++++++-- 2 files changed, 96 insertions(+), 11 deletions(-) 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 a6b65a856c..4d8edcf5d9 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/EDI.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/EDI.pm @@ -47,6 +47,14 @@ my %map = ( path => 'remote_path', ); +my $VENDOR_KLUDGE_MAP = { + INVOIC => { + amount_billed_is_per_unit => [1699342] + }, + ORDRSP => { + } +}; + __PACKAGE__->register_method( method => 'retrieve', @@ -827,6 +835,19 @@ sub edi_date_to_iso { } +# Return hash with a key for every kludge that should apply for this +# msg_type (INVOIC,ORDRSP) and this vendor SAN. +sub get_kludges { + my ($class, $msg_type, $vendor_san) = @_; + + my @kludges; + while (my ($kludge, $vendors) = each %{$VENDOR_KLUDGE_MAP->{$msg_type}}) { + push @kludges, $kludge if grep { $_ eq $vendor_san } @$vendors; + } + + return map { $_ => 1 } @kludges; +} + # create_acq_invoice_from_edi() does what it sounds like it does for INVOIC # messages. For similar operation on ORDRSP messages, see the guts of # process_jedi(). @@ -842,8 +863,26 @@ sub create_acq_invoice_from_edi { my $log_prefix = "create_acq_invoice_from_edi(..., id . ">): "; + my %msg_kludges; + if ($msg_data->{vendor_san}) { + %msg_kludges = $class->get_kludges('INVOIC', $msg_data->{vendor_san}); + } else { + $logger->warn($log_prefix . "no vendor_san field!"); + } + my $eg_inv = Fieldmapper::acq::invoice->new; + # Some troubleshooting aids. Yeah we should have made appropriate links + # for this in the schema, but this is better than nothing. Probably + # *don't* try to i18n this. + $eg_inv->note("Generated from acq.edi_message #" . $message->id . "."); + if (%msg_kludges) { + $eg_inv->note( + $eg_inv->note . + " Vendor kludges: " . join(", ", keys(%msg_kludges)) . "." + ); + } + $eg_inv->provider($provider); $eg_inv->shipper($provider); # XXX Do we really have a meaningful way to # distinguish provider and shipper? @@ -903,6 +942,8 @@ sub create_acq_invoice_from_edi { # and $lineitem->{gross_unit_price} my $lineitem_price = $lineitem->{amount_billed}; + $lineitem_price *= $quantity if $msg_kludges{amount_billed_is_per_unit}; + # if the top-level PO value is unset, get it from the first LI $message->purchase_order($li->purchase_order) unless $message->purchase_order; @@ -930,6 +971,10 @@ sub create_acq_invoice_from_edi { push @eg_inv_cancel_lis, {lineitem => $li, quantity => $quantity} if $li->cancel_reason; + + # The EDIReader class does detect certain per-lineitem taxes, but + # we'll ignore them for now, as the only sample invoices I've yet seen + # containing them also had a final cumulative tax at the end. } my @eg_inv_items; @@ -937,31 +982,33 @@ sub create_acq_invoice_from_edi { my %charge_type_map = ( 'TX' => ['TAX', 'Tax from electronic invoice'], 'CA' => ['PRO', 'Cataloging services'], - 'DL' => ['SHP', 'Delivery'] - ); + 'DL' => ['SHP', 'Delivery'], + 'GST' => ['TAX', 'Goods and services tax'] + ); # XXX i18n, somehow - for my $charge (@{$msg_data->{misc_charges}}) { + for my $charge (@{$msg_data->{misc_charges}}, @{$msg_data->{taxes}}) { my $eg_inv_item = Fieldmapper::acq::invoice_item->new; - my $amount = $charge->{charge_amount}; + my $amount = $charge->{amount}; if (!$amount) { $logger->warn($log_prefix . "charge with no amount"); next; } - my $map = $charge_type_map{$charge->{charge_type}}; + my $map = $charge_type_map{$charge->{type}}; if (!$map) { $map = [ 'PRO', - 'Unknown charge type ' . $charge->{charge_type} + 'Unknown charge type ' . $charge->{type} ]; } $eg_inv_item->inv_item_type($$map[0]); - $eg_inv_item->note($$map[1]); + $eg_inv_item->title($$map[1]); # title is user-visible; note isn't. $eg_inv_item->cost_billed($amount); + $eg_inv_item->amount_paid($amount); push @eg_inv_items, $eg_inv_item; } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/EDIReader.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/EDIReader.pm index 5a28b8fc25..3a3ecc64fb 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Utils/EDIReader.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/EDIReader.pm @@ -17,6 +17,7 @@ use strict; use warnings; my $NEW_MSG_RE = '^UNH'; # starts a new message my $NEW_LIN_RE = '^LIN'; # starts a new line item +my $END_ALL_LIN = '^UNS'; # no more lineitems after this my %edi_fields = ( message_type => qr/^UNH\+[A-z0-9]+\+(\S{6})/, @@ -54,8 +55,15 @@ my %edi_li_quant_fields = ( ); my %edi_charge_fields = ( - charge_type => qr/^ALC\+C\++([^\+]+)/, - charge_amount => qr/^MOA\+(?:8|131|304):([^:]+)/ + type => qr/^ALC\+C\++([^\+]+)/, + amount => qr/^MOA\+(?:8|131|304):([^:]+)/ +); + +# This may need to be liberalized later, but it works for the only example I +# have so far. +my %edi_tax_fields = ( + type => qr/^TAX\+7\+([^\+]+)/, + amount => qr/^MOA\+124:([^:]+)/ ); sub new { @@ -92,7 +100,7 @@ sub read { # - starting a new message if (/$NEW_MSG_RE/) { - $msg = {lineitems => [], misc_charges => []}; + $msg = {lineitems => [], misc_charges => [], taxes => []}; push(@msgs, $msg); } @@ -139,7 +147,7 @@ sub read { # - starting a new misc. charge - if (/$edi_charge_fields{charge_type}/) { + if (/$edi_charge_fields{type}/) { $msg->{_current_charge} = {}; push (@{$msg->{misc_charges}}, $msg->{_current_charge}); } @@ -152,6 +160,36 @@ sub read { if /$edi_charge_fields{$field}/; } } + + # - starting a new tax charge. Taxes wind up on current lineitem if + # any, otherwise in the top-level taxes array + + if (/$edi_tax_fields{type}/) { + $msg->{_current_tax} = {}; + if ($msg->{_current_li}) { + $msg->{_current_li}{tax} = $msg->{_current_tax} + } else { + push (@{$msg->{taxes}}, $msg->{_current_tax}); + } + } + + # - extract tax field + + if (my $tax = $msg->{_current_tax}) { + for my $field (keys %edi_tax_fields) { + ($tax->{$field}) = $_ =~ /$edi_tax_fields{$field}/ + if /$edi_tax_fields{$field}/; + } + } + + # This helps avoid associating taxes and charges at the end of the + # message with the final lineitem inapporiately. + if (/$END_ALL_LIN/) { + # remove the state-maintenance keys + foreach (grep /^_/, keys %$msg) { + delete $msg->{$_}; + } + } } # remove the state-maintenance keys -- 2.11.0