''' 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[a-zA-Z0-9_ \-\']+?)\s(?P\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()