$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});
$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
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);
# 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(..., <acq.edi_message #" .
$message->id . ">): ";
# 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});
# 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);
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;