Found more code examples from STM. Used claude to generate a summary (CLAUDE.md)
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
'''
|
||||
Created on Sep 18, 2015
|
||||
|
||||
Interface a Kravet PO csv file into Jomar EDI tables
|
||||
|
||||
@author: Wes
|
||||
'''
|
||||
from alert import send_alert_html
|
||||
|
||||
import datetime, csv, logging, sys, os, re, StringIO
|
||||
from itertools import groupby, tee
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from operator import itemgetter
|
||||
|
||||
RECIPIENTS = ['admin@example.com']
|
||||
SUBJECT = 'New Kravet 850 Processed.'
|
||||
MESSAGE = None
|
||||
|
||||
TEST_MODE = False
|
||||
|
||||
STM_ITEM_PATTERN = "[\s\S]+[*]\d+$"
|
||||
KFI_ITEM_PATTERN = "\d+[.]\d+[.]\d+"
|
||||
|
||||
STREET = '1500 HIGHWAY 29 SOUTH'
|
||||
CITY = 'ANDERSON'
|
||||
|
||||
PO_NUM = 0
|
||||
LINE_NUM = 1
|
||||
SHIPMENT_NUM = 2
|
||||
VENDOR_NAME = 3
|
||||
CUSTOMER_NUM = 4
|
||||
ORDER_DATE = 5
|
||||
SHIP_TO_NAME = 6
|
||||
SHIP_1 = 7
|
||||
SHIP_2 = 8
|
||||
SHIP_CITY = 9
|
||||
SHIP_STATE = 10
|
||||
SHIP_ZIP = 11
|
||||
SHIP_COUNTRY = 12
|
||||
BILL_1 = 13
|
||||
BILL_2 = 14
|
||||
BILL_CITY = 15
|
||||
BILL_STATE = 16
|
||||
BILL_ZIP = 17
|
||||
STM_ITEM = 18
|
||||
FINISHES = 19
|
||||
KRAVET_ITEM = 20
|
||||
ORDERED_ITEM = 21
|
||||
QTY = 22
|
||||
UNITS = 23
|
||||
UNIT_PRICE = 24
|
||||
EXT_PRICE = 25
|
||||
NOTES = 26
|
||||
PAYMENT_TERMS = 27
|
||||
FREIGHT_TERMS = 28
|
||||
SHIP_VIA = 29
|
||||
CURRENCY = 30
|
||||
DESCRIPTION = 31
|
||||
SIDEMARK = 34
|
||||
|
||||
logger = None
|
||||
LOG_LEVEL = logging.DEBUG
|
||||
engine = create_engine("mssql+pyodbc://jsisbprod", echo=False)
|
||||
Base = declarative_base(engine)
|
||||
metadata = Base.metadata
|
||||
|
||||
########################################################################
|
||||
class OrderHeader(Base):
|
||||
__tablename__ = 'CSPEDI50H' #EDI 850 Header Table
|
||||
__table_args__ = {'autoload':True}
|
||||
class OrderDetail(Base):
|
||||
__tablename__ = 'CSPEDI50D' #EDI 850 Detail Table
|
||||
__table_args__ = {'autoload':True}
|
||||
class ItemXref(Base):
|
||||
__tablename__ = 'TXUYAT01' #Customer Item -> Sunbury Item reference table
|
||||
__table_args__ = {'autoload':True}
|
||||
class ItemInfo(Base):
|
||||
__tablename__ = 'TXMYAT03'
|
||||
__table_args__ = {'autoload':True}
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
|
||||
'''
|
||||
Most of the data we receive from Kravet needs to be "massaged" into something that's
|
||||
actually usable. Trying to consolidate these "fixes" into one place...
|
||||
|
||||
For reference, the agreed upon format for the STM item field is:
|
||||
|
||||
PATTERN*COLOR
|
||||
|
||||
Where:
|
||||
* PATTERN contains only letters, spaces, ', and -
|
||||
* COLOR contains only digits
|
||||
|
||||
Examples:
|
||||
What they sent:
|
||||
KIRSTEN 73383*2015
|
||||
MULBERRY 73151*COL.825
|
||||
|
||||
What they should have sent:
|
||||
KIRSTEN*2015
|
||||
MULBERRY*0825
|
||||
'''
|
||||
def massageSTM(fromKravet):
|
||||
global logger
|
||||
og = fromKravet
|
||||
if not (fromKravet.count('*') == 1):
|
||||
#logger.error("Unexpected format encountered for vendor item string...")
|
||||
return fromKravet
|
||||
|
||||
fromKravet = re.sub("[^A-Za-z0-9*'\-\s]+", '', fromKravet) #Remove all special characters besides *, ', -, and spaces
|
||||
|
||||
#Today's kludge brought to you by: 'KIRSTEN 73383*2015'
|
||||
i = fromKravet.index('*')
|
||||
fromKravet = re.sub('[0-9]+', '', fromKravet[:i]).strip() + fromKravet[i:] #Remove any digits to the left of *
|
||||
|
||||
#The following bandaid was inspired by: 'MULBERRY 73151*COL.825'
|
||||
i = fromKravet.index('*')
|
||||
fromKravet = fromKravet[:i] + re.sub('[A-Za-z]+', '', fromKravet[i:]).strip() #Remove non-digits to the right of *
|
||||
|
||||
#Zero pad 3 digits colors while we're at it...
|
||||
i = fromKravet.index('*') + 1
|
||||
fromKravet = fromKravet[:i] + fromKravet[i:].zfill(4)
|
||||
|
||||
logger.debug('Massaging STM item column: %s --> %s', og, fromKravet)
|
||||
return fromKravet
|
||||
|
||||
'''
|
||||
|
||||
'''
|
||||
def massageKFI(fromKravet):
|
||||
global logger
|
||||
og = fromKravet
|
||||
|
||||
if(fromKravet.startswith('GWF-')):
|
||||
fromKravet = fromKravet[4:]
|
||||
|
||||
logger.debug('Massaging KFI item column: %s --> %s', og, fromKravet)
|
||||
return fromKravet
|
||||
|
||||
def lookUpSunburyItem(session, k_item, ven):
|
||||
global logger
|
||||
k_item = k_item.replace('#','') #Yet another bandaid fix for Kravet...
|
||||
if(re.compile(STM_ITEM_PATTERN).match(k_item) == None):
|
||||
logger.error('Kravet transmitted junk in the STM item column: ' + k_item)
|
||||
return ''
|
||||
|
||||
k = k_item.split('*')
|
||||
k_pat = k[0]
|
||||
k_col = re.compile(r'[^\d.]+').sub('', k[1]).zfill(4)
|
||||
q = session.query(ItemXref,ItemInfo).order_by(ItemXref.UACCOL).filter(ItemXref.UACCNR == ven, ItemInfo.XFLX33 == k_pat, ItemInfo.XFLX34 == k_col, ItemXref.UACANR == ItemInfo.XANR)
|
||||
if(q.count() == 1):
|
||||
for row in q:
|
||||
logger.info('STM Pattern: ' + k_pat + ' Color: ' + k_col + ' --> Sunbury Item: ' + row[0].UACANR)
|
||||
return row[0].UACANR
|
||||
else:
|
||||
session.close()
|
||||
logger.warn('Failed to lookup item # for: ' + k_pat + '-' + k_col + ' is the cross reference entered in Jomar?')
|
||||
return ''
|
||||
#raise RuntimeError('Failed to lookup Sunbury item for STM Pattern: ' + k_pat + ' Color: ' + k_col + ' is the cross reference entered in Jomar?')
|
||||
|
||||
def processKravetPo(inFile):
|
||||
global MESSAGE
|
||||
os.chdir('D:\FTP')
|
||||
initializeLogger('kf_850_interface')
|
||||
logger.info('****************************************')
|
||||
fileName = inFile.split('\\')[-1]
|
||||
rawLines = []
|
||||
session = loadSession()
|
||||
|
||||
logger.info('Loading: ' + 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 ' + inFile + ' to D:\\FTP\\OLD\\KF\\')
|
||||
os.rename(inFile, 'OLD\\KF\\' + fileName)
|
||||
session.close()
|
||||
return
|
||||
#sys.exit("Nothing to process.")
|
||||
|
||||
rawLines.sort(key=itemgetter(0)) #Ensure raw data is sorted by PO number
|
||||
purchaseOrders = groupby(rawLines, itemgetter(0)) #Create group by iterator based on PO number
|
||||
|
||||
for poNumber, lines in purchaseOrders:
|
||||
logger.info('***')
|
||||
logger.info('Processing purchase order ' + poNumber)
|
||||
|
||||
#Create a copy of the lines iterator so we can pull the first line of the PO now to fill in header info
|
||||
lines, flb = tee(lines)
|
||||
fl = flb.next()
|
||||
|
||||
header = OrderHeader()
|
||||
#Order header constants
|
||||
header.UBNR = '001'
|
||||
header.UPLT = '00'
|
||||
header.ULOAD = 'N'
|
||||
header.UBLA = 'SO'
|
||||
header.FLX01 = '00'
|
||||
header.URLRV = '0' #An ID column, in case the same PO is transmitted twice both may be added to DB and sorted out in Jomar
|
||||
|
||||
header.UDAT = int(datetime.datetime.strptime(filename, "Kravet_to_vendor_%d-%b-%Y.csv").strftime('%Y%m%d')) #Transaction Date -> Order Date
|
||||
logger.info('PO Date: ' + str(header.UDAT))
|
||||
|
||||
header.UIHS = int(datetime.datetime.strptime(fl[ORDER_DATE], "%d/%m/%Y").strftime('%Y%m%d'))
|
||||
logger.info('Ordered On: ' + str(header.UIHS))
|
||||
|
||||
header.FOB = fl[FREIGHT_TERMS]
|
||||
logger.info('FOB: ' + header.FOB)
|
||||
|
||||
header.XTX5 = fl[PAYMENT_TERMS]
|
||||
logger.info('Payment terms: ' + header.XTX5)
|
||||
|
||||
header.UIHR = poNumber
|
||||
|
||||
if(fl[CUSTOMER_NUM] == 'KFI' or fl[CUSTOMER_NUM] == '3190'):
|
||||
logger.info('Column 5 = "KFI" using customer number: 3190')
|
||||
header.UKDR = '3190'
|
||||
header.UKDN = '319000'
|
||||
header.UNA2 = 'KRAVET FABRICS'
|
||||
if(fl[SHIP_1].startswith(STREET) and fl[SHIP_CITY].startswith(CITY)):
|
||||
header.UKDN = '319003'
|
||||
elif(fl[CUSTOMER_NUM] == 'LJI' or fl[CUSTOMER_NUM] == '3191'):
|
||||
logger.info('Column 5 = "LJI" using customer number: 3191')
|
||||
header.UKDR = '3191'
|
||||
header.UKDN = '319100'
|
||||
header.UNA2 = 'LEE JOFA'
|
||||
if(fl[SHIP_1].startswith(STREET) and fl[SHIP_CITY].startswith(CITY)):
|
||||
header.UKDN = '319101'
|
||||
elif(fl[CUSTOMER_NUM] == 'BRF' or fl[CUSTOMER_NUM] == '746'):
|
||||
logger.info('Column 5 = "BRF" using customer number: 0746')
|
||||
header.UKDR = '0746'
|
||||
header.UKDN = '074600'
|
||||
header.UNA2 = 'BRUNSCHWIG & FILS'
|
||||
if(fl[SHIP_1].startswith(STREET) and fl[SHIP_CITY].startswith(CITY)):
|
||||
header.UKDN = '074600'
|
||||
elif(fl[CUSTOMER_NUM] == 'GIS'):
|
||||
logger.info('Column 5 = "GIS" using customer number: 3194')
|
||||
header.UKDR = '3194'
|
||||
header.UKDN = '319400'
|
||||
header.UNA2 = 'KRAVET FABRICS/GIS'
|
||||
if(fl[SHIP_1].startswith(STREET) and fl[SHIP_CITY].startswith(CITY)):
|
||||
header.UKDN = '319401'
|
||||
elif(fl[CUSTOMER_NUM] == 'GSR'):
|
||||
logger.info('Column 5 = "GSR" using customer number: 3194')
|
||||
header.UKDR = '3194'
|
||||
header.UKDN = '319400'
|
||||
header.UNA2 = 'KRAVET FABRICS/GIS'
|
||||
if(fl[SHIP_1].startswith(STREET) and fl[SHIP_CITY].startswith(CITY)):
|
||||
header.UKDN = '319401'
|
||||
else:
|
||||
logger.error('Unknown Customer Code: ' + fl[CUSTOMER_NUM])
|
||||
session.close()
|
||||
raise ValueError('Unknown Customer Code: ' + fl[CUSTOMER_NUM])
|
||||
|
||||
if(len(fl[SHIP_TO_NAME].strip()) > 0):
|
||||
header.UNA2 = fl[SHIP_TO_NAME]
|
||||
header.UNA3 = fl[SHIP_1]
|
||||
header.UNA4 = fl[SHIP_2]
|
||||
header.UORT = fl[SHIP_CITY]
|
||||
header.USTA = fl[SHIP_STATE]
|
||||
header.ULND = fl[SHIP_COUNTRY]
|
||||
header.UPLZ = fl[SHIP_ZIP]
|
||||
|
||||
line = 1
|
||||
orderLines = []
|
||||
for l in lines:
|
||||
oline = OrderDetail()
|
||||
oline.VBNR = '001'
|
||||
oline.VPLT = '00'
|
||||
oline.VQUALT = '01'
|
||||
oline.VUOM = 'YDS'
|
||||
oline.VBLA = 'SO'
|
||||
oline.VLFN = line
|
||||
logger.info('Order Line ' + str(line))
|
||||
|
||||
oline.VMGS = l[QTY]
|
||||
logger.info('Quantity Ordered: ' + oline.VMGS + ' ' + l[UNITS] )
|
||||
|
||||
oline.VPR2 = l[UNIT_PRICE]
|
||||
logger.info('Unit Prince: ' + oline.VPR2)
|
||||
|
||||
#Attempt to fix various common issues with the xref data sent from Kravet
|
||||
stmItem = massageSTM(l[STM_ITEM])
|
||||
kravetItem = massageKFI(l[KRAVET_ITEM])
|
||||
|
||||
oline.VANR = lookUpSunburyItem(session, stmItem, header.UKDR)
|
||||
oline.VKDR = header.UKDR
|
||||
oline.VKDL = header.UKDN
|
||||
oline.VIHR = l[PO_NUM]
|
||||
oline.VTXT1 = l[NOTES][:65]
|
||||
logger.info('Notes: ' + oline.VTXT1)
|
||||
|
||||
#If possible parse Sunbury pattern and color and insert into database
|
||||
if not(re.compile(STM_ITEM_PATTERN).match(stmItem) == None):
|
||||
sItem = stmItem.split('*')
|
||||
oline.VFLX01 = sItem[0]
|
||||
oline.VFLX02 = sItem[1]
|
||||
else:
|
||||
logger.warn('STM item is in an unusual format: ' + stmItem)
|
||||
|
||||
#If possible parse Kravet pattern and color and insert into database
|
||||
if not(re.compile(KFI_ITEM_PATTERN).match(kravetItem) == None):
|
||||
kItem = kravetItem.split('.')
|
||||
oline.VFLX03 = kItem[0]
|
||||
oline.VFLX04 = kItem[1]
|
||||
else:
|
||||
logger.warn('Kravet item number is in an unusual format: ' + l[KRAVET_ITEM])
|
||||
|
||||
oline.VRLRV = '0' #ID column for adding duplicate POs to EDI tables
|
||||
line = line + 1
|
||||
orderLines.append(oline)
|
||||
|
||||
#Check for duplicate PO(s) already in database; increment URLRV/VRLRV accordingly if so
|
||||
dups = session.query(OrderHeader).filter(OrderHeader.UBNR == '001', OrderHeader.UPLT == '00', OrderHeader.UKDR == header.UKDR, OrderHeader.UIHR == header.UIHR).count()
|
||||
if(dups > 0):
|
||||
logger.warn('PO ' + header.UIHR + ' already in 850 tables. Adding this anyway with sequence number ' + str(dups) + ' it may be deleted in Jomar if not needed.')
|
||||
header.URLRV = str(dups)
|
||||
for line in orderLines:
|
||||
line.VRLRV = str(dups)
|
||||
|
||||
session.add(header)
|
||||
for line in orderLines:
|
||||
session.add(line)
|
||||
|
||||
if not TEST_MODE:
|
||||
logger.info('Writing records to database...')
|
||||
session.commit()
|
||||
else:
|
||||
logger.warn('**TEST MODE ENABLED ROLLING BACK TRANSACTIONS**')
|
||||
session.rollback()
|
||||
|
||||
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?')
|
||||
|
||||
if not TEST_MODE:
|
||||
logger.info('Moving ' + inFile + ' to D:\\FTP\\OLD\\KF\\')
|
||||
os.rename(inFile, 'OLD\\KF\\' + fileName)
|
||||
|
||||
MESSAGE.truncate(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if(len(sys.argv) < 2):
|
||||
print 'Must supply fully qualified path to csv file ex:'
|
||||
print 'python kf_850.py D:\\FTP\\Kravet_to_vendor_25-MAY-2016.csv'
|
||||
else:
|
||||
processKravetPo(sys.argv[1])
|
||||
Reference in New Issue
Block a user