255 lines
8.2 KiB
Python
255 lines
8.2 KiB
Python
'''
|
|
Created on Apr 27, 2017
|
|
|
|
Import Glen Raven ASN data into the DB
|
|
|
|
@author: Wes
|
|
'''
|
|
from alert import send_alert_html
|
|
|
|
import datetime, csv, logging, sys, os, StringIO
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy.orm import sessionmaker
|
|
from operator import itemgetter
|
|
from itertools import groupby
|
|
|
|
RECIPIENTS = ['admin@example.com']
|
|
SUBJECT = 'Inbound Glen Raven ASN Processed'
|
|
MESSAGE = None
|
|
|
|
TEST_MODE = False
|
|
GR_AP_ACCOUNT = '001306'
|
|
JOMAR_PO_ORDER_TYPE = 'JB'
|
|
|
|
VN = 0
|
|
PACK_SLIP = 1
|
|
YARN_ITEM = 2
|
|
COLOR_DESC = 3
|
|
PO_DO = 4
|
|
LOT_NUMBER = 5
|
|
CASE = 6
|
|
CASE_UNITS = 7
|
|
GRS_LBS = 8
|
|
NET_LBS = 9
|
|
TRNS_DATE = 9
|
|
SHIP_DATE = 10
|
|
SHIP_VIA = 11
|
|
|
|
logger = None
|
|
LOG_LEVEL = logging.DEBUG
|
|
engine = create_engine("mssql+pyodbc://jsisbprod", echo=False)
|
|
#engine = create_engine("mssql+pyodbc://@JTest", echo=False)
|
|
Base = declarative_base(engine)
|
|
metadata = Base.metadata
|
|
|
|
#---------------------------------------------------------------------#
|
|
class PSPASN00(Base):
|
|
__tablename__ = 'PSPASN00' #Inboud ASN table
|
|
__table_args__ = {'autoload':True}
|
|
class TXEYAT00(Base):
|
|
__tablename__ = 'TXEYAT00' #Vendor Item Cross reference table
|
|
__table_args__ = {'autoload':True}
|
|
class TXUYUF01(Base):
|
|
__tablename__ = 'TXUYUF01' #Order Detail Table
|
|
__table_args__ = {'autoload':True}
|
|
#---------------------------------------------------------------------#
|
|
|
|
class ItemTranslationError(RuntimeError):
|
|
''' Raising this if we fail to lookup STM item using GR item '''
|
|
|
|
class POError(RuntimeError):
|
|
''' Raising this if we fail to lookup required info from relevant PO '''
|
|
|
|
def loadSession():
|
|
global engine
|
|
Session = sessionmaker(bind=engine)
|
|
session = Session()
|
|
return session
|
|
|
|
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:\\FTP\\logs\\' + 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.INFO)
|
|
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)
|
|
|
|
'''
|
|
Query the vendor item xref table to translate GR --> STM
|
|
|
|
Likely reasons this failed:
|
|
* _gr_item_ not entered as a xref for any of our items
|
|
* _gr_item_ entered for more than 1 of our items
|
|
|
|
Returns the matching STM item number if found.
|
|
'''
|
|
def GRtoSTM(gr_item):
|
|
global session
|
|
global logger
|
|
q = session.query(TXEYAT00).filter(TXEYAT00.UALNR == GR_AP_ACCOUNT, TXEYAT00.UALAN == gr_item)
|
|
if(q.count() == 1):
|
|
for row in q:
|
|
return row.UAANR
|
|
else:
|
|
logger.debug('GRtoSTM() rows returned: %s', q.count())
|
|
return None
|
|
|
|
'''
|
|
Query the order detail table by _po_ and _item_.
|
|
|
|
Likely reasons this failed:
|
|
* _po_ doesn't actually exist
|
|
* _item_ not ordered on _po_
|
|
* _item_ appears on more than 1 line on _po_
|
|
|
|
Returns the line number of _item_ on _po_ if found.
|
|
'''
|
|
def getLineNum(po, item):
|
|
global session
|
|
global logger
|
|
q = session.query(TXUYUF01).filter(TXUYUF01.VBLA == JOMAR_PO_ORDER_TYPE, TXUYUF01.VBLN == po, TXUYUF01.VANR == item)
|
|
if(q.count() == 1):
|
|
for row in q:
|
|
return row.VLFN
|
|
else:
|
|
return None
|
|
|
|
def processInboundGRASN(inFile):
|
|
global MESSAGE
|
|
global session
|
|
os.chdir('D:\FTP')
|
|
initializeLogger('gr_856_inbound_interface')
|
|
logger.info('****************************************')
|
|
fileName = inFile.split('\\')[-1]
|
|
rawLines = []
|
|
session = loadSession()
|
|
|
|
logger.info('Loading: %s', inFile)
|
|
try:
|
|
f = open(inFile, 'r')
|
|
except IOError:
|
|
logger.fatal('Unable to load file. Did you specify the full path? Exiting...')
|
|
return
|
|
filename = inFile.split('\\')[-1]
|
|
|
|
try:
|
|
reader = csv.reader(f)
|
|
for row in reader:
|
|
rawLines.append(row)
|
|
finally:
|
|
f.close()
|
|
|
|
rawLines.pop(0) #Remove first line as it is just column headers
|
|
|
|
if(len(rawLines) == 0):
|
|
logger.warning('They sent us an empty file...')
|
|
logger.info('Moving %s to D:\\FTP\\OLD\\GR\\', inFile)
|
|
os.rename(inFile, 'OLD\\GR\\' + fileName)
|
|
session.close()
|
|
return
|
|
|
|
rawLines.sort(key=itemgetter(PO_DO)) #Ensure raw data is sorted by PO number
|
|
purchaseOrders = groupby(rawLines, itemgetter(PO_DO)) #Create group by iterator based on PO number
|
|
|
|
#For each unique PO appearing in this transmission
|
|
for poNumber, lines in purchaseOrders:
|
|
logger.info('****')
|
|
logger.info('Processing records for purchase order: %s', poNumber)
|
|
|
|
#For each record of this transmission on poNumber
|
|
for line in lines:
|
|
if line[VN] not in ['19']:
|
|
logger.warn('Encountered record with invalid vendor number!')
|
|
logger.warn(line)
|
|
continue
|
|
|
|
#Translate GR item to STM item
|
|
stm_item = GRtoSTM(line[YARN_ITEM])
|
|
|
|
if stm_item:
|
|
#Query order detail by PO and STM item to get the correct PO line number
|
|
line_num = getLineNum(poNumber, stm_item)
|
|
|
|
if line_num:
|
|
logger.info('Processing Case: %s (%s)', line[CASE], stm_item)
|
|
asn = PSPASN00()
|
|
asn.BUSINESS_UNIT = '001'
|
|
asn.PLANT = '00'
|
|
asn.VENDOR = GR_AP_ACCOUNT
|
|
asn.PO_NUMBER = poNumber
|
|
asn.PO_ITEM = stm_item
|
|
asn.PO_LINE = line_num
|
|
asn.PACKING_SLIP = line[PACK_SLIP]
|
|
asn.CASE_NUMBER = line[CASE]
|
|
asn.QTY_GROSS = line[GRS_LBS]
|
|
asn.QTY_NET = line[NET_LBS]
|
|
asn.VENDOR_LOT = line[LOT_NUMBER]
|
|
session.add(asn)
|
|
else:
|
|
logger.error('Failed to find a line on PO %s for: %s', poNumber, stm_item)
|
|
|
|
try:
|
|
send_alert_html(RECIPIENTS, 'Error Processing GR ASN', attachments=[inFile], message=MESSAGE.getvalue())
|
|
except:
|
|
logger.error('Unable to send e-mail. Is the internet out?')
|
|
|
|
raise POError('Failed to retrieve PO info from database', poNumber, stm_item)
|
|
else:
|
|
logger.error('Failed to lookup STM item number for GR Item: %s', line[YARN_ITEM])
|
|
|
|
try:
|
|
send_alert_html(RECIPIENTS, 'Error Processing GR ASN', attachments=[inFile], message=MESSAGE.getvalue())
|
|
except:
|
|
logger.error('Unable to send e-mail. Is the internet out?')
|
|
|
|
raise ItemTranslationError('Failed to lookup STM item number for GR Item', line[YARN_ITEM])
|
|
|
|
if not TEST_MODE:
|
|
logger.info('Writing records to database...')
|
|
session.commit()
|
|
else:
|
|
logger.warn('**TEST MODE ENABLED ROLLING BACK TRANSACTIONS**')
|
|
session.rollback()
|
|
|
|
session.close()
|
|
|
|
if not TEST_MODE:
|
|
try:
|
|
logger.info('Sending e-mail alert...')
|
|
send_alert_html(RECIPIENTS, SUBJECT, attachments=[inFile], message=MESSAGE.getvalue())
|
|
except:
|
|
logger.error('Unable to send e-mail. Is the internet out?')
|
|
|
|
logger.info('Moving ' + inFile + ' to D:\\FTP\\OLD\\GR\\')
|
|
os.rename(inFile, 'OLD\\GR\\' + fileName)
|
|
|
|
MESSAGE.truncate(0)
|
|
|
|
if __name__ == '__main__':
|
|
if(len(sys.argv) < 2):
|
|
print 'Must supply fully qualified path to csv file ex:'
|
|
print 'python gr_856_in.py D:\\FTP\\INCOMING\\GR_ASN_0100334639.csv'
|
|
else:
|
|
processInboundGRASN(sys.argv[1])
|