1 # ---------------------------------------------------------------
2 # Copyright (C) 2012 Equinox Software, Inc
3 # Author: Bill Erickson <berickr@esilibrary.com>
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
15 package OpenILS::Utils::EDIReader;
16 use strict; use warnings;
18 my $NEW_MSG_RE = '^UNH'; # starts a new message
19 my $NEW_LIN_RE = '^LIN'; # starts a new line item
20 my $END_ALL_LIN = '^UNS'; # no more lineitems after this
23 message_type => qr/^UNH\+[A-z0-9]+\+(\S{6})/,
24 buyer_san => qr/^NAD\+BY\+([^:]+)::31B/,
25 buyer_acct => qr/^NAD\+BY\+([^:]+)::91/,
26 buyer_ident => qr/^NAD\+BY\+([^:]+)::9$/, # alternate SAN
27 buyer_code => qr/^RFF\+API:(\S+)/,
28 vendor_san => qr/^NAD\+SU\+([^:]+)::31B/,
29 vendor_acct => qr/^NAD\+SU\+([^:]+)::91/,
30 vendor_ident => qr/^NAD\+SU\+([^:]+)::9$/, # alternate SAN
31 purchase_order => qr/^RFF\+ON:(\S+)/,
32 invoice_ident => qr/^BGM\+380\+([^\+]+)/,
33 total_billed => qr/^MOA\+86:([^:]+)/,
34 invoice_date => qr/^DTM\+137:([^:]+)/, # This is really "messge date"
35 # We don't retain a top-level container code -- they can repeat.
36 _container_code => qr/^GIN\+BJ\+([^:]+)/,
37 _container_code_alt => qr/^PCI\+33E\+([^:]+)/,
38 lading_number => qr/^RFF\+BM:([^:]+)/
42 id => qr/^RFF\+LI:(?:[^\/]+\/)?(\d+)/,
43 index => qr/^LIN\+([^\+]+)/,
44 amount_billed => qr/^MOA\+203:([^:]+)/,
45 net_unit_price => qr/^PRI\+AAA:([^:]+)/,
46 gross_unit_price=> qr/^PRI\+AAB:([^:]+)/,
47 expected_date => qr/^DTM\+44:([^:]+)/,
48 avail_status => qr/^FTX\+LIN\++([^:]+):8B:28/,
49 # "1B" codes are deprecated, but still in use.
50 # Pretend it's "12B" and it should just work
51 order_status => qr/^FTX\+LIN\++([^:]+):12?B:28/,
52 # DESADV messages have multiple PO ID's, one RFF+ON per LIN.
53 purchase_order => qr/^RFF\+ON:(\S+)/
56 my %edi_li_ident_fields = (
57 ident => qr/^LIN\+\S+\++([^:]+):?(\S+)?/,
58 ident2 => qr/^PIA\+0*5\+([^:]+):?(\S+)?/,
61 my %edi_li_quant_fields = (
62 code => qr/^QTY\+(\d+):/,
63 quantity => qr/^QTY\+\d+:(\d+)/
66 my %edi_charge_fields = (
67 type => qr/^ALC\+C\++([^\+]+)/,
68 amount => qr/^MOA\+(?:8|131|304):([^:]+)/
71 # This may need to be liberalized later, but it works for the only example I
73 my %edi_tax_fields = (
74 type => qr/^TAX\+7\+([^\+]+)/,
75 amount => qr/^MOA\+124:([^:]+)/
79 return bless({}, shift());
87 open(EDI_FILE, $file) or die "Cannot open $file: $!\n";
88 my $edi = join('', <EDI_FILE>);
91 return $self->read($edi);
94 # Reads an EDI string and parses the package one "line" at a time, extracting
95 # needed information via regular expressions. Returns an array of messages,
96 # each represented as a hash. See %edi_*fields above for lists of which fields
97 # may be present within a message.
101 my $edi = shift or return [];
106 foreach (split(/'/, $edi)) {
109 # - starting a new message
112 $msg = {lineitems => [], misc_charges => [], taxes => []};
116 # extract top-level message fields
120 for my $field (keys %edi_fields) {
121 ($msg->{$field}) = $_ =~ /$edi_fields{$field}/
122 if /$edi_fields{$field}/;
125 # - starting a new lineitem
128 $msg->{_current_li} = {};
130 # In DESADV messages there may be multiple container codes
131 # per message. They precede the lineitems contained within
132 # each container. Instead of restructuring the messages to
133 # be containers of lineitems, just tag each lineitem with
134 # its container if one is specified.
135 my $ccode = $msg->{_container_code} || $msg->{_container_code_alt};
136 $msg->{_current_li}->{container_code} = $ccode if $ccode;
138 push(@{$msg->{lineitems}}, $msg->{_current_li});
141 # - extract lineitem fields
143 if (my $li = $msg->{_current_li}) {
145 for my $field (keys %edi_li_fields) {
146 ($li->{$field}) = $_ =~ /$edi_li_fields{$field}/g
147 if /$edi_li_fields{$field}/;
150 for my $field (keys %edi_li_ident_fields) {
151 if (/$edi_li_ident_fields{$field}/) {
152 my ($ident, $type) = $_ =~ /$edi_li_ident_fields{$field}/;
153 push(@{$li->{identifiers}}, {code => $type, value => $ident});
157 if (/$edi_li_quant_fields{quantity}/) {
159 ($quant->{quantity}) = $_ =~ /$edi_li_quant_fields{quantity}/;
160 ($quant->{code}) = $_ =~ /$edi_li_quant_fields{code}/;
161 push(@{$li->{quantities}}, $quant);
166 # - starting a new misc. charge
168 if (/$edi_charge_fields{type}/) {
169 $msg->{_current_charge} = {};
170 push (@{$msg->{misc_charges}}, $msg->{_current_charge});
173 # - extract charge fields
175 if (my $charge = $msg->{_current_charge}) {
176 for my $field (keys %edi_charge_fields) {
177 ($charge->{$field}) = $_ =~ /$edi_charge_fields{$field}/
178 if /$edi_charge_fields{$field}/;
182 # - starting a new tax charge. Taxes wind up on current lineitem if
183 # any, otherwise in the top-level taxes array
185 if (/$edi_tax_fields{type}/) {
186 $msg->{_current_tax} = {};
187 if ($msg->{_current_li}) {
188 $msg->{_current_li}{tax} = $msg->{_current_tax}
190 push (@{$msg->{taxes}}, $msg->{_current_tax});
194 # - extract tax field
196 if (my $tax = $msg->{_current_tax}) {
197 for my $field (keys %edi_tax_fields) {
198 ($tax->{$field}) = $_ =~ /$edi_tax_fields{$field}/
199 if /$edi_tax_fields{$field}/;
203 # This helps avoid associating taxes and charges at the end of the
204 # message with the final lineitem inapporiately.
205 if (/$END_ALL_LIN/) {
206 # remove the state-maintenance keys
207 foreach (grep /^_/, keys %$msg) {
213 # remove the state-maintenance keys
214 for my $msg (@msgs) {
215 foreach (grep /^_/, keys %$msg) {