Files

269 lines
12 KiB
Python

'''
Created on Dec 8, 2015
Working with David Keenan (dkeenan@maharam.com) at Maharam to send Invoices to them as XML documents.
@author: Wes
'''
from alert import send_alert_html
from glob import glob
import datetime, sys, re, logging, StringIO
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql.expression import update
from sqlalchemy.sql.schema import Table, Column
from sqlalchemy.sql.sqltypes import Integer
import xml.etree.cElementTree as ET
RECIPIENTS = ['admin@example.com']
SUBJECT = 'Invoices Transmitted to Maharam.'
MESSAGE = None
TEST_MODE = False
LOG_LEVEL = logging.DEBUG
ACC_NUMBERS = ['3439', '6590']
DESC_PATTERN = "(?P<pName>[a-zA-Z0-9_ \-\']+?)\s(?P<color>\d+)"
engine = create_engine("mssql+pyodbc://jsisbprod", echo=False) #Production
Base = declarative_base(engine)
metadata = Base.metadata
logger = None
########################################################################
#Jomar Invoice (EDI 810) staging tables #
########################################################################
class CSPINV00(Base):
__tablename__ = 'CSPINV00'
__table_args__ = {'autoload':True}
class CSPINV01(Base):
__tablename__ = 'CSPINV01'
__table_args__ = {'autoload':True}
class TXMYAT03(Base):
__tablename__ = 'TXMYAT03'
__table_args__ = {'autoload':True}
class XREF(Base): #Customer Xref Table
__tablename__ = 'TXUYAT01'
__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 - %(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)
def createMaharamInvoiceDocuments():
global MESSAGE
initializeLogger('MAH_810_GENERATE')
if TEST_MODE:
logger.warn('**RUNNING IN TEST MODE**')
logger.warn('THIS WILL NOT FLAG INVOICES AS PROCESSED IN CSPINV00!')
else:
logger.info('**RUNNING IN PRODUCTION MODE**')
session = loadSession()
invoices = []
invoices_to_flag_processed = []
#root = ET.Element("MaharamInvoices")
logger.info('Querying EDI table for unprocessed invoices...')
logger.debug('SELECT * FROM CSPINV00 WHERE IVCUSTB IN (\'3439\', \'6590\') ORDER BY IVIDAT;')
q = session.query(CSPINV00).order_by(CSPINV00.IVIDAT).filter(CSPINV00.IVCUSTB.in_(ACC_NUMBERS))
for row in q:
if(row.IVPROCSS == 'Y'):
continue
businessGroup = row.IVBNR
plant = row.IVPLT
invoiceType = row.IVITYP
invoiceNum = row.IVINV
invoices_to_flag_processed.append(invoiceNum)
date = datetime.datetime.strptime(str(row.IVIDAT), "%Y%m%d")
customerBillToAccount = row.IVCUSTB
customerPO = row.IVCPO
orderType = row.IVSTYP
orderNum = row.IVSO
terms = row.IVTERMS
bAddress1 = row.IVBADD1
bAddress2 = row.IVBADD2
bAddress3 = row.IVBADD3
bCity = row.IVBCTY
bState = row.IVBST
bZip = row.IVBZIP
bCountry = row.IVBCTY
sAddress1 = row.IVSADD1
sAddress2 = row.IVSADD2
sAddress3 = row.IVSADD3
sCity = row.IVSCTY
sState = row.IVSST
sZip = row.IVSZIP
sCountry = row.IVSCTY
lineNum = []
itemNum = []
itemDesc = []
cItem = []
cItemDesc = []
qtyInvoiced = []
price = []
invAmtDom = []
invAmtFor = []
invAmtGrossDom = []
invAmtGrossFor = []
cPatterns = []
cColors = []
cSKUs = []
logger.info('Querying EDI table for invoice detail lines for invoice: ' + invoiceNum)
logger.debug('SELECT * FROM CSPINV01 WHERE CSPINV01.I1BNR = \'' + businessGroup + '\' AND CSPINV01.I1PLT = \'' + plant + '\' AND CSPINV01.I1ITYP = \'' + invoiceType + '\' AND CSPINV01.I1INV = \'' + invoiceNum + '\';')
q2 = session.query(CSPINV01).filter(CSPINV01.I1BNR == businessGroup, CSPINV01.I1PLT == plant, CSPINV01.I1ITYP == invoiceType, CSPINV01.I1INV == invoiceNum)
for row2 in q2:
lineNum.append(row2.I1ILINE)
#itemNum.append(row2.I1ITEM) Sending Maharam our PATTERN-COLOR instead of new item number for the time being
itemDesc.append(row2.I1ITEMT)
cItem.append(row2.I1ITEMC)
cItemDesc.append(row2.I1ITEMCT)
qtyInvoiced.append(row2.I1QTY)
price.append(row2.I1PRI)
invAmtDom.append(row2.I1AMTND)
invAmtFor.append(row2.I1AMTNF)
invAmtGrossDom.append(row2.I1AMTGD)
invAmtGrossFor.append(row2.I1AMTGF)
if(row2.I1ITEM == 'INSURANCE' or row2.I1ITEM == 'FREIGHT'):
cPatterns.append('')
cColors.append('')
cSKUs.append('')
itemNum.append(row2.I1ITEM)
continue
blah = re.match(DESC_PATTERN, row2.I1ITEMT, flags=0)
ourPattern = blah.group('pName')
ourColor = blah.group('color')
itemNum.append(ourPattern.strip() + '-' + ourColor.strip()) #Sending Maharam our PATTERN-COLOR instead of new item number for the time being
'''
This queries a view I created to join TXUYAT01 (customer xref) and TXMYAT03 (item info) together.
The join basically adds the Sunbury pattern name, and color broken out into their own columns to the customer xref table.
As far as I know, a customer can only have one 'reference' for a given pattern/color combination so this should be fine.
It also handles instances where a finish/backing was changed during order entry, which results in a new item number that may not
make it into the customer xref table, which broke the old method that relied on jomar item numbers.
'''
logger.info('Querying customer item cross reference table for Sunbury item: ' + ourPattern + '-' + ourColor)
logger.debug('SELECT * FROM item_xref_wes WHERE item_xref_wes.bGroup = \'' + businessGroup + '\' AND item_xref_wes.plant = \'' + plant + '\' AND item_xref_wes.customer = \'' + customerBillToAccount + '\' AND item_xref_wes.sPatt = \'' + ourPattern + '\' AND item_xref_wes.sCol = \'' + ourColor + '\';')
xref_lookup = session.execute('SELECT * FROM item_xref_wes WHERE item_xref_wes.bGroup = :bgroup AND item_xref_wes.plant = :plt AND item_xref_wes.customer = :cust AND item_xref_wes.sPatt = :pat AND item_xref_wes.sCol = :col', {'bgroup' : businessGroup, 'plt' : plant, 'cust' : customerBillToAccount, 'pat' : ourPattern, 'col' : ourColor})
for r in xref_lookup:
cPatterns.append(r.cPatt)
cColors.append(r.cCol)
cSKUs.append(r.cPatt.split('/')[0] + '-' + r.cCol.split('/')[0])
if(len(lineNum) != len(cSKUs)):
logger.warning('Failed to lookup customer cross reference! Skipping ' + invoiceNum)
logger.warning(itemNum)
logger.warning(itemDesc)
logger.warning(cSKUs)
invoices_to_flag_processed.remove(invoiceNum)
send_alert_html(RECIPIENTS, ('Maharam Invoice: ' + invoiceNum + ' not sent!'), message='There is a cross reference issue. Check the log.')
continue
root = ET.Element("MaharamInvoices")
invoice = ET.SubElement(root, "Invoice")
invoiceInfo = ET.SubElement(invoice, "InvoiceInfo")
ET.SubElement(invoiceInfo, "InvoiceDate").text = date.strftime('%m-%d-%Y')
ET.SubElement(invoiceInfo, "InvoiceNumber").text = invoiceNum
billToAddress = ET.SubElement(invoiceInfo, "BillToAddress")
ET.SubElement(billToAddress, "Address").text = bAddress1
if(len(bAddress2) > 0):
ET.SubElement(billToAddress, "Address").text = bAddress2
if(len(bAddress3) > 0):
ET.SubElement(billToAddress, "Address").text = bAddress3
ET.SubElement(invoiceInfo, "BillToCity").text = bCity
ET.SubElement(invoiceInfo, "BillToState").text = bState
ET.SubElement(invoiceInfo, "BillToPostalCode").text = bZip
ET.SubElement(invoiceInfo, "BillToCountry").text = bCountry
ET.SubElement(invoiceInfo, "AttnName").text = ''
ET.SubElement(invoiceInfo, "AttnEmail").text = ''
ET.SubElement(invoiceInfo, "TermsCode").text = ''
ET.SubElement(invoiceInfo, "TermsDesc").text = terms
purchaseOrder = ET.SubElement(invoice, "PurchaseOrder")
ET.SubElement(purchaseOrder, "PONum").text = customerPO
ET.SubElement(purchaseOrder, "ShipToName").text = ''
shipToAddress = ET.SubElement(purchaseOrder, "ShipToAddress")
ET.SubElement(shipToAddress, "Address").text = sAddress1
if(len(bAddress2) > 0):
ET.SubElement(shipToAddress, "Address").text = sAddress2
if(len(bAddress3) > 0):
ET.SubElement(shipToAddress, "Address").text = sAddress3
ET.SubElement(purchaseOrder, "ShipToCity").text = sCity
ET.SubElement(purchaseOrder, "ShipToState").text = sState
ET.SubElement(purchaseOrder, "ShipToPostalCode").text = sZip
ET.SubElement(purchaseOrder, "ShipToCountry").text = sCountry
for x in range(0, len(lineNum)):
line = ET.SubElement(purchaseOrder, "Line")
ET.SubElement(line, "LineNumber").text = '' #str(lineNum[x])
ET.SubElement(line, "SKU").text = cSKUs[x]
ET.SubElement(line, "ResourceSKU").text = str(itemNum[x])
ET.SubElement(line, "QuantityBilled").text = str(qtyInvoiced[x])
ET.SubElement(line, "UM").text = 'YD'
ET.SubElement(line, "Price").text = str(price[x])
ET.SubElement(line, "Curr").text = 'USD'
tree = ET.ElementTree(root)
tree.write("SEND_MF\\STM_INVOICE_" + str(invoiceNum) + ".xml")
if(len(invoices_to_flag_processed) > 0) and not TEST_MODE:
ex = update(CSPINV00.__table__).where(CSPINV00.IVINV.in_(invoices_to_flag_processed)).values(IVPROCSS=u"Y")
session.execute(ex)
session.commit()
if(len(invoices_to_flag_processed) == 0):
logger.warn('Nothing to send!')
else:
try:
logger.info('Sending e-mail alert...')
send_alert_html(RECIPIENTS, SUBJECT, attachments=glob('D:\\FTP\\SEND_MF\\*.xml'), message=MESSAGE.getvalue())
except:
logger.error('Unable to send e-mail. Is the internet out?')
session.close()
MESSAGE.truncate(0)
if __name__ == '__main__':
createMaharamInvoiceDocuments()