850 addition and other minor changes
parent
a239a09799
commit
dd7fc8f119
|
@ -47,6 +47,7 @@ def main():
|
||||||
#read in the information from Shandex and store it
|
#read in the information from Shandex and store it
|
||||||
for edi_filename in X12_DIRECTORY.iterdir():
|
for edi_filename in X12_DIRECTORY.iterdir():
|
||||||
if SHANDEX_846_FILENAME_RE.match(edi_filename.name):
|
if SHANDEX_846_FILENAME_RE.match(edi_filename.name):
|
||||||
|
pprint.pprint(edi_filename.name)
|
||||||
shandex_inventory=process_file(edi_filename)
|
shandex_inventory=process_file(edi_filename)
|
||||||
# file moved to 997 processing folder to be sent later
|
# file moved to 997 processing folder to be sent later
|
||||||
shutil.move(edi_filename, EDI_997_DIRECTORY / edi_filename.name)
|
shutil.move(edi_filename, EDI_997_DIRECTORY / edi_filename.name)
|
||||||
|
@ -240,7 +241,7 @@ def process_file(edi_filename: pathlib.Path):
|
||||||
for fields in tokens_from_edi_file(edi_filename):
|
for fields in tokens_from_edi_file(edi_filename):
|
||||||
if fields[0] == "BIA":
|
if fields[0] == "BIA":
|
||||||
advice_date = fields[4]
|
advice_date = fields[4]
|
||||||
if fields[0] == 'LIN':
|
if fields[0] == 'LIN' and len(fields) > 5:
|
||||||
if product != '': #check loop entry
|
if product != '': #check loop entry
|
||||||
if product not in shandex_inventory: #if we haven't seen the product yet add it
|
if product not in shandex_inventory: #if we haven't seen the product yet add it
|
||||||
# pprint.pprint('product was not found')
|
# pprint.pprint('product was not found')
|
||||||
|
|
|
@ -0,0 +1,310 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Consume a 850 file from Shandex, and translate into a Sage X3
|
||||||
|
readable file - import template ZPOH.
|
||||||
|
For Shandex we also need to reply with a 997
|
||||||
|
"""
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
import dataclasses
|
||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
|
import functools
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import typing
|
||||||
|
import pprint
|
||||||
|
import smtplib
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
|
||||||
|
import records # type: ignore
|
||||||
|
|
||||||
|
import yamamotoyama # type: ignore
|
||||||
|
import yamamotoyama.x3_imports # type: ignore
|
||||||
|
|
||||||
|
THIS_DIRECTORY = pathlib.Path(__file__).parent
|
||||||
|
X12_DIRECTORY = THIS_DIRECTORY / "incoming"
|
||||||
|
IMPORTS_DIRECTORY = THIS_DIRECTORY / "x3_imports"
|
||||||
|
EDI_997_DIRECTORY = THIS_DIRECTORY / "997_processing"
|
||||||
|
|
||||||
|
SHANDEX_850_FILENAME_RE = re.compile(
|
||||||
|
r"\A 850_STASH-YAMAMOTOYAMA_ \S+ [.]edi \Z", re.X | re.M | re.S
|
||||||
|
)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Do it!
|
||||||
|
"""
|
||||||
|
for edi_filename in X12_DIRECTORY.iterdir():
|
||||||
|
if SHANDEX_850_FILENAME_RE.match(edi_filename.name):
|
||||||
|
process_file(edi_filename)
|
||||||
|
# file moved to 997 processing folder to be sent later
|
||||||
|
shutil.move(edi_filename, EDI_997_DIRECTORY / edi_filename.name)
|
||||||
|
combine_zpohs()
|
||||||
|
|
||||||
|
|
||||||
|
def new_850_alert(ordref, orddat):
|
||||||
|
msg = MIMEMultipart()
|
||||||
|
msg['Subject'] = 'New PO from Shandex'
|
||||||
|
msg['Precedence'] = 'bulk'
|
||||||
|
msg['From'] = 'x3report@stashtea.com'
|
||||||
|
msg['To'] = 'icortes@yamamotoyama.com'
|
||||||
|
msg['CC'] = 'bleeson@stashtea.com'
|
||||||
|
emailtext = f'Ref: {ordref}\nDate: {orddat}'
|
||||||
|
msg.attach(MIMEText(emailtext, 'plain'))
|
||||||
|
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:
|
||||||
|
smtp.login(user='x3reportmk2@yamamotoyama.com', password=r'n</W<7fr"VD~\2&[pZc5')
|
||||||
|
smtp.send_message(msg)
|
||||||
|
|
||||||
|
def combine_zpohs():
|
||||||
|
"""
|
||||||
|
Collect all ZPOH imports into a single file for easy import.
|
||||||
|
"""
|
||||||
|
archive_directory = IMPORTS_DIRECTORY / "archive"
|
||||||
|
archive_directory.mkdir(exist_ok=True)
|
||||||
|
with (IMPORTS_DIRECTORY / "ZPOH.dat").open(
|
||||||
|
"w", encoding="utf-8", newline="\n"
|
||||||
|
) as combined_import_file:
|
||||||
|
for individual_import_filename in IMPORTS_DIRECTORY.glob(
|
||||||
|
"ZPOH_*.dat"
|
||||||
|
):
|
||||||
|
with individual_import_filename.open(
|
||||||
|
"r", encoding="utf-8", newline="\n"
|
||||||
|
) as individual_import_file:
|
||||||
|
for line in individual_import_file:
|
||||||
|
combined_import_file.write(line)
|
||||||
|
shutil.move(
|
||||||
|
individual_import_filename,
|
||||||
|
archive_directory / individual_import_filename.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tokens_from_edi_file(
|
||||||
|
edi_filename: pathlib.Path,
|
||||||
|
) -> typing.Iterator[typing.List[str]]:
|
||||||
|
"""
|
||||||
|
Read tokens from EDI file
|
||||||
|
"""
|
||||||
|
with edi_filename.open(encoding="utf-8", newline="") as edi_file:
|
||||||
|
for record in edi_file.read().split("~"):
|
||||||
|
fields = record.split("*")
|
||||||
|
if fields[0] in {
|
||||||
|
"ISA",
|
||||||
|
"ST",
|
||||||
|
"N2",
|
||||||
|
"N3",
|
||||||
|
"N4",
|
||||||
|
}:
|
||||||
|
continue
|
||||||
|
yield fields
|
||||||
|
|
||||||
|
|
||||||
|
def process_file(edi_filename: pathlib.Path):
|
||||||
|
"""
|
||||||
|
Convert a specific EDI file into an import file.
|
||||||
|
"""
|
||||||
|
with yamamotoyama.get_connection() as database:
|
||||||
|
purchase_order = PO()
|
||||||
|
pohnum = ''
|
||||||
|
for fields in tokens_from_edi_file(edi_filename):
|
||||||
|
#BEG*00*SA*PO0040865**20241209
|
||||||
|
if fields[0] == "BEG":
|
||||||
|
_, _, _, ordref, _, orddat = fields[:6]
|
||||||
|
purchase_order.header.ordref = ordref
|
||||||
|
purchase_order.header.orddat = datetime.datetime.strptime(
|
||||||
|
orddat, "%Y%m%d"
|
||||||
|
).date() # 20230922
|
||||||
|
purchase_order.header.pohfcy='WON'
|
||||||
|
purchase_order.header.bpsnum='PMW'
|
||||||
|
purchase_order.header.cur='USD'
|
||||||
|
#DTM*010*20250109
|
||||||
|
if fields[0] == "DTM" and fields[1] == '010':
|
||||||
|
expected_ship_date = fields[2]
|
||||||
|
#PO1*1*2688*CA*15.02**VP*C08225*IN*10077652082255
|
||||||
|
if fields[0] == "PO1":
|
||||||
|
_, lineno, qty_str, uom, pricestr, _, _, itmref, gtin = fields[:9]
|
||||||
|
detail = PODetail(
|
||||||
|
itmref=itmref,
|
||||||
|
itmdes=get_itmdes(itmref, database),
|
||||||
|
prhfcy='WON',
|
||||||
|
uom=uom,
|
||||||
|
qtyuom=int(qty_str),
|
||||||
|
extrcpdat=expected_ship_date,
|
||||||
|
gropri=pricestr,
|
||||||
|
)
|
||||||
|
purchase_order.append(detail)
|
||||||
|
time_stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
new_850_alert(ordref, purchase_order.header.orddat)#TODO new alert type, after it hits X3?
|
||||||
|
with yamamotoyama.x3_imports.open_import_file(
|
||||||
|
IMPORTS_DIRECTORY / f"ZPOH_{purchase_order.header.ordref}_{time_stamp}.dat"
|
||||||
|
) as import_file:
|
||||||
|
purchase_order.output(import_file)
|
||||||
|
|
||||||
|
|
||||||
|
def get_itmdes(itmref, database):
|
||||||
|
result = database.query(
|
||||||
|
"""
|
||||||
|
select
|
||||||
|
ITMDES1_0
|
||||||
|
from PROD.ITMMASTER
|
||||||
|
where
|
||||||
|
ITMREF_0 = :itmref
|
||||||
|
""",
|
||||||
|
itmref=itmref,
|
||||||
|
).first()['ITMDES1_0']
|
||||||
|
return result
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class PODetail:
|
||||||
|
"""
|
||||||
|
Information that goes on a PO detail line, taken from ZPOH template.
|
||||||
|
"""
|
||||||
|
|
||||||
|
itmref: str = ""
|
||||||
|
itmdes: str = ""
|
||||||
|
prhfcy: str = ""
|
||||||
|
uom: str = ""
|
||||||
|
qtyuom: int = 0
|
||||||
|
extrcpdat: datetime.date = datetime.date(1753, 1, 1)
|
||||||
|
gropri: decimal.Decimal = 0
|
||||||
|
discrgval1: decimal.Decimal = 0
|
||||||
|
discrgval2: decimal.Decimal = 0
|
||||||
|
discrgval3: decimal.Decimal = 0
|
||||||
|
pjt: str = ""
|
||||||
|
vat: str = ''
|
||||||
|
star91: str = ""
|
||||||
|
star92: str = ""
|
||||||
|
|
||||||
|
def convert_to_strings(self) -> typing.List[str]:
|
||||||
|
"""
|
||||||
|
Convert to strings for X3 import writing.
|
||||||
|
"""
|
||||||
|
def fix_uom(uom):
|
||||||
|
x3_uom = ''
|
||||||
|
if uom == 'CA':
|
||||||
|
x3_uom = 'CS'
|
||||||
|
else:
|
||||||
|
x3_uom = uom
|
||||||
|
return x3_uom
|
||||||
|
|
||||||
|
return yamamotoyama.x3_imports.convert_to_strings(
|
||||||
|
[
|
||||||
|
"L",
|
||||||
|
self.itmref,
|
||||||
|
self.itmdes,
|
||||||
|
self.prhfcy,
|
||||||
|
fix_uom(self.uom),
|
||||||
|
self.qtyuom,
|
||||||
|
self.extrcpdat,
|
||||||
|
self.gropri,
|
||||||
|
self.discrgval1,
|
||||||
|
self.discrgval2,
|
||||||
|
self.discrgval3,
|
||||||
|
self.pjt,
|
||||||
|
self.vat,
|
||||||
|
self.star91,
|
||||||
|
self.star92,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class POHeader:
|
||||||
|
"""
|
||||||
|
Information that goes on a po header, taken from ZPOH template.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pohfcy: str = ""
|
||||||
|
pohnum: str = ""
|
||||||
|
orddat: datetime.date = datetime.date(1753, 1, 1)
|
||||||
|
bpsnum: str = ""
|
||||||
|
ordref: str = ""
|
||||||
|
bpsnum: str = ""
|
||||||
|
cur: str = "USD"
|
||||||
|
star71 = ""
|
||||||
|
star72 = ""
|
||||||
|
star81 = ""
|
||||||
|
star82 = ""
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to_strings(self) -> typing.List[str]:
|
||||||
|
"""
|
||||||
|
Convert to X3 import line
|
||||||
|
"""
|
||||||
|
return yamamotoyama.x3_imports.convert_to_strings(
|
||||||
|
[
|
||||||
|
"E",
|
||||||
|
self.pohfcy,
|
||||||
|
'',#self.pohnum,
|
||||||
|
self.orddat.strftime("%Y%m%d"),
|
||||||
|
self.bpsnum,
|
||||||
|
self.ordref,
|
||||||
|
self.cur,
|
||||||
|
self.star71,
|
||||||
|
self.star72,
|
||||||
|
self.star81,
|
||||||
|
self.star82,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PODetailList:
|
||||||
|
"""
|
||||||
|
List of PO details
|
||||||
|
"""
|
||||||
|
|
||||||
|
_details: typing.List[PODetail]
|
||||||
|
_item_set: typing.Set[str]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._details = []
|
||||||
|
self._item_set = set()
|
||||||
|
|
||||||
|
def append(
|
||||||
|
self,
|
||||||
|
po_detail: PODetail
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Append
|
||||||
|
"""
|
||||||
|
self._details.append(po_detail)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._details)
|
||||||
|
|
||||||
|
|
||||||
|
class PO:
|
||||||
|
"""
|
||||||
|
Warehouse po, both header & details
|
||||||
|
"""
|
||||||
|
|
||||||
|
header: POHeader
|
||||||
|
details: PODetailList
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.header = POHeader()
|
||||||
|
self.details = PODetailList()
|
||||||
|
|
||||||
|
def append(
|
||||||
|
self,
|
||||||
|
po_detail: PODetail,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Add detail information.
|
||||||
|
"""
|
||||||
|
self.details.append(po_detail)
|
||||||
|
|
||||||
|
def output(self, import_file: typing.TextIO):
|
||||||
|
"""
|
||||||
|
Output entire po to import_file.
|
||||||
|
"""
|
||||||
|
output = functools.partial(
|
||||||
|
yamamotoyama.x3_imports.output_with_file, import_file
|
||||||
|
)
|
||||||
|
output(self.header.convert_to_strings())
|
||||||
|
for detail in self.details:
|
||||||
|
output(detail.convert_to_strings())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -81,6 +81,8 @@ X3_CUSTOMER_MAPPING = {
|
||||||
'PURI1000_PURIBC' : 'PURI0003',
|
'PURI1000_PURIBC' : 'PURI0003',
|
||||||
'PURI1000_PURIAB' : 'PURI0004',
|
'PURI1000_PURIAB' : 'PURI0004',
|
||||||
'AMAZ1200_YEG2' : 'AMAZ0179',
|
'AMAZ1200_YEG2' : 'AMAZ0179',
|
||||||
|
'HORI1000_0000' : 'HORI0001',
|
||||||
|
'NATI1100_NATION' : 'NATI0004',
|
||||||
}
|
}
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
40
edi_943.py
40
edi_943.py
|
@ -99,6 +99,26 @@ def write_943(database: records.Connection, shipment: str):
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
site = str(get_shipment_destination(database, shipment))
|
site = str(get_shipment_destination(database, shipment))
|
||||||
datestamp_string = now.strftime("%Y-%m-%d-%H-%M-%S")
|
datestamp_string = now.strftime("%Y-%m-%d-%H-%M-%S")
|
||||||
|
#2024-09-25 never sent multiple 943s, mark them as sent before processing
|
||||||
|
with database.transaction() as _:
|
||||||
|
database.query(
|
||||||
|
"""
|
||||||
|
update [PROD].[SDELIVERY]
|
||||||
|
set [XX4S_943RDY_0] = 1
|
||||||
|
where [SOHNUM_0] = :shipment
|
||||||
|
""",
|
||||||
|
shipment=shipment,
|
||||||
|
)
|
||||||
|
order = get_order_for_shipment(database, shipment)
|
||||||
|
database.query(
|
||||||
|
"""
|
||||||
|
update [PROD].[SORDER]
|
||||||
|
set [XX4S_UDF2_0] = :sent_message
|
||||||
|
where [SOHNUM_0] = :order
|
||||||
|
""",
|
||||||
|
order=order,
|
||||||
|
sent_message=f"943 Sent {datetime.date.today().isoformat()}",
|
||||||
|
)
|
||||||
with (X12_SHANDEX / f"{site}-{shipment}-{datestamp_string}-943.edi").open(
|
with (X12_SHANDEX / f"{site}-{shipment}-{datestamp_string}-943.edi").open(
|
||||||
"w", encoding="utf-8", newline="\n"
|
"w", encoding="utf-8", newline="\n"
|
||||||
) as x12_file:
|
) as x12_file:
|
||||||
|
@ -122,25 +142,7 @@ def write_943(database: records.Connection, shipment: str):
|
||||||
package_count += detail.qtystu_0
|
package_count += detail.qtystu_0
|
||||||
if header:
|
if header:
|
||||||
output(header.footer(package_count, detail_count))
|
output(header.footer(package_count, detail_count))
|
||||||
with database.transaction() as _:
|
|
||||||
database.query(
|
|
||||||
"""
|
|
||||||
update [PROD].[SDELIVERY]
|
|
||||||
set [XX4S_943RDY_0] = 1
|
|
||||||
where [SOHNUM_0] = :shipment
|
|
||||||
""",
|
|
||||||
shipment=shipment,
|
|
||||||
)
|
|
||||||
order = get_order_for_shipment(database, shipment)
|
|
||||||
database.query(
|
|
||||||
"""
|
|
||||||
update [PROD].[SORDER]
|
|
||||||
set [XX4S_UDF2_0] = :sent_message
|
|
||||||
where [SOHNUM_0] = :order
|
|
||||||
""",
|
|
||||||
order=order,
|
|
||||||
sent_message=f"943 Sent {datetime.date.today().isoformat()}",
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_shipment_destination(database: records.Connection, shipment: str) -> str:
|
def get_shipment_destination(database: records.Connection, shipment: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -74,6 +74,7 @@ def main():
|
||||||
"""
|
"""
|
||||||
for edi_filename in X12_DIRECTORY.iterdir():
|
for edi_filename in X12_DIRECTORY.iterdir():
|
||||||
if SHANDEX_947_FILENAME_RE.match(edi_filename.name):
|
if SHANDEX_947_FILENAME_RE.match(edi_filename.name):
|
||||||
|
pprint.pprint(edi_filename.name)
|
||||||
process_file(edi_filename)
|
process_file(edi_filename)
|
||||||
# file moved to 997 processing folder to be sent later
|
# file moved to 997 processing folder to be sent later
|
||||||
shutil.move(edi_filename, EDI_997_DIRECTORY / edi_filename.name)
|
shutil.move(edi_filename, EDI_997_DIRECTORY / edi_filename.name)
|
||||||
|
@ -140,6 +141,7 @@ def gtin_lookup(gtin):
|
||||||
and [ITF].[STOFCY_0] = 'WON'
|
and [ITF].[STOFCY_0] = 'WON'
|
||||||
where
|
where
|
||||||
replace([ITM].[ZCASEUPC_0],' ','') = :zcaseupc
|
replace([ITM].[ZCASEUPC_0],' ','') = :zcaseupc
|
||||||
|
or replace([ITM].[EANCOD_0],' ','') = :zcaseupc
|
||||||
""",
|
""",
|
||||||
zcaseupc=gtin,
|
zcaseupc=gtin,
|
||||||
).first()["ITMREF_0"]
|
).first()["ITMREF_0"]
|
||||||
|
|
|
@ -24,13 +24,6 @@ SELECT_STATEMENT = """
|
||||||
and SDH.bpcord <> ''
|
and SDH.bpcord <> ''
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SELECT_STATEMENT_TESTING = """
|
|
||||||
select
|
|
||||||
PO
|
|
||||||
from staging.dbo.shandex_shipments SDH
|
|
||||||
where
|
|
||||||
PO = '4542_O0216777'
|
|
||||||
"""
|
|
||||||
|
|
||||||
HEADER_STATEMENT = """
|
HEADER_STATEMENT = """
|
||||||
select
|
select
|
||||||
|
@ -178,7 +171,7 @@ SUBDETAIL_NAMES = ['S','sta','pcu','qtypcu','loc','lot','sernum']
|
||||||
|
|
||||||
def get_shipments(database):
|
def get_shipments(database):
|
||||||
with database.transaction():
|
with database.transaction():
|
||||||
result = database.query(SELECT_STATEMENT_TESTING).all()#TODO REMOVE TESTING
|
result = database.query(SELECT_STATEMENT).all()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_shipment_headers(database, po):
|
def get_shipment_headers(database, po):
|
||||||
|
|
Loading…
Reference in New Issue