From d56e34e5814482b983afce87567bdd2dd38dcbdf Mon Sep 17 00:00:00 2001 From: Lebbeous Fogle-Weekley Date: Mon, 23 Jul 2012 11:18:17 -0400 Subject: [PATCH] more Signed-off-by: Lebbeous Fogle-Weekley --- .../edi_translator/data/brodart/1130B766274.jedi | 289 +++++++++ .../data/ingram/Ingram_SAMPLE_EDIFACT_INVOIC.TXT | 1 + .../edi_translator/data/ingram/invoice-munged.edi | 1 + Open-ILS/src/edi_translator/single-invoice.jedi | 358 ++++++++++ .../src/edi_translator/test_invoice_process.pl | 722 +++++++++++++++++++++ 5 files changed, 1371 insertions(+) create mode 100644 Open-ILS/src/edi_translator/data/brodart/1130B766274.jedi create mode 100644 Open-ILS/src/edi_translator/data/ingram/Ingram_SAMPLE_EDIFACT_INVOIC.TXT create mode 100644 Open-ILS/src/edi_translator/data/ingram/invoice-munged.edi create mode 100644 Open-ILS/src/edi_translator/single-invoice.jedi create mode 100644 Open-ILS/src/edi_translator/test_invoice_process.pl diff --git a/Open-ILS/src/edi_translator/data/brodart/1130B766274.jedi b/Open-ILS/src/edi_translator/data/brodart/1130B766274.jedi new file mode 100644 index 0000000000..d1ed502423 --- /dev/null +++ b/Open-ILS/src/edi_translator/data/brodart/1130B766274.jedi @@ -0,0 +1,289 @@ +{ + "recipient_qual": "31B", + "trailer": [ + "UNZ", + { "0020": "1261", "0036": 6 } + ], + "body": [ + { + "INVOIC": [ + [ + "UNH", + { + "S009": { + "0052": "D", "0065": "INVOIC", "0054": "96A", "0051": "UN" + }, + "0062": "1590" + } + ], + [ + "BGM", + { + "1225": "43", + "C002": { "1001": "380" }, + "1004": "B766274" + } + ], + [ + "DTM", + { + "C507": { + "2005": "137", "2380": "20091128", "2379": "102" + } + } + ], + [ + "SG2", + [ + [ + "NAD", + { + "3035": "BY", + "C082": { + "3039": "3618129", "3055": "31B" + } + } + ] + ] + ], + [ + "SG2", + [ + [ + "NAD", + { + "3035": "SU", + "C082": { + "3039": "1697684", "3055": "31B" + } + } + ] + ] + ], + [ + "SG2", + [ + [ + "NAD", + { + "3035": "SU", + "C082": { + "3039": "824016", "3055": "91" + } + } + ] + ] + ], + [ + "SG7", + [ + [ + "CUX", + { + "C504": [ + { "6345": "USD", "6347": "2", "6343": "4" }, + { "6345": "USD", "6347": "3", "6343": "11" } + ] + } + ] + ] + ], + [ + "SG8", + [ + [ + "PAT", + { + "4279": "1", + "C112": { + "2475": "5", "2151": "D", "2152": 30, "2009": "1" + } + } + ], + [ + "DTM", + { + "C507": { + "2005": "13", "2380": "20091228", "2379": "102" + } + } + ] + ] + ], + [ + "SG25", + [ + [ + "LIN", + { + "1082": 1 + } + ], + [ + "PIA", + { + "4347": "5", + "C212": [ + { + "7140": "1741047625", "7143": "IB" + } + ] + } + ], + [ + "PIA", + { + "4347": "5", + "C212": [ + { + "7140": "9781741047622", "7143": "EN" + } + ] + } + ], + [ + "IMD", + { + "C273": { "7008": [ "LONELY PLANET ALASKA" ] }, + "7077": "F", + "7081": "81" + } + ], + [ + "IMD", + { + "C273": { "7008": [ "Benchwick, Greg" ] }, + "7077": "F", + "7081": "80" + } + ], + [ + "QTY", + { + "C186": { "6060": 2, "6063": "47" } + } + ], + [ + "SG26", + [ + [ + "MOA", + { + "C516": { + "5004": 36.06, "6345": "USD", "5025": "203", "6343": "4" + } + } + ] + ] + ], + [ + "SG28", + [ + [ + "PRI", + { + "C509": { + "5125": "INV", "5118": 21.99, "5375": "CA", "5387": "RTP" + } + } + ] + ] + ], + [ + "SG29", + [ + [ + "RFF", + { + "C506": { + "1153": "LI", "1154": "16600/29" + } + } + ] + ] + ] + ] + ], + [ + "UNS", + { + "0081": "S" + } + ], + [ + "CNT", + { + "C270": { + "6069": "1", "6066": 2 + } + } + ], + [ + "CNT", + { + "C270": { + "6069": "2", "6066": 1 + } + } + ], + [ + "SG48", + [ + [ + "MOA", + { + "C516": { + "5004": 42.66, "5025": "86" + } + } + ] + ] + ], + [ + "SG51", + [ + [ + "ALC", + { + "4471": "6", "5463": "C", "C214": { "7161": "C&P" } + } + ], + [ + "MOA", + { + "C516": { "5004": 6.6, "5025": "8" } + } + ] + ] + ], + [ + "UNT", + { + "0074": 25, "0062": "1590" + } + ] + ] + }, + ], + "sender": "1697684", + "header": [ + "UNB", + { + "S003": { "0007": "31B", "0010": "3618129" }, + "S004": { "0019": 700, "0017": 91130 }, + "0020": "1261", + "S001": { "0001": "UNOA", "0002": "4" }, + "S002": { "0007": "31B", "0004": "1697684" } + } + ], + "recipient": "3618129", + "sender_qual": "31B", + "UNA": { + "decimal_sign": ".", + "seg_term": "'", + "esc_char": "?", + "de_sep": "+", + "ce_sep": ":", + "rep_sep": "*" + } +} diff --git a/Open-ILS/src/edi_translator/data/ingram/Ingram_SAMPLE_EDIFACT_INVOIC.TXT b/Open-ILS/src/edi_translator/data/ingram/Ingram_SAMPLE_EDIFACT_INVOIC.TXT new file mode 100644 index 0000000000..141ff80894 --- /dev/null +++ b/Open-ILS/src/edi_translator/data/ingram/Ingram_SAMPLE_EDIFACT_INVOIC.TXT @@ -0,0 +1 @@ +UNB+UNOA:0+1697978:31B+3999999:31B+120224:0108+00000000008486++EANCOM'UNG+INVOIC+1697978:31B+3999999:31B+120224:0108+00000000004281+UN+D:96A'UNH+00000000015684+INVOIC:D:96A:UN:EAN008'DTM+137:20120223:102'BGM+380+60407199+9'DTM+137:20120223:102'NAD+BY+2099999::91'NAD+SU+1697978::31B'RFF+IA:224'LIN+1++9780312362911:EN'PIA+5+0312362919:IB'QTY+47:1:PCE'MOA+203:15.25'PRI+AAA:15.25::DPR'PRI+AAB:27.99::SRP'RFF+ON:15398'RFF+LI:15398/1'ALC+A++++DI'PCD+3:0'LIN+2++9781592537280:EN'PIA+5+1592537286:IB'QTY+47:1:PCE'MOA+203:11.99'PRI+AAA:11.99::DPR'PRI+AAB:19.99::SRP'RFF+ON:15398'RFF+LI:15398/2'ALC+A++++DI'PCD+3:0'LIN+3++9780857684080:EN'PIA+5+0857684086:IB'QTY+47:1:PCE'MOA+203:14.16'PRI+AAA:14.16::DPR'PRI+AAB:25.99::SRP'RFF+ON:15398'RFF+LI:15398/3'ALC+A++++DI'PCD+3:0'LIN+4++9781414339436:EN'PIA+5+1414339437:IB'QTY+47:1:PCE'MOA+203:14.71'PRI+AAA:14.71::DPR'PRI+AAB:26.99::SRP'RFF+ON:15398'RFF+LI:15398/4'ALC+A++++DI'PCD+3:0'LIN+5++9780062004369:EN'PIA+5+0062004360:IB'QTY+47:1:PCE'MOA+203:8.99'PRI+AAA:8.99::DPR'PRI+AAB:14.99::SRP'RFF+ON:15398'RFF+LI:15398/5'ALC+A++++DI'PCD+3:0'LIN+6++9781410440730:EN'PIA+5+1410440737:IB'QTY+47:1:PCE'MOA+203:35.99'PRI+AAA:35.99::DPR'PRI+AAB:35.99::SRP'RFF+ON:15398'RFF+LI:15398/6'ALC+A++++DI'PCD+3:0'LIN+7++9780071668996:EN'PIA+5+0071668993:IB'QTY+47:1:PCE'MOA+203:13.2'PRI+AAA:13.2::DPR'PRI+AAB:22::SRP'RFF+ON:15398'RFF+LI:15398/7'ALC+A++++DI'PCD+3:0'LIN+8++9780892968190:EN'PIA+5+0892968192:IB'QTY+47:1:PCE'MOA+203:14.16'PRI+AAA:14.16::DPR'PRI+AAB:25.99::SRP'RFF+ON:15398'RFF+LI:15398/8'ALC+A++++DI'PCD+3:0'LIN+9++9780307591548:EN'PIA+5+0307591549:IB'QTY+47:1:PCE'MOA+203:13.63'PRI+AAA:13.63::DPR'PRI+AAB:25::SRP'RFF+ON:15398'RFF+LI:15398/9'ALC+A++++DI'PCD+3:0'LIN+10++9781451612066:EN'PIA+5+1451612060:IB'QTY+47:1:PCE'MOA+203:13.08'PRI+AAA:13.08::DPR'PRI+AAB:24::SRP'RFF+ON:15398'RFF+LI:15398/10'ALC+A++++DI'PCD+3:0'LIN+11++9780345524942:EN'PIA+5+0345524942:IB'QTY+47:1:PCE'MOA+203:9'PRI+AAA:9::DPR'PRI+AAB:15::SRP'RFF+ON:15398'RFF+LI:15398/12'ALC+A++++DI'PCD+3:0'LIN+12++9781416535171:EN'PIA+5+1416535179:IB'QTY+47:1:PCE'MOA+203:24.53'PRI+AAA:24.53::DPR'PRI+AAB:45::SRP'RFF+ON:15398'RFF+LI:15398/14'ALC+A++++DI'PCD+3:0'LIN+13++9780758261588:EN'PIA+5+0758261586:IB'QTY+47:1:PCE'MOA+203:9'PRI+AAA:9::DPR'PRI+AAB:15::SRP'RFF+ON:15398'RFF+LI:15398/15'ALC+A++++DI'PCD+3:0'LIN+14++9780393311143:EN'PIA+5+0393311147:IB'QTY+47:1:PCE'MOA+203:9.57'PRI+AAA:9.57::DPR'PRI+AAB:15.95::SRP'RFF+ON:15398'RFF+LI:15398/16'ALC+A++++DI'PCD+3:0'LIN+15++9780451462800:EN'PIA+5+0451462807:IB'QTY+47:1:PCE'MOA+203:4.79'PRI+AAA:4.79::DPR'PRI+AAB:7.99::SRP'RFF+ON:15398'RFF+LI:15398/17'ALC+A++++DI'PCD+3:0'LIN+16++9781410444899:EN'PIA+5+1410444899:IB'QTY+47:1:PCE'MOA+203:35.99'PRI+AAA:35.99::DPR'PRI+AAB:35.99::SRP'RFF+ON:15398'RFF+LI:15398/18'ALC+A++++DI'PCD+3:0'LIN+17++9781402792809:EN'PIA+5+1402792808:IB'QTY+47:1:PCE'MOA+203:13.6'PRI+AAA:13.6::DPR'PRI+AAB:24.95::SRP'RFF+ON:15398'RFF+LI:15398/19'ALC+A++++DI'PCD+3:0'LIN+18++9781118038123:EN'PIA+5+1118038126:IB'QTY+47:1:PCE'MOA+203:16.34'PRI+AAA:16.34::DPR'PRI+AAB:29.99::SRP'RFF+ON:15398'RFF+LI:15398/20'ALC+A++++DI'PCD+3:0'LIN+19++9780307950215:EN'PIA+5+0307950212:IB'QTY+47:2:PCE'MOA+203:16.8'PRI+AAA:8.4::DPR'PRI+AAB:14::SRP'RFF+ON:15398'RFF+LI:15398/21'ALC+A++++DI'PCD+3:0'LIN+20++9780765317582:EN'PIA+5+0765317583:IB'QTY+47:1:PCE'MOA+203:14.16'PRI+AAA:14.16::DPR'PRI+AAB:25.99::SRP'RFF+ON:15398'RFF+LI:15398/13'ALC+A++++DI'PCD+3:0'UNS+S'CNT+2:20'CNT+1:21'MOA+86:317.43'ALC+C+8.49+++ZZZ'MOA+8:8.49'UNT+213+00000000015684'UNE+3+00000000004281'UNZ+1+00000000008486' \ No newline at end of file diff --git a/Open-ILS/src/edi_translator/data/ingram/invoice-munged.edi b/Open-ILS/src/edi_translator/data/ingram/invoice-munged.edi new file mode 100644 index 0000000000..147855bfdf --- /dev/null +++ b/Open-ILS/src/edi_translator/data/ingram/invoice-munged.edi @@ -0,0 +1 @@ +UNB+UNOA:3+1697978:31B+3999999:31B+120224:0108+00000000008486++EANCOM'UNG+INVOIC+1697978:31B+3999999:31B+120224:0108+00000000004281+UN+D:96A'UNH+00000000015684+INVOIC:D:96A:UN:EAN008'DTM+137:20120223:102'BGM+380+60407199+9'DTM+137:20120223:102'NAD+BY+2099999::91'NAD+SU+1697978::31B'RFF+IA:224'LIN+1++9780312362911:EN'PIA+5+0312362919:IB'QTY+47:1:PCE'MOA+203:15.25'PRI+AAA:15.25::DPR'PRI+AAB:27.99::SRP'RFF+ON:15398'RFF+LI:15398/1'ALC+A++++DI'PCD+3:0'LIN+2++9781592537280:EN'PIA+5+1592537286:IB'QTY+47:1:PCE'MOA+203:11.99'PRI+AAA:11.99::DPR'PRI+AAB:19.99::SRP'RFF+ON:15398'RFF+LI:15398/2'ALC+A++++DI'PCD+3:0'LIN+3++9780857684080:EN'PIA+5+0857684086:IB'QTY+47:1:PCE'MOA+203:14.16'PRI+AAA:14.16::DPR'PRI+AAB:25.99::SRP'RFF+ON:15398'RFF+LI:15398/3'ALC+A++++DI'PCD+3:0'LIN+4++9781414339436:EN'PIA+5+1414339437:IB'QTY+47:1:PCE'MOA+203:14.71'PRI+AAA:14.71::DPR'PRI+AAB:26.99::SRP'RFF+ON:15398'RFF+LI:15398/4'ALC+A++++DI'PCD+3:0'LIN+5++9780062004369:EN'PIA+5+0062004360:IB'QTY+47:1:PCE'MOA+203:8.99'PRI+AAA:8.99::DPR'PRI+AAB:14.99::SRP'RFF+ON:15398'RFF+LI:15398/5'ALC+A++++DI'PCD+3:0'LIN+6++9781410440730:EN'PIA+5+1410440737:IB'QTY+47:1:PCE'MOA+203:35.99'PRI+AAA:35.99::DPR'PRI+AAB:35.99::SRP'RFF+ON:15398'RFF+LI:15398/6'ALC+A++++DI'PCD+3:0'LIN+7++9780071668996:EN'PIA+5+0071668993:IB'QTY+47:1:PCE'MOA+203:13.2'PRI+AAA:13.2::DPR'PRI+AAB:22::SRP'RFF+ON:15398'RFF+LI:15398/7'ALC+A++++DI'PCD+3:0'LIN+8++9780892968190:EN'PIA+5+0892968192:IB'QTY+47:1:PCE'MOA+203:14.16'PRI+AAA:14.16::DPR'PRI+AAB:25.99::SRP'RFF+ON:15398'RFF+LI:15398/8'ALC+A++++DI'PCD+3:0'LIN+9++9780307591548:EN'PIA+5+0307591549:IB'QTY+47:1:PCE'MOA+203:13.63'PRI+AAA:13.63::DPR'PRI+AAB:25::SRP'RFF+ON:15398'RFF+LI:15398/9'ALC+A++++DI'PCD+3:0'LIN+10++9781451612066:EN'PIA+5+1451612060:IB'QTY+47:1:PCE'MOA+203:13.08'PRI+AAA:13.08::DPR'PRI+AAB:24::SRP'RFF+ON:15398'RFF+LI:15398/10'ALC+A++++DI'PCD+3:0'LIN+11++9780345524942:EN'PIA+5+0345524942:IB'QTY+47:1:PCE'MOA+203:9'PRI+AAA:9::DPR'PRI+AAB:15::SRP'RFF+ON:15398'RFF+LI:15398/12'ALC+A++++DI'PCD+3:0'LIN+12++9781416535171:EN'PIA+5+1416535179:IB'QTY+47:1:PCE'MOA+203:24.53'PRI+AAA:24.53::DPR'PRI+AAB:45::SRP'RFF+ON:15398'RFF+LI:15398/14'ALC+A++++DI'PCD+3:0'LIN+13++9780758261588:EN'PIA+5+0758261586:IB'QTY+47:1:PCE'MOA+203:9'PRI+AAA:9::DPR'PRI+AAB:15::SRP'RFF+ON:15398'RFF+LI:15398/15'ALC+A++++DI'PCD+3:0'LIN+14++9780393311143:EN'PIA+5+0393311147:IB'QTY+47:1:PCE'MOA+203:9.57'PRI+AAA:9.57::DPR'PRI+AAB:15.95::SRP'RFF+ON:15398'RFF+LI:15398/16'ALC+A++++DI'PCD+3:0'LIN+15++9780451462800:EN'PIA+5+0451462807:IB'QTY+47:1:PCE'MOA+203:4.79'PRI+AAA:4.79::DPR'PRI+AAB:7.99::SRP'RFF+ON:15398'RFF+LI:15398/17'ALC+A++++DI'PCD+3:0'LIN+16++9781410444899:EN'PIA+5+1410444899:IB'QTY+47:1:PCE'MOA+203:35.99'PRI+AAA:35.99::DPR'PRI+AAB:35.99::SRP'RFF+ON:15398'RFF+LI:15398/18'ALC+A++++DI'PCD+3:0'LIN+17++9781402792809:EN'PIA+5+1402792808:IB'QTY+47:1:PCE'MOA+203:13.6'PRI+AAA:13.6::DPR'PRI+AAB:24.95::SRP'RFF+ON:15398'RFF+LI:15398/19'ALC+A++++DI'PCD+3:0'LIN+18++9781118038123:EN'PIA+5+1118038126:IB'QTY+47:1:PCE'MOA+203:16.34'PRI+AAA:16.34::DPR'PRI+AAB:29.99::SRP'RFF+ON:15398'RFF+LI:15398/20'ALC+A++++DI'PCD+3:0'LIN+19++9780307950215:EN'PIA+5+0307950212:IB'QTY+47:2:PCE'MOA+203:16.8'PRI+AAA:8.4::DPR'PRI+AAB:14::SRP'RFF+ON:15398'RFF+LI:15398/21'ALC+A++++DI'PCD+3:0'LIN+20++9780765317582:EN'PIA+5+0765317583:IB'QTY+47:1:PCE'MOA+203:14.16'PRI+AAA:14.16::DPR'PRI+AAB:25.99::SRP'RFF+ON:15398'RFF+LI:15398/13'ALC+A++++DI'PCD+3:0'UNS+S'CNT+2:20'CNT+1:21'MOA+86:317.43'ALC+C+8.49+++ZZZ'MOA+8:8.49'UNT+213+00000000015684'UNE+3+00000000004281'UNZ+1+00000000008486' diff --git a/Open-ILS/src/edi_translator/single-invoice.jedi b/Open-ILS/src/edi_translator/single-invoice.jedi new file mode 100644 index 0000000000..64215e3c21 --- /dev/null +++ b/Open-ILS/src/edi_translator/single-invoice.jedi @@ -0,0 +1,358 @@ +{ + "recipient_qual": "31B", + "trailer": [ + "UNZ", + { + "0020": "1261", + "0036": 6 + } + ], + "body": [ + { + "INVOIC": [ + [ + "UNH", + { + "S009": { + "0052": "D", + "0065": "INVOIC", + "0054": "96A", + "0051": "UN" + }, + "0062": "1590" + } + ], + [ + "BGM", + { + "1225": "43", + "C002": { + "1001": "380" + }, + "1004": "B766274" + } + ], + [ + "DTM", + { + "C507": { + "2005": "137", + "2380": "20091128", + "2379": "102" + } + } + ], + [ + "SG2", + [ + [ + "NAD", + { + "3035": "BY", + "C082": { + "3039": "3618129", + "3055": "31B" + } + } + ] + ] + ], + [ + "SG2", + [ + [ + "NAD", + { + "3035": "SU", + "C082": { + "3039": "1697684", + "3055": "31B" + } + } + ] + ] + ], + [ + "SG2", + [ + [ + "NAD", + { + "3035": "SU", + "C082": { + "3039": "824016", + "3055": "91" + } + } + ] + ] + ], + [ + "SG7", + [ + [ + "CUX", + { + "C504": [ + { + "6345": "USD", + "6347": "2", + "6343": "4" + }, + { + "6345": "USD", + "6347": "3", + "6343": "11" + } + ] + } + ] + ] + ], + [ + "SG8", + [ + [ + "PAT", + { + "4279": "1", + "C112": { + "2475": "5", + "2151": "D", + "2152": 30, + "2009": "1" + } + } + ], + [ + "DTM", + { + "C507": { + "2005": "13", + "2380": "20091228", + "2379": "102" + } + } + ] + ] + ], + [ + "SG25", + [ + [ + "LIN", + { + "1082": 1 + } + ], + [ + "PIA", + { + "4347": "5", + "C212": [ + { + "7140": "1741047625", + "7143": "IB" + } + ] + } + ], + [ + "PIA", + { + "4347": "5", + "C212": [ + { + "7140": "9781741047622", + "7143": "EN" + } + ] + } + ], + [ + "IMD", + { + "C273": { + "7008": [ + "LONELY PLANET ALASKA" + ] + }, + "7077": "F", + "7081": "81" + } + ], + [ + "IMD", + { + "C273": { + "7008": [ + "Benchwick, Greg" + ] + }, + "7077": "F", + "7081": "80" + } + ], + [ + "QTY", + { + "C186": { + "6060": 2, + "6063": "47" + } + } + ], + [ + "SG26", + [ + [ + "MOA", + { + "C516": { + "5004": 36.06, + "6345": "USD", + "5025": "203", + "6343": "4" + } + } + ] + ] + ], + [ + "SG28", + [ + [ + "PRI", + { + "C509": { + "5125": "INV", + "5118": 21.99, + "5375": "CA", + "5387": "RTP" + } + } + ] + ] + ], + [ + "SG29", + [ + [ + "RFF", + { + "C506": { + "1153": "LI", + "1154": "16600/29" + } + } + ] + ] + ] + ] + ], + [ + "UNS", + { + "0081": "S" + } + ], + [ + "CNT", + { + "C270": { + "6069": "1", + "6066": 2 + } + } + ], + [ + "CNT", + { + "C270": { + "6069": "2", + "6066": 1 + } + } + ], + [ + "SG48", + [ + [ + "MOA", + { + "C516": { + "5004": 42.66, + "5025": "86" + } + } + ] + ] + ], + [ + "SG51", + [ + [ + "ALC", + { + "4471": "6", + "5463": "C", + "C214": { + "7161": "C&P" + } + } + ], + [ + "MOA", + { + "C516": { + "5004": 6.6, + "5025": "8" + } + } + ] + ] + ], + [ + "UNT", + { + "0074": 25, + "0062": "1590" + } + ] + ] + }, + ], + "sender": "1697684", + "header": [ + "UNB", + { + "S003": { + "0007": "31B", + "0010": "3618129" + }, + "S004": { + "0019": 700, + "0017": 91130 + }, + "0020": "1261", + "S001": { + "0001": "UNOA", + "0002": "4" + }, + "S002": { + "0007": "31B", + "0004": "1697684" + } + } + ], + "recipient": "3618129", + "sender_qual": "31B", + "UNA": { + "decimal_sign": ".", + "seg_term": "'", + "esc_char": "?", + "de_sep": "+", + "ce_sep": ":", + "rep_sep": "*" + } +} diff --git a/Open-ILS/src/edi_translator/test_invoice_process.pl b/Open-ILS/src/edi_translator/test_invoice_process.pl new file mode 100644 index 0000000000..b2def78f9f --- /dev/null +++ b/Open-ILS/src/edi_translator/test_invoice_process.pl @@ -0,0 +1,722 @@ +use strict; +use warnings; + +require "/openils/bin/oils_header.pl"; + +my %opts = ( +); +use OpenILS::Application::Acq::EDI; + + +use OpenSRF::Utils::Logger qw(:logger); +use OpenSRF::Utils::JSON; + +use OpenILS::Application::Acq::Lineitem; +use OpenILS::Utils::RemoteAccount; +use OpenILS::Utils::CStoreEditor q/new_editor/; +use OpenILS::Utils::Fieldmapper; +use OpenILS::Application::Acq::EDI::Translator; + +use Business::EDI; + +use Data::Dumper; +our $verbose = 1; + +sub new { + my($class, %args) = @_; + my $self = bless(\%args, $class); + # $self->{args} = {}; + return $self; +} + +# our $reasons = {}; # cache for acq.cancel_reason rows ? + +our $translator; + +sub translator { + return $translator ||= OpenILS::Application::Acq::EDI::Translator->new(@_); +} + +my %map = ( + host => 'remote_host', + username => 'remote_user', + password => 'remote_password', + account => 'remote_account', + # in_dir => 'remote_path', # field_map overrides path with in_dir + path => 'remote_path', +); + + +## Just for debugging stuff: +sub add_a_msg { + my ($self, $conn) = @_; + my $e = new_editor(xact=>1); + my $incoming = Fieldmapper::acq::edi_message->new; + $incoming->edi("This is content"); + $incoming->account(1); + $incoming->remote_file('in/some_file.edi'); + $e->create_acq_edi_message($incoming);; + $e->commit; +} +# __PACKAGE__->register_method( method => 'add_a_msg', api_name => 'open-ils.acq.edi.add_a_msg'); # debugging + +__PACKAGE__->register_method( + method => 'retrieve', + api_name => 'open-ils.acq.edi.retrieve', + authoritative => 1, + signature => { + desc => 'Fetch incoming message(s) from EDI accounts. ' . + 'Optional arguments to restrict to one vendor and/or a max number of messages. ' . + 'Note that messages are not parsed or processed here, just fetched and translated.', + params => [ + {desc => 'Authentication token', type => 'string'}, + {desc => 'Vendor ID (undef for "all")', type => 'number'}, + {desc => 'Date Inactive Since', type => 'string'}, + {desc => 'Max Messages Retrieved', type => 'number'} + ], + return => { + desc => 'List of new message IDs (empty if none)', + type => 'array' + } + } +); + +sub retrieve_core { + my ($self, $set, $max, $e, $test) = @_; # $e is a working editor + + $e ||= new_editor(); + $set ||= __PACKAGE__->retrieve_vendors($e); + + my @return = (); + my $vcount = 0; + foreach my $account (@$set) { + my $count = 0; + my $server; + $logger->info("EDI check for vendor " . ++$vcount . " of " . scalar(@$set) . ": " . $account->host); + unless ($server = __PACKAGE__->remote_account($account)) { # assignment, not comparison + $logger->err(sprintf "Failed remote account mapping for %s (%s)", $account->host, $account->id); + next; + }; +# my $rf_starter = './'; # default to current dir + if ($account->in_dir) { + if ($account->in_dir =~ /\*+.*\//) { + $logger->err("EDI in_dir has a slash after an asterisk in value: '" . $account->in_dir . "'. Skipping account with indeterminate target dir!"); + next; + } +# $rf_starter = $account->in_dir; +# $rf_starter =~ s/((\/)?[^\/]*)\*+[^\/]*$//; # kill up to the first (possible) slash before the asterisk: keep the preceeding static dir +# $rf_starter .= '/' if $rf_starter or $2; # recap the dir, or replace leading "/" if there was one (but don't add if empty) + } + my @files = ($server->ls({remote_file => ($account->in_dir || './')})); + my @ok_files = grep {$_ !~ /\/\.?\.$/ and $_ ne '0'} @files; + $logger->info(sprintf "%s of %s files at %s/%s", scalar(@ok_files), scalar(@files), $account->host, $account->in_dir); + # $server->remote_path(undef); + foreach my $remote_file (@ok_files) { + # my $remote_file = $rf_starter . $_; + my $description = sprintf "%s/%s", $account->host, $remote_file; + + # deduplicate vs. acct/filenames already in DB + my $hits = $e->search_acq_edi_message([ + { + account => $account->id, + remote_file => $remote_file, + status => {'in' => [qw/ processed /]}, # if it never got processed, go ahead and get the new one (try again) + # create_time => 'NOW() - 60 DAYS', # if we wanted to allow filenames to be reused after a certain time + # ideally we would also use the date from FTP, but that info isn't available via RemoteAccount + } + # { flesh => 1, flesh_fields => {...}, } + ]); + if (scalar(@$hits)) { + $logger->debug("EDI: $remote_file already retrieved. Skipping"); + warn "EDI: $remote_file already retrieved. Skipping"; + next; + } + + ++$count; + $max and $count > $max and last; + $logger->info(sprintf "%s of %s targets: %s", $count, scalar(@ok_files), $description); + print sprintf "%s of %s targets: %s\n", $count, scalar(@ok_files), $description; + if ($test) { + push @return, "test_$count"; + next; + } + my $content; + my $io = IO::Scalar->new(\$content); + unless ( $server->get({remote_file => $remote_file, local_file => $io}) ) { + $logger->error("(S)FTP get($description) failed"); + next; + } + my $incoming = __PACKAGE__->process_retrieval($content, $remote_file, $server, $account->id, $e); +# $server->delete(remote_file => $_); # delete remote copies of saved message + push @return, $incoming->id; + } + } + return \@return; +} + +# my $in = OpenILS::Application::Acq::EDI->process_retrieval($file_content, $remote_filename, $server, $account_id, $editor); + +sub process_retrieval { + my $incoming = Fieldmapper::acq::edi_message->new; + my ($class, $content, $remote, $server, $account_or_id, $e) = @_; + $content or return; + $e ||= new_editor; + + my $account = __PACKAGE__->record_activity( $account_or_id, $e ); + + my $z; # must predeclare + $z = ( $content =~ s/('UNH\+\d+\+ORDRSP:)0(:96A:UN')/$1D$2/g ) + and $logger->warn("Patching bogus spec reference ORDRSP:0:96A:UN => ORDRSP:D:96A:UN ($z times)"); # Hack/fix some faulty "0" in (B&T) data + + $incoming->remote_file($remote); + $incoming->account($account->id); + $incoming->edi($content); + $incoming->message_type(($content =~ /'UNH\+\d+\+(\S{6}):/) ? $1 : 'ORDRSP'); # cheap sniffing, ORDRSP fallback + __PACKAGE__->attempt_translation($incoming); + $e->xact_begin; + $e->create_acq_edi_message($incoming); + $e->xact_commit; + # refresh: send process_jedi the updated row + $e->xact_begin; + my $outgoing = $e->retrieve_acq_edi_message($incoming->id); # refresh again! + $e->xact_rollback; + my $res = __PACKAGE__->process_jedi($outgoing, $server, $account, $e); + $e->xact_begin; + $outgoing = $e->retrieve_acq_edi_message($incoming->id); # refresh again! + $e->xact_rollback; + $outgoing->status($res ? 'processed' : 'proc_error'); + if ($res) { + $e->xact_begin; + $e->update_acq_edi_message($outgoing); + $e->xact_commit; + } + return $outgoing; +} + +# ->send_core +# $account is a Fieldmapper object for acq.edi_account row +# $messageset is an arrayref with acq.edi_message.id values +# $e is optional editor object +sub send_core { + my ($class, $account, $message_ids, $e) = @_; # $e is a working editor + + ($account and scalar @$message_ids) or return; + $e ||= new_editor(); + + $e->xact_begin; + my @messageset = map {$e->retrieve_acq_edi_message($_)} @$message_ids; + $e->xact_rollback; + my $m_count = scalar(@messageset); + (scalar(@$message_ids) == $m_count) or + $logger->warn(scalar(@$message_ids) - $m_count . " bad IDs passed to send_core (ignored)"); + + my $log_str = sprintf "EDI send to edi_account %s (%s)", $account->id, $account->host; + $logger->info("$log_str: $m_count message(s)"); + $m_count or return; + + my $server; + my $server_error; + unless ($server = __PACKAGE__->remote_account($account, 1)) { # assignment, not comparison + $logger->error("Failed remote account connection for $log_str"); + $server_error = 1; + }; + foreach (@messageset) { + $_ or next; # we already warned about bum ids + my ($res, $error); + if ($server_error) { + $error = "Server error: Failed remote account connection for $log_str"; # already told $logger, this is to update object below + } elsif (! $_->edi) { + $logger->error("Message (id " . $_->id. ") for $log_str has no EDI content"); + $error = "EDI empty!"; + } elsif ($res = $server->put({remote_path => $account->path, content => $_->edi, single_ext => 1})) { + # This is the successful case! + $_->remote_file($res); + $_->status('complete'); + $_->process_time('NOW'); # For outbound files, sending is the end of processing on the EG side. + $logger->info("Sent message (id " . $_->id. ") via $log_str"); + } else { + $logger->error("(S)FTP put to $log_str FAILED: " . ($server->error || 'UNKOWNN')); + $error = "put FAILED: " . ($server->error || 'UNKOWNN'); + } + if ($error) { + $_->error($error); + $_->error_time('NOW'); + } + $logger->info("Calling update_acq_edi_message"); + $e->xact_begin; + unless ($e->update_acq_edi_message($_)) { + $logger->error("EDI send_core update_acq_edi_message failed for message object: " . Dumper($_)); + OpenILS::Application::Acq::EDI::Translator->debug_file(Dumper($_ ), '/tmp/update_acq_edi_message.FAIL'); + OpenILS::Application::Acq::EDI::Translator->debug_file(Dumper($_->to_bare_hash), '/tmp/update_acq_edi_message.FAIL.to_bare_hash'); + } + # There's always an update, even if we failed. + $e->xact_commit; + __PACKAGE__->record_activity($account, $e); # There's always an update, even if we failed. + } + return \@messageset; +} + +# attempt_translation does not touch the DB, just the object. +sub attempt_translation { + my ($class, $edi_message, $to_edi) = @_; + my $tran = translator(); + my $ret = $to_edi ? $tran->json2edi($edi_message->jedi) : $tran->edi2json($edi_message->edi); +# $logger->error("json: " . Dumper($json)); # debugging + if (not $ret or (! ref($ret)) or $ret->is_fault) { # RPC::XML::fault on failure + $edi_message->status('trans_error'); + $edi_message->error_time('NOW'); + my $pre = "EDI Translator " . ($to_edi ? 'json2edi' : 'edi2json') . " failed"; + my $message = ref($ret) ? + ("$pre, Error " . $ret->code . ": " . __PACKAGE__->nice_string($ret->string)) : + ("$pre: " . __PACKAGE__->nice_string($ret) ) ; + $edi_message->error($message); + $logger->error( $message); + return; + } + $edi_message->status('translated'); + $edi_message->translate_time('NOW'); + if ($to_edi) { + $edi_message->edi($ret->value); # translator returns an object + } else { + $edi_message->jedi($ret->value); # translator returns an object + } + return $edi_message; +} + +sub retrieve_vendors { + my ($self, $e, $vendor_id, $last_activity) = @_; # $e is a working editor + + $e ||= new_editor(); + + my $criteria = {'+acqpro' => {active => 't'}}; + $criteria->{'+acqpro'}->{id} = $vendor_id if $vendor_id; + return $e->search_acq_edi_account([ + $criteria, { + 'join' => 'acqpro', + flesh => 1, + flesh_fields => { + acqedi => ['provider'] + } + } + ]); +# {"id":{"!=":null},"+acqpro":{"active":"t"}}, {"join":"acqpro", "flesh_fields":{"acqedi":["provider"]},"flesh":1} +} + +# This is the SRF-exposed call, so it does checkauth + +sub retrieve { + my ($self, $conn, $auth, $vendor_id, $last_activity, $max) = @_; + + my $e = new_editor(authtoken=>$auth); + unless ($e and $e->checkauth()) { + $logger->warn("checkauth failed for authtoken '$auth'"); + return (); + } + # return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $li->purchase_order->ordering_agency); # add permission here ? + + my $set = __PACKAGE__->retrieve_vendors($e, $vendor_id, $last_activity) or return $e->die_event; + return __PACKAGE__->retrieve_core($e, $set, $max); +} + + +# field_map takes the hashref of vendor data with fields from acq.edi_account and +# maps them to the argument style needed for RemoteAccount. It also extrapolates +# data from the remote_host string for type and port, when available. + +sub field_map { + my $self = shift; + my $vendor = shift or return; + my $no_override = @_ ? shift : 0; + my %args = (); + $verbose and $logger->warn("vendor: " . Dumper($vendor)); + foreach (keys %map) { + $args{$map{$_}} = $vendor->$_ if defined $vendor->$_; + } + unless ($no_override) { + $args{remote_path} = $vendor->in_dir; # override "path" with "in_dir" + } + my $host = $args{remote_host} || ''; + ($host =~ s/^(S?FTP)://i and $args{type} = uc($1)) or + ($host =~ s/^(SSH|SCP)://i and $args{type} = 'SCP' ) ; + $host =~ s/:(\d+)$// and $args{port} = $1; + ($args{remote_host} = $host) =~ s#/+##; + $verbose and $logger->warn("field_map: " . Dumper(\%args)); + return %args; +} + + +# The point of remote_account is to get the RemoteAccount object with args from the DB + +sub remote_account { + my ($self, $vendor, $outbound, $e) = @_; + + unless (ref($vendor)) { # It's not a hashref/object. + $vendor or return; # If in fact it's nothing: abort! + # else it's a vendor_id string, so get the full vendor data + $e ||= new_editor(); + my $set_of_one = $self->retrieve_vendors($e, $vendor) or return; + $vendor = shift @$set_of_one; + } + + return OpenILS::Utils::RemoteAccount->new( + $self->field_map($vendor, $outbound) + ); +} + +# takes account ID or account Fieldmapper object + +sub record_activity { + my ($class, $account_or_id, $e) = @_; + $account_or_id or return; + $e ||= new_editor(); + my $account = ref($account_or_id) ? $account_or_id : $e->retrieve_acq_edi_account($account_or_id); + $logger->info("EDI record_activity calling update_acq_edi_account"); + $account->last_activity('NOW') or return; + $e->xact_begin; + $e->update_acq_edi_account($account) or $logger->warn("EDI: in record_activity, update_acq_edi_account FAILED"); + $e->xact_commit; + return $account; +} + +sub nice_string { + my $class = shift; + my $string = shift or return ''; + chomp($string); + my $head = @_ ? shift : 100; + my $tail = @_ ? shift : 25; + (length($string) < $head + $tail) and return $string; + my $h = substr($string,0,$head); + my $t = substr($string, -1*$tail); + $h =~s/\s*$//o; + $t =~s/\s*$//o; + return "$h ... $t"; + # return substr($string,0,$head) . "... " . substr($string, -1*$tail); +} + +sub jedi2perl { + my ($class, $jedi) = @_; + $jedi or return; + my $msg = OpenSRF::Utils::JSON->JSON2perl( $jedi ); + open (FOO, ">>/tmp/JSON2perl_dump.txt"); + print FOO Dumper($msg), "\n\n"; + close FOO; + $logger->warn("Dumped JSON2perl to /tmp/JSON2perl_dump.txt"); + return $msg; +} + +our @datecodes = (35, 359, 17, 191, 69, 76, 75, 79, 85, 74, 84, 223); +our @noop_6063 = (21); + +# ->process_jedi($message, $server, $remote, $e) +# $message is an edi_message object +# +sub process_jedi { + my ($class, $message, $server, $remote, $e) = @_; + $message or return; + $server ||= {}; # context + $remote ||= {}; # context + $e ||= new_editor; + my $jedi; + unless (ref($message) and $jedi = $message->jedi) { # assignment, not comparison + $logger->warn("EDI process_jedi missing required argument (edi_message object with jedi)!"); + return; + } + my $perl = __PACKAGE__->jedi2perl($jedi); + my $error = ''; + if (ref($message) and not $perl) { + $error = ($message->error || '') . " JSON2perl (jedi2perl) FAILED to convert jedi"; + } + elsif (! $perl->{body}) { + $error = "EDI interchange body not found!"; + } + elsif (! $perl->{body}->[0]) { + $error = "EDI interchange body not a populated arrayref!"; + } + if ($error) { + $logger->warn($error); + $message->error($error); + $message->error_time('NOW'); + $e->xact_begin; + $e->update_acq_edi_message($message) or $logger->warn("EDI update_acq_edi_message failed! $!"); + $e->xact_commit; + return; + } + +# Crazy data structure. Most of the arrays will be 1 element... we think. +# JEDI looks like: +# {'body' => [{'ORDERS' => [['UNH',{'0062' => '4635','S009' => {'0057' => 'EAN008','0051' => 'UN','0052' => 'D','0065' => 'ORDERS', ... +# +# So you might access it like: +# $obj->{body}->[0]->{ORDERS}->[0]->[0] eq 'UNH' + + $logger->info("EDI interchange body has " . scalar(@{$perl->{body}}) . " message(s)"); + my @ok_msg_codes = qw/ORDRSP OSTRPT/; + my @messages; + my $i = 0; + foreach my $part (@{$perl->{body}}) { + $i++; + unless (ref $part and scalar keys %$part) { + $logger->warn("EDI interchange message $i lacks structure. Skipping it."); + next; + } + foreach my $key (keys %$part) { + if (! grep {$_ eq $key} @ok_msg_codes) { # We only do one type for now. TODO: other types here + $logger->warn("EDI interchange $i contains unhandled '$key' message. Ignoring it."); + next; + } + my $msg = __PACKAGE__->message_object($part->{$key}) or next; + push @messages, $msg; + + my $bgm = $msg->xpath('BGM') or $logger->warn("EDI No BGM segment found?!"); + my $tag4343 = $msg->xpath('BGM/4343'); + my $tag1225 = $msg->xpath('BGM/1225'); + if (ref $tag4343) { + $logger->info(sprintf "EDI $key BGM/4343 Response Type: %s - %s", $tag4343->value, $tag4343->label) + } else { + $logger->warn("EDI $key BGM/4343 Response Type Code unrecognized"); # next; #? + } + if (ref $tag1225) { + $logger->info(sprintf "EDI $key BGM/1225 Message Function: %s - %s", $tag1225->value, $tag1225->label); + } else { + $logger->warn("EDI $key BGM/1225 Message Function Code unrecognized"); # next; #? + } + + # TODO: currency check, just to be paranoid + # *should* be unnecessary (vendor should reply in currency we send in ORDERS) + # That begs a policy question: how to handle mismatch? convert (bad accuracy), reject, or ignore? I say ignore. + + # ALL those codes below are basically some form of (lastest) delivery date/time + # see, e.g.: http://www.stylusstudio.com/edifact/D04B/2005.htm + # The order is the order of definitiveness (first match wins) + # Note: if/when we do serials via EDI, dates (and ranges/periods) will need massive special handling + my @dates; + my $ddate; + + foreach my $date ($msg->xpath('delivery_schedule')) { + my $val_2005 = $date->xpath_value('DTM/2005') or next; + (grep {$val_2005 eq $_} @datecodes) or next; # no match means some other kind of date we don't care about + push @dates, $date; + } + if (@dates) { + DATECODE: foreach my $dcode (@datecodes) { # now cycle back through hits in order of dcode definitiveness + foreach my $date (@dates) { + $date->xpath_value('DTM/2005') == $dcode or next; + $ddate = $date->xpath_value('DTM/2380') and last DATECODE; + # TODO: conversion based on format specified in DTM/2379 (best encapsulated in Business::EDI) + } + } + } + foreach my $detail ($msg->part('line_detail')) { + my $eg_line = __PACKAGE__->eg_li($detail, $remote, $server->{remote_host}, $e) or next; + my $li_date = $detail->xpath_value('DTM/2380') || $ddate; + my $price = $detail->xpath_value('line_price/PRI/5118') || ''; + $eg_line->expected_recv_time($li_date) if $li_date; + $eg_line->estimated_unit_price($price) if $price; + if (not $message->purchase_order) { # first good lineitem sets the message PO link + $message->purchase_order($eg_line->purchase_order); # EG $message object NOT Business::EDI $msg object + $e->xact_begin; + $e->update_acq_edi_message($message) or $logger->warn("EDI update_acq_edi_message (for PO number) failed! $!"); + $e->xact_commit; + } + # $e->search_acq_edi_account([]); + my $touches = 0; + my $eg_lids = $e->search_acq_lineitem_detail({lineitem => $eg_line->id}); # should be the same as $eg_line->lineitem_details + my $lidcount = scalar(@$eg_lids); + $lidcount == $eg_line->item_count or $logger->warn( + sprintf "EDI: LI %s itemcount (%d) mismatch, %d LIDs found", $eg_line->id, $eg_line->item_count, $lidcount + ); + foreach my $qty ($detail->part('all_QTY')) { + my $ubound = $qty->xpath_value('6060') or next; # nothing to do if qty is 0 + my $val_6063 = $qty->xpath_value('6063'); + $ubound > 0 or next; # don't be crazy! + if (! $val_6063) { + $logger->warn("EDI: Response for LI " . $eg_line->id . " specifies quantity $ubound with no 6063 code! Contact vendor to resolve."); + next; + } + + my $eg_reason = $e->retrieve_acq_cancel_reason(1200 + $val_6063); # DB populated w/ 6063 keys in 1200's + if (! $eg_reason) { + $logger->warn("EDI: Unhandled quantity code '$val_6063' (LI " . $eg_line->id . ") $ubound items unprocessed"); + next; + } elsif (grep {$val_6063 == $_} @noop_6063) { # an FYI like "ordered quantity" + $ubound eq $lidcount + or $logger->warn("EDI: LI " . $eg_line->id . " -- Vendor says we ordered $ubound, but we have $lidcount LIDs!)"); + next; + } + # elsif ($val_6063 == 83) { # backorder + #} elsif ($val_6063 == 85) { # cancel + #} elsif ($val_6063 == 12 or $val_6063 == 57 or $val_6063 == 84 or $val_6063 == 118) { + # despatched, in transit, urgent delivery, or quantity manifested + #} + if ($touches >= $lidcount) { + $logger->warn("EDI: LI " . $eg_line->id . ", We already updated $touches of $lidcount LIDS, " . + "but message wants QTY $ubound more set to " . $eg_reason->label . ". Ignoring!"); + next; + } + $e->xact_begin; + foreach (1 .. $ubound) { + my $eg_lid = shift @$eg_lids or $logger->warn("EDI: Used up all $lidcount LIDs! Ignoring extra status " . $eg_reason->label); + $eg_lid or next; + $logger->debug(sprintf "Updating LID %s to %s", $eg_lid->id, $eg_reason->label); + $eg_lid->cancel_reason($eg_reason->id); + $e->update_acq_lineitem_detail($eg_lid); + $touches++; + } + $e->xact_commit; + if ($ubound == $eg_line->item_count) { + $eg_line->cancel_reason($eg_reason->id); # if ALL the items have the same cancel_reason, the PO gets it too + } + } + $eg_line->edit_time('NOW'); # TODO: have this field automatically updated via ON UPDATE trigger. + $e->xact_begin; + $e->update_acq_lineitem($eg_line) or $logger->warn("EDI: update_acq_lineitem FAILED"); + $e->xact_commit; + # print STDERR "Lineitem update: ", Dumper($eg_line); + } + } + } + return \@messages; +} + +# returns message object if processing should continue +# returns false/undef value if processing should abort + +sub message_object { + my $class = shift; + my $body = shift or return; + my $key = shift if @_; + my $keystring = $key || 'UNSPECIFIED'; + + my $msg = Business::EDI::Message->new($body); + unless ($msg) { + $logger->error("EDI interchange message: $keystring body failed Business::EDI constructor. Skipping it."); + return; + } + $key = $msg->code if ! $key; # Now we set the key for reference if it wasn't specified + my $val_0065 = $msg->xpath_value('UNH/S009/0065') || ''; + unless ($val_0065 eq $key) { + $logger->error("EDI $key UNH/S009/0065 ('$val_0065') conflicts w/ message type $key. Aborting"); + return; + } + my $val_0051 = $msg->xpath_value('UNH/S009/0051') || ''; + unless ($val_0051 eq 'UN') { + $logger->warn("EDI $key UNH/S009/0051 designates '$val_0051', not 'UN' as controlling agency. Attempting to process anyway"); + } + my $val_0054 = $msg->xpath_value('UNH/S009/0054') || ''; + if ($val_0054) { + $logger->info("EDI $key UNH/S009/0054 uses Spec revision version '$val_0054'"); + # Possible Spec Version limitation + # my $yy = $tag_0054 ? substr($val_0054,0,2) : ''; + # unless ($yy eq '00' or $yy > 94 ...) { + # $logger->warn("EDI $key UNH/S009/0051 Spec revision version '$val_0054' not supported"); + # } + } else { + $logger->warn("EDI $key UNH/S009/0054 does not reference a known Spec revision version"); + } + return $msg; +} + +=head2 ->eg_li($lineitem_object, [$remote, $server_log_string, $editor]) + +my $line_item = OpenILS::Application::Acq::EDI->eg_li($edi_line, $remote, "test_server_01", $e); + + $remote is a acq.edi_account Fieldmapper object. + $server_log_string is an arbitrary string use to identify the remote host in potential log messages. + +Updates: + acq.lineitem.estimated_unit_price, + acq.lineitem.state (dependent on mapping codes), + acq.lineitem.expected_recv_time, + acq.lineitem.edit_time (consequently) + +=cut + +sub eg_li { + my ($class, $line, $server, $server_log_string, $e) = @_; + $line or return; + $e ||= new_editor(); + + my $id; + # my $rff = $line->part('line_reference/RFF') or $logger->warn("EDI ORDRSP line_detail/RFF missing!"); + my $val_1153 = $line->xpath_value('line_reference/RFF/1153') || ''; + my $val_1154 = $line->xpath_value('line_reference/RFF/1154') || ''; + my $val_1082 = $line->xpath_value('LIN/1082') || ''; + + my @po_nums; + + $val_1154 =~ s#^(.*)\/##; # Many sources send the ID as 'order_ID/LI_ID' + $1 and push @po_nums, $1; + $val_1082 =~ s#^(.*)\/##; # Many sources send the ID as 'order_ID/LI_ID' + $1 and push @po_nums, $1; + + # TODO: possible check of po_nums + # now do a lot of checking + + if ($val_1153 eq 'LI') { + $id = $val_1154 or $logger->warn("EDI ORDRSP RFF/1154 reference to LI empty. Attempting failover to LIN/1082"); + } else { + $logger->warn("EDI ORDRSP RFF/1153 unexpected value ('$val_1153', not 'LI'). Attempting failover to LIN/1082"); + } + + # FIXME - the line item ID in LIN/1082 ought to match RFF/1154, but + # not all materials vendors obey this. Commenting out check for now + # as being too strict. + #if ($id and $val_1082 and $val_1082 ne $id) { + # $logger->warn("EDI ORDRSP LIN/1082 Line Item ID mismatch ($id vs. $val_1082): cannot target update"); + # return; + #} + + $id ||= $val_1082 || ''; + if ($id eq '') { + $logger->warn('Cannot identify line item from EDI message'); + return; + } + + $logger->info("EDI retrieve/update lineitem $id"); + + my $li = OpenILS::Application::Acq::Lineitem::retrieve_lineitem_impl($e, $id, { + flesh_li_details => 1, + }, 1); # Could send more {options}. The 1 is for no_auth. + + if (! $li or ref($li) ne 'Fieldmapper::acq::lineitem') { + $logger->error("EDI failed to retrieve lineitem by id '$id' for server $server_log_string"); + return; + } + unless ((! $server) or (! $server->provider)) { # but here we want $server to be acq.edi_account instead of RemoteAccount + if ($server->provider != $li->provider) { + # links go both ways: acq.provider.edi_default and acq.edi_account.provider + $logger->info("EDI acct provider (" . $server->provider. ") doesn't match lineitem provider(" + . $li->provider . "). Checking acq.provider.edi_default..."); + my $provider = $e->retrieve_acq_provider($li->provider); + if ($provider->edi_default != $server->id) { + $logger->error(sprintf "EDI provider/acct %s/%s (%s) is blocked from updating lineitem $id belonging to provider/edi_default %s/%s", + $server->provider, $server->id, $server->label, $li->provider, $provider->edi_default); + return; + } + } + } + + my @lin_1229 = $line->xpath('LIN/1229') or $logger->warn("EDI LIN/1229 Action Code missing!"); + my $key = $lin_1229[0] or return; + + my $eg_reason = $e->retrieve_acq_cancel_reason(1000 + $key->value); # DB populated w/ spec keys in 1000's + $eg_reason or $logger->warn(sprintf "EDI LIN/1229 Action Code '%s' (%s) not recognized in acq.cancel_reason", $key->value, $key->label); + $eg_reason or return; + + $li->cancel_reason($eg_reason->id); + unless ($eg_reason->keep_debits) { + $logger->warn("EDI LIN/1229 Action Code '%s' (%s) has keep_debits=0", $key->value, $key->label); + } + + my @prices = $line->xpath_value("line_price/PRI/5118"); + $li->estimated_unit_price($prices[0]) if @prices; + + return $li; +} + +osrf_connect($opts{"config-file"} || $ENV{OSRF_CONFIG} || "/openils/conf/opensrf_core.xml"); + +exit main @ARGV; + +0; + -- 2.11.0