From 0b6e5d328304a1e26ae65a44d6b8bda5d8b6fd93 Mon Sep 17 00:00:00 2001 From: Wes Date: Wed, 24 Jun 2020 23:04:02 -0400 Subject: [PATCH] Adding customer invoice interface example --- mf_810.py | 268 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 mf_810.py diff --git a/mf_810.py b/mf_810.py new file mode 100644 index 0000000..38ce471 --- /dev/null +++ b/mf_810.py @@ -0,0 +1,268 @@ +''' +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 = ['WRay@glenraven.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()