''' Finished Goods Interface - AS400 to Jomar Inventory Synchronization Created on Jun 24, 2015 @author: Wes PURPOSE: Reads CSV files generated by the AS400 when inventory is scanned in shipping and creates corresponding Jomar ERP inventory transactions to keep the two systems in sync. INPUT: CSV files in D:\INTERFACE\ named as400_jsi_#####.csv (where ##### is a sequential transaction number). Each row represents a roll/piece with columns: [0] ORDER - AS400 order number (e.g. U17435) [1] LINE - AS400 line number [2] TICKET - Roll/piece ticket number (unique identifier) [3] FINISHED_YARDS [4] INVOICED_YARDS [5] BIN - Physical bin location [6] WAREHOUSE - Warehouse code (SH, BG, B8, etc.) [7] WEIGHT - Roll weight in lbs [8] SPAT - Pattern code [9] SCOL - Color code Can be run two ways: - No arguments: processes all CSV files in PATH - Single file path argument: processes just that file (preferred when using the PowerShell file watcher to avoid collisions between concurrent runs) PROCESSING LOGIC (per row): 1. Validation checks (skip row to error list if any fail): - Duplicate ticket within same transmission - Invalid bin (HLD, or blank bin in SH warehouse) - Finished yards <= 0 - Invoiced yards <= 0 2. Order/line lookup in Jomar: - Query TXUYUV00 (order header) to map AS400 order -> Jomar order number - Query TXUYUF01 (order detail) + TXMYAT03 (item info) to find Jomar line number - Verify pattern and color match between AS400 and Jomar 3. Transaction generation based on roll state in Jomar (TXRYRT00 roll/tag master): a. Roll does NOT exist in Jomar: -> Create Production Receipt (08) transaction to add the roll -> Sets warehouse/bin, yardage, weight, lot, NAFTA qualification -> Pull orders (PULL_ORDERS list) get no order/line linkage b. Roll ALREADY exists in Jomar: - If shipped yards > 0: error (roll already shipped, can't modify) - If yardage or weight differs from current values: -> Create adjustment Production Receipt (08) with the delta - If bin or warehouse differs: -> Create Movement (06) transaction to relocate the roll - If nothing differs: skip (log and ignore) All transactions are written to TXRYBW10 (the finished goods interface table) and processed immediately by Jomar. ERROR HANDLING: - Rows that fail validation or lookup are collected in eRows - Error rows are written to D:\INTERFACE\ERRORS\ERRORS_{lot}.csv - Error CSV is emailed to ROLL_ERROR_RECIPIENTS - If a file with a lower transaction number is still unprocessed when a newer file arrives, the script alerts TX_ERROR_RECIPIENTS and exits (prevents out-of-order processing) OUTPUT: - Processed CSV files are moved to D:\INTERFACE\PROCESSED\ - Log file written to D:\INTERFACE\Finished_Goods_Interface.log TEST_MODE (when True): - Prevents files being moved to the PROCESSED folder - Suppresses email notifications - Rolls back all database transactions instead of committing ''' import ConfigParser import csv, os, smtplib from email import Encoders from email.MIMEBase import MIMEBase from email.MIMEImage import MIMEImage from email.MIMEMultipart import MIMEMultipart from email.MIMEText import MIMEText from email.Utils import COMMASPACE, formatdate from glob import glob import logging from operator import itemgetter import sys, sqlite3, re from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker TEST_MODE = False LOG_LEVEL = logging.DEBUG PATH = 'D:\\INTERFACE\\' PULL_ORDERS = ['U17435', 'U32614', 'U37151', 'U38029', 'U39702', 'U38664', 'U30473', 'U61478', 'U64631', 'U74634', 'U89496'] IN_FILE_FORMAT = "[\S\s]+as400_jsi_(?P[\d][\d][\d][\d][\d]).csv" ROLL_ERROR_RECIPIENTS = ['user1@example.com', 'user2@example.com'] TX_ERROR_RECIPIENTS = ['admin@example.com', 'user1@example.com'] ORDER = 0 LINE = 1 TICKET = 2 FINISHED_YARDS = 3 INVOICED_YARDS = 4 BIN = 5 WAREHOUSE = 6 WEIGHT = 7 SPAT = 8 SCOL = 9 #This script must be run as SUNBURYTEXTILE\Administrator #engine = create_engine("mssql+pyodbc://jsisbprod", echo=False) engine = create_engine("mssql+pyodbc://{}:{}@jsisbprod".format( os.environ['JOMAR_DB_UID'], os.environ['JOMAR_DB_PWD']), echo=False) Base = declarative_base(engine) metadata = Base.metadata class TXRYBW10(Base): __tablename__ = 'TXRYBW10' #Finished good interface table __table_args__ = {'autoload':True} class TXUYUV00(Base): __tablename__ = 'TXUYUV00' #Order Header __table_args__ = {'autoload':True} class TXUYUF01(Base): __tablename__ = 'TXUYUF01' #Order Detail __table_args__ = {'autoload':True} class TXRYRT00(Base): __tablename__ = 'TXRYRT00' #Roll/tag master __table_args__ = {'autoload':True} class ItemInfo(Base): __tablename__ = 'TXMYAT03' __table_args__ = {'autoload':True} ''' MODIFIED - 01/04/2018 - SB-20 This table is refreshed from the AS400 every hour. Need this to determine NAFTA qualification ''' class HTSCDSFL(Base): __tablename__ = 'as400_HTSCDSFL' __table_args__ = {'autoload':True} def load_email_credentials(): vault = sqlite3.connect('D:\\FTP\\ftp.db') cursor = vault.execute("SELECT * from accounts where code = 'DNR'") for row in cursor: return [row[1], row[2]] ''' I found the bulk of this function already written on someone's blog for sending mail via outlook 365. I made a few modifications for our purposes. ''' def send_mail(send_from, send_to, subject, text, files=None, data_attachments=None, server="smtp.example.com", port=25, tls=False, html=False, images=None, username=None, password=None, config_file=None, config=None): if files is None: files = [] if images is None: images = [] if data_attachments is None: data_attachments = [] if config_file is not None: config = ConfigParser.ConfigParser() config.read(config_file) if config is not None: server = config.get('smtp', 'server') port = config.get('smtp', 'port') tls = config.get('smtp', 'tls').lower() in ('true', 'yes', 'y') username = config.get('smtp', 'username') password = config.get('smtp', 'password') msg = MIMEMultipart('related') msg['From'] = send_from msg['To'] = send_to if isinstance(send_to, basestring) else COMMASPACE.join(send_to) msg['Date'] = formatdate(localtime=True) msg['Subject'] = subject msg.attach( MIMEText(text, 'html' if html else 'plain') ) for f in files: part = MIMEBase('application', "octet-stream") part.set_payload( open(f,"rb").read() ) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f)) msg.attach(part) for f in data_attachments: part = MIMEBase('application', "octet-stream") part.set_payload( f['data'] ) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="%s"' % f['filename']) msg.attach(part) for (n, i) in enumerate(images): fp = open(i, 'rb') msgImage = MIMEImage(fp.read()) fp.close() msgImage.add_header('Content-ID', ''.format(str(n+1))) msg.attach(msgImage) smtp = smtplib.SMTP(server, int(port)) if tls: smtp.starttls() #if username is not None: # smtp.login(username, password) smtp.sendmail(send_from, send_to, msg.as_string()) smtp.close() ''' Opens connection to database returning a session object. ''' def loadSession(): global engine Session = sessionmaker(bind=engine) session = Session() return session def initializeLogger(name='log'): global logger logger = logging.getLogger(__name__) logger.setLevel(LOG_LEVEL) handler = logging.FileHandler('D:\\INTERFACE\\' + name + '.log') handler.setLevel(LOG_LEVEL) formatter = logging.Formatter('%(asctime)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.INFO) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) logger.addHandler(ch) ''' MODIFIED - 01/04/2018 - SB-20 This function is the brains of the operation regarding the NAFTA changes. Simple lookup ''' def NAFTA(session, pattern, color): logger.debug('Attempting lookup in HTSCDSFL to determine NAFTA qualification') logger.debug('SELECT * FROM as400_HTSCDSFL WHERE as400_HTSCDSFL.HTSPAT = \'%s\' AND as400_HTSCDSFL.HTSCLR = \'%s\';', pattern, color) p = session.query(HTSCDSFL).filter(HTSCDSFL.HTSPAT == pattern, HTSCDSFL.HTSCLR == color) for r in p: logger.debug('Record found in HTSCDSFL. Value of HTSNQC is \'%s\'', r.HTSNQC) if r.HTSNQC == 'X': return 'Y' else: return 'N' logger.debug('No record found in HTSCDSFL!') return 'N' if __name__ == '__main__': os.chdir('D:\INTERFACE') initializeLogger('Finished_Goods_Interface') logger.info('********************************************************************************') if(len(sys.argv) < 2): logger.info('No args supplied, processing all files in folder...') logger.debug('Obtaining list of CSV files in ' + PATH + '\\') files = glob(PATH + '\*.csv') # Verify file name and check for any previous transactions hung up in folder elif re.match(IN_FILE_FORMAT, sys.argv[1], re.IGNORECASE): logger.info('Processing single file: ' + sys.argv[1]) files = [sys.argv[1]] in_file_tx = int(re.match(IN_FILE_FORMAT, sys.argv[1], re.IGNORECASE).group('txnum')) #parse transaction num from file name and cast to int ls = glob(PATH + '\*.csv') for f in ls: test = int(re.match(IN_FILE_FORMAT, f, re.IGNORECASE).group('txnum')) if test < (in_file_tx - 1): logger.error('Trying to process: %s but %s is still in folder.', sys.argv[1], f) try: if not TEST_MODE: logger.info('Alerting relevant people to this situation...') msg = 'For whatever reason ' + f + ' has not finished processing before ' + sys.argv[1] + ' was submitted.' dnr_account = load_email_credentials() send_mail('donotreply@example.com', TX_ERROR_RECIPIENTS, 'TRANSACTION HUNG UP IN FGI!', msg, username=dnr_account[0], password=dnr_account[1], html=True) except: logger.error('Could not send e-mail. Check internet connection.') logger.error('THERE ARE OLDER TRANSACTIONS THAT HAVE NOT PROCESSED!! Exting...') sys.exit() # Invalid file specified. print error msg and exit. else: logger.error('File name: %s does not conform to as400_jsi_#####.csv', sys.argv[1]) sys.exit() session = loadSession() ''' Process each CSV file as400_jsi_00001.csv ''' for f in files: if not('as400_jsi' in f): logger.debug('Skipping ' + f + ' wrong file name...') continue rows = [] eRows = [] jLot = f.split('_')[2].split('.')[0] ERRORS = False #This can be set True in a number of places, This conditions whether or not an e-mail is sent logger.info('****************************************') logger.info('Loading file ' + f) #Read file fully into rows with open(f, 'rb') as g: reader = csv.reader(g) for row in reader: rows.append(row) #rows = sorted(rows, key=itemgetter(0, 1, 2)) #sort rows read by Order,Line,Piece since AS400 seems to supply data in random order logger.info(str(len(rows)) + ' records read.') tickets_processed = [] ''' Process each record in file ''' for row in reversed(rows): #print row[ORDER], row[LINE], row[TICKET], row[FINISHED_YARDS], row[INVOICED_YARDS], row[BIN] if row[TICKET] in tickets_processed: logger.warn('Encountered ticket: ' + row[TICKET] + ' more than once in single transmission!') row.append('DUPLICATE TICKET ROW. DOUBLE CHECK YARDAGE/WEIGHT IN JOMAR!!!') eRows.append(row) ERRORS = True continue jOrder = 'null' jLine = -1 jItem = -1 #if(row[ORDER] in PULL_ORDERS): #logger.warn(row[ORDER] + ' is a pull order. Ignoring for now! Ticket: ' + row[TICKET]) #row.append('**IGNORING PULL ORDERS FOR NOW**') #eRows.append(row) #ERRORS = True #continue #Neil says no pieces in HLD bin should make it this far; just in case... if(row[BIN] == 'HLD' or (row[WAREHOUSE] == 'SH' and row[BIN] == ' ')): #print row[ORDER], row[LINE], row[TICKET], row[FINISHED_YARDS], row[INVOICED_YARDS], row[BIN] logger.warn(row[WAREHOUSE] + '-' + row[BIN] + ' is not a valid bin! Ticket: ' + row[TICKET]) row.append('INVALID BIN') eRows.append(row) ERRORS = True continue if(float(row[FINISHED_YARDS]) <= 0): #print row[ORDER], row[LINE], row[TICKET], row[FINISHED_YARDS], row[INVOICED_YARDS], row[BIN] logger.warn('Finished Yards must be > 0. Ticket: ' + row[TICKET]) row.append('Finished Yards must be > 0') eRows.append(row) ERRORS = True continue if(float(row[INVOICED_YARDS]) <= 0): #print row[ORDER], row[LINE], row[TICKET], row[FINISHED_YARDS], row[INVOICED_YARDS], row[BIN] logger.warn('Invoiced Yards must be > 0. Ticket: ' + row[TICKET]) row.append('Invoiced Yards must be > 0') eRows.append(row) ERRORS = True continue #Get Jomar order number logger.info('Querying order header table for Jomar order number...') logger.debug('SELECT * FROM TXUYUV00 WHERE TXUYUV00.UBNR = \'001\' AND TXUYUV00.UPLT = \'00\' AND TXUYUV00.UBLA = \'SO\' AND (TXUYUV00.URBPO = \'' + row[ORDER] + '\' OR TXUYUV00.URBPO = \'' + row[ORDER][1:] + '\');') q = session.query(TXUYUV00).order_by(TXUYUV00.UBLN).filter(TXUYUV00.UBNR == '001', TXUYUV00.UPLT == '00', TXUYUV00.UBLA == 'SO', ((TXUYUV00.URBPO == row[ORDER]) | (TXUYUV00.URBPO == row[ORDER][1:]))) if(q.count() != 1 and row[ORDER]): #print row[ORDER], row[LINE], row[TICKET], row[FINISHED_YARDS], row[INVOICED_YARDS], row[BIN] logger.warn('Cannot locate order (' + row[ORDER] + ') in Jomar system. Ticket: ' + row[TICKET]) row.append('Cannot locate order in Jomar system.') eRows.append(row) ERRORS = True continue for r in q: jOrder = r.UBLN logger.info('Jomar order: ' + jOrder + ' Ticket: ' + row[TICKET]) #print 'Jomar Order:', jOrder, 'Matches Sunbury Order:', row[ORDER] #Get Jomar Line Number logger.info('Querying order detail table for Jomar line number...') logger.debug('SELECT VLFN, VANR, XFLX33, XFLX34 FROM TXUYUF01 LEFT JOIN TXMYAT03 ON TXUYUF01.VBNR = TXMYAT03.XBNR AND TXUYUF01.VPLT = TXMYAT03.XPLT AND TXUYUF01.VANR = TXMYAT03.XANR WHERE TXUYUF01.VBNR = \'001\' AND TXUYUF01.VPLT = \'00\' AND TXUYUF01.VBLA = \'SO\' AND TXUYUF01.VBLN = \'' + jOrder + '\' AND TXUYUF01.VPLIN = ' + str(int(row[LINE])) + ';') q = session.query(TXUYUF01,ItemInfo).order_by(TXUYUF01.VBLN).filter(TXUYUF01.VBNR == ItemInfo.XBNR, TXUYUF01.VPLT == ItemInfo.XPLT, TXUYUF01.VANR == ItemInfo.XANR).filter(TXUYUF01.VBNR == '001', TXUYUF01.VPLT == '00', TXUYUF01.VBLA == 'SO', TXUYUF01.VBLN == jOrder, TXUYUF01.VPLIN == int(row[LINE])) if(q.count() != 1 and row[ORDER]): #print row[ORDER], row[LINE], row[TICKET], row[FINISHED_YARDS], row[INVOICED_YARDS], row[BIN] logger.warn('AS400 Line: ' + row[LINE] + ' does not exist on Jomar order: ' + jOrder) row.append('Jomar Order: ' + jOrder + ' - There are no lines on this order where AS400 Line Number = ' + row[LINE] + ' Ticket: ' + row[TICKET]) eRows.append(row) ERRORS = True continue skip = False #Verify pattern and color match b/t AS400 and Jomar for r in q: if(r[1].XFLX33.strip().upper() != row[SPAT].strip() and row[ORDER]): skip = True ERRORS = True logger.warn('Pattern Mismatch! Jomar: ' + r[1].XFLX33 + ' AS400: ' + row[SPAT].strip() + ' Ticket: ' + row[TICKET]) row.append('Pattern Mismatch! Jomar: ' + r[1].XFLX33 + ' AS400: ' + row[SPAT].strip()) eRows.append(row) if(int(r[1].XFLX34) != int(row[SCOL].strip()) and row[ORDER]): skip = True ERRORS = True logger.warn('Color Mismatch! Jomar: ' + r[1].XFLX34 + ' AS400: ' + row[SCOL].strip() + ' Ticket: ' + row[TICKET]) row.append('Color Mismatch! Jomar: ' + r[1].XFLX34 + ' AS400: ' + row[SCOL].strip()) eRows.append(row) if(skip == False): jLine = r[0].VLFN jItem = r[0].VANR logger.info('Jomar line: ' + str(jLine) + ' Ticket: ' + row[TICKET]) logger.info('Jomar item: ' + str(jItem) + ' Ticket: ' + row[TICKET]) #print ' Jomar Line:', jLine, 'Matches Sunbury Line:', row[LINE] if(skip): continue ''' Not doing this anymore for obvious reasons... Wish I would've gotten the information concerning the other possible inventory transactions before 04/20/16...... ''' #If a roll with this ticket number exists in the inventory, get rid of it #session.query(TXRYRT00).filter(TXRYRT00.RTRNR == row[TICKET]).delete() tickets_processed.append(row[TICKET]) #Made it past all of the error checks, add this ticket to processed list. logger.info('Querying Roll/Tag master to see if this ticket exists in Jomar already. Ticket: ' + row[TICKET]) logger.debug('SELECT COUNT(*) FROM TXRYRT00 WHERE TXRYRT00.RTRNR = ' + row[TICKET] + ';') q = session.query(TXRYRT00).filter(TXRYRT00.RTRNR == row[TICKET]).count() if(q == 0): #Do an 08 transaction... logger.info('No previous record of roll. Adding production receipt (08) transaction. Ticket: ' + row[TICKET]) i = TXRYBW10() i.RWAPL = '00' i.RWSPL = '00' i.RWSLA = 'SO' i.RWPLT = '00' i.RWBNR = '001' i.RWALA = 'SO' i.RWBEW = '08' ''' MODIFIED - 01/04/2018 - SB-20 Set RTGRML to 'US' for all rolls ''' i.RWGRML = 'US' if(row[ORDER] not in PULL_ORDERS): i.RWALN = jOrder i.RWSLN = jOrder i.RWAFN = jLine i.RWSFN = jLine else: i.RWALN = None i.RWSLN = None i.RWAFN = None i.RWSFN = None i.RWANR = jItem i.RWCHA = jLot i.RWRNR = row[TICKET] i.RWPGLG = row[FINISHED_YARDS] i.RWPLG = row[INVOICED_YARDS] if(row[BIN] == 'BAG'): logger.debug('BAG bin = BG Warehouse. Ticket: ' + row[TICKET]) i.RWLOC = 'BG' i.RWLPL = '' elif(row[BIN].strip() == '08'): logger.debug('08 bin = B8 Warehouse. Ticket: ' + row[TICKET]) i.RWLOC = 'B8' i.RWLPL = '' else: if(row[WAREHOUSE] == 'SH'): i.RWLPL = row[BIN] else: i.RWLPL = '' i.RWLOC = row[WAREHOUSE] i.RWPWG = int(float(row[WEIGHT])) #i.RWTWG = int(float(row[WEIGHT])) #This was screwing up proformas. This is a tare weight field and so it should be zero for our purposes i.RWTWG = 0 i.RWQAL = '01' ''' MODIFIED - 01/04/2018 - SB-20 Determine if roll is NAFTA qualified. ''' i.RWORCD = NAFTA(session, row[SPAT].strip(), str(int(row[SCOL].strip()))) session.add(i) else: logger.debug('Roll already exists. Querying roll/tag master again to determine what to do...') logger.debug('SELECT * FROM TXRYRT00 WHERE TXRYRT00.RTRNR = ' + row[TICKET] + ';') p = session.query(TXRYRT00).filter(TXRYRT00.RTRNR == row[TICKET]) for r in p: ''' MODIFIED 01/23/17 Ran across a scenario where rolls already shipped in Jomar came through the interface again. Now checking for shipped yardage here so the script can kick out an error instead of compounding issue. ''' if float(r.RTSLG) > 0: tickets_processed.remove(row[TICKET]) #Remove this ticket from processed list logger.warn('Shipped yards greater than zero! Ticket: %s', row[TICKET]) row.append('Shipped yards > 0') eRows.append(row) ERRORS = True continue current_lot = r.RTCHA current_inv_yards = r.RTPLG current_fin_yards = r.RTPGLG current_inv_lbs = r.RTPWG current_bin = r.RTLPL current_warehouse = r.RTLOC dest_bin = row[BIN] dest_warehouse = row[WAREHOUSE] #Adjust bins and warehouses for bagging machine nonsense.... if(row[BIN].strip() == '08'): dest_bin = '' dest_warehouse = 'B8' if(row[BIN].strip() == 'BAG'): dest_bin = '' dest_warehouse = 'BG' if((float(current_inv_yards) != float(row[INVOICED_YARDS])) or (int(current_inv_lbs) != int(float(row[WEIGHT]))) or (float(current_fin_yards) != float(row[FINISHED_YARDS]))): logger.info('Roll exists but length/weight differ. Adjusting via a Production Receipt transaction (08) Ticket: ' + row[TICKET]) if (float(current_inv_yards) != float(row[INVOICED_YARDS])): logger.debug('Current Invoiced Yards = %s Transmitted = %s Adjustment = %s', current_inv_yards, row[INVOICED_YARDS], str(float(row[INVOICED_YARDS]) - float(current_inv_yards))) if (int(current_inv_lbs) != int(float(row[WEIGHT]))): logger.debug('Current Weight = %s Transmitted = %s Adjustment = %s', current_inv_lbs, row[WEIGHT], (int(float(row[WEIGHT])) - int(current_inv_lbs))) if (float(current_fin_yards) != float(row[FINISHED_YARDS])): logger.debug('Current Finished Yards = %s Transmitted = %s Adjustment = %s', current_fin_yards, row[FINISHED_YARDS], str(float(row[FINISHED_YARDS]) - float(current_fin_yards))) i = TXRYBW10() i.RWAPL = '00' i.RWSPL = '00' i.RWSLA = 'SO' i.RWPLT = '00' i.RWBNR = '001' i.RWALA = 'SO' i.RWBEW = '08' if(row[ORDER] not in PULL_ORDERS): i.RWALN = jOrder i.RWSLN = jOrder i.RWAFN = jLine i.RWSFN = jLine else: i.RWALN = None i.RWSLN = None i.RWAFN = None i.RWSFN = None i.RWANR = jItem i.RWCHA = current_lot i.RWRNR = row[TICKET] i.RWPGLG = str(float(row[FINISHED_YARDS]) - float(current_fin_yards)) i.RWPLG = str(float(row[INVOICED_YARDS]) - float(current_inv_yards)) i.RWLOC = current_warehouse.strip() i.RWLPL = current_bin.strip() i.RWPWG = (int(float(row[WEIGHT])) - int(current_inv_lbs)) #i.RWTWG = (int(float(row[WEIGHT])) - int(current_inv_lbs)) i.RWTWG = 0 i.RWQAL = '01' session.add(i) if((current_bin.strip() != dest_bin.strip()) or (current_warehouse.strip() != dest_warehouse.strip())): logger.info('Roll exists but bins are different. Adding a movement (06) transaction...' + current_warehouse + '-' + current_bin + ' --> ' + dest_warehouse + '-' + dest_bin + ' Ticket: ' + row[TICKET]) i = TXRYBW10() i.RWAPL = '00' i.RWSPL = '00' i.RWSLA = 'SO' i.RWPLT = '00' i.RWBNR = '001' i.RWALA = 'SO' i.RWBEW = '06' if(row[ORDER] not in PULL_ORDERS): i.RWALN = jOrder i.RWSLN = jOrder i.RWAFN = jLine i.RWSFN = jLine else: i.RWALN = None i.RWSLN = None i.RWAFN = None i.RWSFN = None i.RWANR = jItem i.RWCHA = current_lot i.RWRNR = row[TICKET] i.RWPGLG = row[FINISHED_YARDS] i.RWPLG = row[INVOICED_YARDS] i.RWLOC = dest_warehouse.strip() #Xfer to warehouse i.RWLPL = dest_bin.strip() #Xfer to bin i.RWLO2 = current_warehouse.strip() #Xfer from warehouse i.RWLP2 = current_bin.strip() #Xfer from bin i.RWPL2 = '00' #Xfer from plant i.RWPWG = int(float(row[WEIGHT])) #i.RWTWG = int(float(row[WEIGHT])) i.RWTWG = 0 i.RWQAL = '01' session.add(i) if( (float(current_inv_yards) == float(row[INVOICED_YARDS])) and (int(current_inv_lbs) == int(float(row[WEIGHT]))) and (current_bin.strip() == dest_bin.strip()) and (current_warehouse.strip() == dest_warehouse.strip()) and (float(current_fin_yards) == float(row[FINISHED_YARDS]))): logger.info('Nothing appears to be different. Ignoring. Ticket: ' + row[TICKET]) ''' End record processing ''' #If errors were encountered during processing, e-mail the suspect rows to Aaron in a csv if ERRORS: #Dump eRows to errors.csv logger.debug("Dumping error rows to: " + PATH + "ERRORS\\ERRORS_" + jLot + ".csv") with open(PATH + "ERRORS\\ERRORS_" + jLot + ".csv", "wb") as g: writer = csv.writer(g) writer.writerows(eRows) try: if not TEST_MODE: logger.info('E-mailing Aaron list of error rows...') dnr_account = load_email_credentials() send_mail('donotreply@example.com', ROLL_ERROR_RECIPIENTS, 'AS400 -> Jomar Inventory Errors', 'See Attachment.', username=dnr_account[0], password=dnr_account[1], files=[PATH + "ERRORS\\ERRORS_" + jLot + ".csv"]) except: logger.error('Could not send e-mail. Check internet connection.') if not TEST_MODE: logger.debug("Moving " + f + " to " + "PROCESSED\\" + f.split("\\")[2]) os.rename(f, PATH + "PROCESSED\\" + f.split("\\")[2]) ''' End file processing ''' #When all files have been processed commit new records to interface table. if(TEST_MODE): logger.warn('**TEST MODE ENABLED. ROLLING BACK DATABASE TRANSACTIONS**') session.rollback() else: logger.debug('Committing database transactions...') session.commit() logger.debug('Closing database connection.') session.close()