204 lines
7.1 KiB
Python
204 lines
7.1 KiB
Python
'''
|
|
AS400 Invoice Interface - Jomar to AS400 Invoice Synchronization
|
|
|
|
Created on: 2017-11-20
|
|
@author: Wes
|
|
|
|
PURPOSE:
|
|
Pushes invoice data from Jomar ERP into the AS400 so that invoices can be
|
|
automatically closed on the AS400 side without manual entry.
|
|
|
|
USAGE:
|
|
python AS400_Invoice_Interface.py <date>
|
|
|
|
The date argument (format matching Jomar's RDATLS field) selects which
|
|
invoices to extract. Typically run daily for the current invoice date.
|
|
|
|
PROCESSING LOGIC:
|
|
1. Extract from Jomar (MSSQL via SQLAlchemy):
|
|
- Query joins invoice rolls (txuyre01), roll/tag master (TXRYRT00),
|
|
order header (TXUYUV00), and shipment table (TXUYLS00) to build a
|
|
row set of: invoice number, invoice line, ticket, AS400 order number,
|
|
invoiced yards, finished yards, ship date, and invoice date.
|
|
- Only pulls invoices matching the supplied date.
|
|
- Excludes FREIGHT items and sales return (SR) lines.
|
|
|
|
2. Load into AS400 (pyodbc via iSeries ODBC):
|
|
- Backs up existing rows from STMDATA.JSIINV to WES.JSIINV_BAK
|
|
(only when running against production library).
|
|
- Deletes all current rows from STMDATA.JSIINV (staging table).
|
|
- Inserts each extracted row into STMDATA.JSIINV.
|
|
- Rows missing a ticket number, order number, or with non-numeric
|
|
tickets (e.g. credits) are skipped and collected as exceptions.
|
|
|
|
3. Exception handling:
|
|
- Exception rows are written to a temporary CSV (AII_ERRORS_{date}.csv).
|
|
- CSV is emailed to RECIPIENTS, then deleted from disk.
|
|
|
|
OUTPUT:
|
|
- STMDATA.JSIINV populated on the AS400 for downstream invoice closure.
|
|
- Log file at D:\AS400_Invoice_Interface.log
|
|
- Email alert with exception CSV if any rows were skipped.
|
|
'''
|
|
import pyodbc, time, logging, sys, os, StringIO, csv, sqlite3
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy.orm import sessionmaker
|
|
from FTP.alert import send_alert_html
|
|
|
|
RECIPIENTS = ['admin@example.com', 'user1@example.com']
|
|
SUBJECT = 'AS400 Invoice Inteface Exceptions'
|
|
MESSAGE = None
|
|
|
|
AS400_LIB = 'STMDATA'
|
|
logger = None
|
|
LOG_LEVEL = logging.DEBUG
|
|
|
|
INVOICE = 0
|
|
INVOICE_LINE = 1
|
|
TICKET = 2
|
|
U_NUMBER = 3
|
|
INV_YARDS = 4
|
|
FIN_YARDS =5
|
|
SHIP_DATE = 6
|
|
INV_DATE = 7
|
|
|
|
#engine = create_engine("mssql+pyodbc://jsisbprod", echo=False)
|
|
for row in sqlite3.connect('D:\\FTP\\ftp.db').execute("SELECT * FROM connection_strings WHERE code = 'wfa'"):
|
|
engine = create_engine(row[1], echo=False)
|
|
Base = declarative_base(engine)
|
|
metadata = Base.metadata
|
|
|
|
def initializeLogger(name='log'):
|
|
global logger
|
|
global MESSAGE
|
|
#logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(name)
|
|
if len(logger.handlers) > 0:
|
|
return logger
|
|
logger.setLevel(LOG_LEVEL)
|
|
handler = logging.FileHandler('D:\\' + name + '.log')
|
|
handler.setLevel(LOG_LEVEL)
|
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
handler.setFormatter(formatter)
|
|
logger.addHandler(handler)
|
|
|
|
#Also send logs > DEBUG to stdout
|
|
ch = logging.StreamHandler(sys.stdout)
|
|
ch.setLevel(logging.DEBUG)
|
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
|
ch.setFormatter(formatter)
|
|
logger.addHandler(ch)
|
|
|
|
#Capture log messages in a string for e-mail alert
|
|
MESSAGE = StringIO.StringIO()
|
|
sh = logging.StreamHandler(MESSAGE)
|
|
sh.setLevel(logging.DEBUG)
|
|
sh.setFormatter(formatter)
|
|
logger.addHandler(sh)
|
|
|
|
def loadSession():
|
|
global engine
|
|
Session = sessionmaker(bind=engine)
|
|
session = Session()
|
|
return session
|
|
|
|
def build_extract_query(d):
|
|
q = ("SELECT r1rnr,r1lfn,RIGHT(rtrnr, 5) rtrnr,urbpo,rtilg,rtpglg,lsdat,'***DATE***' as invdte "
|
|
"FROM txuyre01 "
|
|
"LEFT JOIN invoice_rolls ON rtbnr=r1bnr AND rtplt=r1plt AND rtinv=r1rnr AND r1lfn=rtafn "
|
|
"LEFT JOIN TXUYUV00 ON RTALN = UBLN "
|
|
"LEFT JOIN TXUYLS00 ON RTDLN = LSNR "
|
|
"WHERE r1rnr IN (SELECT RRNR FROM TXUYRE00 WHERE RDATLS = '***DATE***') "
|
|
"AND R1ANR NOT LIKE 'FREIGHT' AND R1ART NOT LIKE 'SR'"
|
|
"ORDER BY r1lfn,r1rnr")
|
|
return q.replace('***DATE***',d)
|
|
|
|
def build_insert_statement(r):
|
|
q = 'INSERT INTO ' + AS400_LIB + '.JSIINV VALUES (INVNUM,INVLIN,TICK#,\'ORDERA\',INVYDS,FINYDS,SHPDTE,INVDTE)'
|
|
q = q.replace('INVNUM',r[INVOICE])
|
|
q = q.replace('INVLIN',str(r[INVOICE_LINE]))
|
|
q = q.replace('TICK#',r[TICKET])
|
|
q = q.replace('ORDERA',r[U_NUMBER])
|
|
q = q.replace('INVYDS',str(r[INV_YARDS]))
|
|
q = q.replace('FINYDS',str(r[FIN_YARDS]))
|
|
q = q.replace('SHPDTE',str(r[SHIP_DATE]))
|
|
q = q.replace('INVDTE',str(r[INV_DATE]))
|
|
return q
|
|
|
|
def AII_main(d):
|
|
os.chdir('D:\\')
|
|
initializeLogger('AS400_Invoice_Interface')
|
|
logger.info('Connecting to JSISBPROD...')
|
|
session = loadSession()
|
|
|
|
extract = []
|
|
|
|
logger.info('Running extract query for: %s', d)
|
|
logger.debug(build_extract_query(d))
|
|
for row in session.execute(build_extract_query(d)):
|
|
extract.append(row)
|
|
|
|
logger.debug('Disconnecting from JSISBPROD...')
|
|
session.close()
|
|
|
|
logger.info('Opening connection with AS400...')
|
|
connection = pyodbc.connect(
|
|
driver='{iSeries Access ODBC Driver}',
|
|
system='SUNBURY',
|
|
uid=os.environ['AS400_UID'],
|
|
pwd=os.environ['AS400_PWD'],
|
|
autocommit=True)
|
|
c1 = connection.cursor()
|
|
|
|
if 'STMDATA' in AS400_LIB:
|
|
try:
|
|
logger.info('Backing up rows from STMDATA.JSIINV --> WES.JSIINV_BAK')
|
|
c1.execute('INSERT INTO WES.JSIINV_BAK SELECT * FROM STMDATA.JSIINV')
|
|
except:
|
|
logger.warn('***Failed to backup rows currently in STMDATA.JSIINV***')
|
|
|
|
logger.info('Deleting old rows from %s.JSIINV', AS400_LIB)
|
|
c1.execute('DELETE FROM ' + AS400_LIB + '.JSIINV')
|
|
|
|
exceptions = []
|
|
|
|
logger.info('Inserting rows into %s.JSIINV', AS400_LIB)
|
|
for row in extract:
|
|
# Ticket will be null for credits
|
|
if row[TICKET] and row[U_NUMBER] and row[TICKET].isdigit():
|
|
#print build_insert_statement(row)
|
|
c1.execute(build_insert_statement(row))
|
|
else:
|
|
exceptions.append(row)
|
|
|
|
logger.debug('Disconnecting from AS400...')
|
|
c1.close()
|
|
|
|
if len(exceptions) > 0:
|
|
#Open file for writing
|
|
logger.debug('Dumping exceptions to csv file...')
|
|
fname = "AII_ERRORS_" + d + ".csv"
|
|
logger.debug('Creating %s. Using \'excel\' dialect for csv writer.', fname)
|
|
f = open(fname, 'wb')
|
|
writer = csv.writer(f, dialect='excel')
|
|
writer.writerow(['Invoice #', 'Invoice Line', 'Ticket', 'AS400 Order', 'Invoiced Yards', 'Ship Date', 'Invoice Date'])
|
|
|
|
for row in exceptions:
|
|
writer.writerow(row)
|
|
|
|
f.close()
|
|
|
|
try:
|
|
logger.debug('Sending e-mail...')
|
|
send_alert_html(RECIPIENTS, (SUBJECT + " - " + d), attachments=[fname], message=MESSAGE.getvalue())
|
|
except:
|
|
logger.error('Unable to send e-mail. Is the internet out?')
|
|
|
|
logger.debug('Deleting %s', fname)
|
|
os.remove(fname)
|
|
|
|
if __name__ == '__main__':
|
|
AII_main(sys.argv[1])
|
|
#print build_extract_query(sys.argv[1])
|