]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Utils/EDIReader.pm
LP#1713138 EDI buyer codes from RFF+API
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Utils / EDIReader.pm
1 # ---------------------------------------------------------------
2 # Copyright (C) 2012 Equinox Software, Inc
3 # Author: Bill Erickson <berickr@esilibrary.com>
4 #
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.
9
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;
17
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
21
22 my %edi_fields = (
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_code      => qr/^RFF\+API:(\S+)/,
27     vendor_san      => qr/^NAD\+SU\+([^:]+)::31B/,
28     vendor_acct     => qr/^NAD\+SU\+([^:]+)::91/,
29     purchase_order  => qr/^RFF\+ON:(\S+)/,
30     invoice_ident   => qr/^BGM\+380\+([^\+]+)/,
31     total_billed    => qr/^MOA\+86:([^:]+)/,
32     invoice_date    => qr/^DTM\+137:([^:]+)/
33 );
34
35 my %edi_li_fields = (
36     id      => qr/^RFF\+LI:(?:[^\/]+\/)?(\d+)/,
37     index   => qr/^LIN\+([^\+]+)/,
38     amount_billed   => qr/^MOA\+203:([^:]+)/,
39     net_unit_price  => qr/^PRI\+AAA:([^:]+)/,
40     gross_unit_price=> qr/^PRI\+AAB:([^:]+)/,
41     expected_date   => qr/^DTM\+44:([^:]+)/,
42     avail_status    => qr/^FTX\+LIN\++([^:]+):8B:28/,
43     # "1B" codes are deprecated, but still in use.  
44     # Pretend it's "12B" and it should just work
45     order_status    => qr/^FTX\+LIN\++([^:]+):12?B:28/
46 );
47
48 my %edi_li_ident_fields = (
49     ident  => qr/^LIN\+\S+\++([^:]+):?(\S+)?/,
50     ident2 => qr/^PIA\+0*5\+([^:]+):?(\S+)?/, 
51 );
52
53 my %edi_li_quant_fields = (
54     code     => qr/^QTY\+(\d+):/,
55     quantity => qr/^QTY\+\d+:(\d+)/
56 );
57
58 my %edi_charge_fields = (
59     type   => qr/^ALC\+C\++([^\+]+)/,
60     amount => qr/^MOA\+(?:8|131|304):([^:]+)/
61 );
62
63 # This may need to be liberalized later, but it works for the only example I
64 # have so far.
65 my %edi_tax_fields = (
66     type   => qr/^TAX\+7\+([^\+]+)/,
67     amount => qr/^MOA\+124:([^:]+)/
68 );
69
70 sub new {
71     return bless({}, shift());
72 }
73
74 # see read()
75 sub read_file {
76     my $self = shift;
77     my $file = shift;
78
79     open(EDI_FILE, $file) or die "Cannot open $file: $!\n";
80     my $edi = join('', <EDI_FILE>);
81     close EDI_FILE;
82
83     return $self->read($edi);
84 }
85
86 # Reads an EDI string and parses the package one "line" at a time, extracting 
87 # needed information via regular expressions.  Returns an array of messages, 
88 # each represented as a hash.  See %edi_*fields above for lists of which fields 
89 # may be present within a message.
90
91 sub read {
92     my $self = shift;
93     my $edi = shift or return [];
94     my @msgs;
95
96     $edi =~ s/\n//og;
97
98     foreach (split(/'/, $edi)) {
99         my $msg = $msgs[-1];
100
101         # - starting a new message
102
103         if (/$NEW_MSG_RE/) { 
104             $msg = {lineitems => [], misc_charges => [], taxes => []};
105             push(@msgs, $msg);
106         }
107
108         # extract top-level message fields
109
110         next unless $msg;
111
112         for my $field (keys %edi_fields) {
113             ($msg->{$field}) = $_ =~ /$edi_fields{$field}/
114                 if /$edi_fields{$field}/;
115         }
116
117         # - starting a new lineitem
118
119         if (/$NEW_LIN_RE/) {
120             $msg->{_current_li} = {};
121             push(@{$msg->{lineitems}}, $msg->{_current_li});
122         }
123
124         # - extract lineitem fields
125
126         if (my $li = $msg->{_current_li}) {
127
128             for my $field (keys %edi_li_fields) {
129                 ($li->{$field}) = $_ =~ /$edi_li_fields{$field}/g
130                     if /$edi_li_fields{$field}/;
131             }
132
133             for my $field (keys %edi_li_ident_fields) {
134                 if (/$edi_li_ident_fields{$field}/) {
135                     my ($ident, $type) = $_ =~ /$edi_li_ident_fields{$field}/;
136                     push(@{$li->{identifiers}}, {code => $type, value => $ident});
137                 }
138             }
139
140             if (/$edi_li_quant_fields{quantity}/) {
141                 my $quant = {};
142                 ($quant->{quantity}) = $_ =~ /$edi_li_quant_fields{quantity}/;
143                 ($quant->{code}) = $_ =~ /$edi_li_quant_fields{code}/;
144                 push(@{$li->{quantities}}, $quant);
145             }
146
147         }
148
149         # - starting a new misc. charge
150
151         if (/$edi_charge_fields{type}/) {
152             $msg->{_current_charge} = {};
153             push (@{$msg->{misc_charges}}, $msg->{_current_charge});
154         }
155
156         # - extract charge fields
157
158         if (my $charge = $msg->{_current_charge}) {
159             for my $field (keys %edi_charge_fields) {
160                 ($charge->{$field}) = $_ =~ /$edi_charge_fields{$field}/
161                     if /$edi_charge_fields{$field}/;
162             }
163         }
164
165         # - starting a new tax charge.  Taxes wind up on current lineitem if
166         # any, otherwise in the top-level taxes array
167
168         if (/$edi_tax_fields{type}/) {
169             $msg->{_current_tax} = {};
170             if ($msg->{_current_li}) {
171                 $msg->{_current_li}{tax} = $msg->{_current_tax}
172             } else {
173                 push (@{$msg->{taxes}}, $msg->{_current_tax});
174             }
175         }
176
177         # - extract tax field
178
179         if (my $tax = $msg->{_current_tax}) {
180             for my $field (keys %edi_tax_fields) {
181                 ($tax->{$field}) = $_ =~ /$edi_tax_fields{$field}/
182                     if /$edi_tax_fields{$field}/;
183             }
184         }
185
186         # This helps avoid associating taxes and charges at the end of the
187         # message with the final lineitem inapporiately.
188         if (/$END_ALL_LIN/) {
189             # remove the state-maintenance keys
190             foreach (grep /^_/, keys %$msg) {
191                 delete $msg->{$_};
192             }
193         }
194     }
195
196     # remove the state-maintenance keys
197     for my $msg (@msgs) {
198         foreach (grep /^_/, keys %$msg) {
199             delete $msg->{$_};
200         }
201     }
202
203     return \@msgs;
204 }