Reworked 867 process and minor updates to others.

master
bleeson 2024-08-20 09:43:11 -07:00
parent 5e9de92626
commit 0a569a5b51
8 changed files with 1166 additions and 122 deletions

View File

@ -49,7 +49,7 @@ def main():
if SHANDEX_846_FILENAME_RE.match(edi_filename.name): if SHANDEX_846_FILENAME_RE.match(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)
#get stock information about WON and store it #get stock information about WON and store it
#pass date from EDI so we can subtract newer stock movements? #pass date from EDI so we can subtract newer stock movements?
x3_won_inventory=get_x3_won_inventory() x3_won_inventory=get_x3_won_inventory()
@ -214,7 +214,8 @@ def stock_count_alert():
msg['Subject'] = 'New Stock Count from Shandex' msg['Subject'] = 'New Stock Count from Shandex'
msg['Precedence'] = 'bulk' msg['Precedence'] = 'bulk'
msg['From'] = 'x3report@stashtea.com' msg['From'] = 'x3report@stashtea.com'
msg['To'] = 'bleeson@stashtea.com'#TODO correct receipientscares msg['To'] = 'isenn@yamamotoyama.com,vgomez@yamamotoyama.com'
msg['Cc'] = 'bleeson@stashtea.com'
emailtext = f'Attached.' emailtext = f'Attached.'
msg.attach(MIMEText(emailtext, 'plain')) msg.attach(MIMEText(emailtext, 'plain'))
for file in EDI_846_ATTACHMENTS.iterdir(): for file in EDI_846_ATTACHMENTS.iterdir():

1077
edi_867_to_table.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ import yamamotoyama # type:ignore
SITE_MAPPING = { SITE_MAPPING = {
'WNJ' : 'SOURCELOGISTICS', 'WNJ' : 'SOURCELOGISTICS',
'WCA' : 'SOURCELOGISTICS', 'WCA' : 'SOURCELOGISTICS',
'WON' : 'SHANDEXTEST ' # TODO CHANGE TO SHANDEX, needs to be 15 characters 'WON' : 'SHANDEX ' #Testing value is SHANDEXTEST with spaces to 15 characters
} }
SHIPPING_CODE_MAPPING = { SHIPPING_CODE_MAPPING = {
@ -35,14 +35,12 @@ SHIPPING_CODE_MAPPING = {
THIS_DIRECTORY = pathlib.Path(__file__).parent THIS_DIRECTORY = pathlib.Path(__file__).parent
X12_SHANDEX = THIS_DIRECTORY / "outgoing" X12_SHANDEX = THIS_DIRECTORY / "outgoing"
# X12_SOURCELOGISTICS = THIS_DIRECTORY / "edi-testing" #test directories
# X12_SHANDEX = THIS_DIRECTORY / "edi-testing"
def main(): def main():
""" """
Do it! Do it!
""" """
with yamamotoyama.get_connection('test') as database: with yamamotoyama.get_connection() as database:
shipments = list(get_shipments(database)) shipments = list(get_shipments(database))
for shipment in shipments: for shipment in shipments:
write_943(database, shipment) write_943(database, shipment)
@ -127,7 +125,7 @@ def write_943(database: records.Connection, shipment: str):
with database.transaction() as _: with database.transaction() as _:
database.query( database.query(
""" """
update [FY23TEST].[SDELIVERY] update [PROD].[SDELIVERY]
set [XX4S_943RDY_0] = 1 set [XX4S_943RDY_0] = 1
where [SOHNUM_0] = :shipment where [SOHNUM_0] = :shipment
""", """,
@ -136,7 +134,7 @@ def write_943(database: records.Connection, shipment: str):
order = get_order_for_shipment(database, shipment) order = get_order_for_shipment(database, shipment)
database.query( database.query(
""" """
update [FY23TEST].[SORDER] update [PROD].[SORDER]
set [XX4S_UDF2_0] = :sent_message set [XX4S_UDF2_0] = :sent_message
where [SOHNUM_0] = :order where [SOHNUM_0] = :order
""", """,
@ -153,7 +151,7 @@ def get_shipment_destination(database: records.Connection, shipment: str) -> str
""" """
select select
[SDH].[BPCORD_0] [SDH].[BPCORD_0]
from [FY23TEST].[SDELIVERY] as [SDH] from [PROD].[SDELIVERY] as [SDH]
where where
[SDH].[SDHNUM_0] = :shipment [SDH].[SDHNUM_0] = :shipment
""", """,
@ -172,7 +170,7 @@ def get_order_for_shipment(database: records.Connection, shipment: str) -> str:
""" """
select select
[SDH].[SOHNUM_0] [SDH].[SOHNUM_0]
from [FY23TEST].[SDELIVERY] as [SDH] from [PROD].[SDELIVERY] as [SDH]
where where
[SDH].[SDHNUM_0] = :shipment [SDH].[SDHNUM_0] = :shipment
""", """,
@ -191,8 +189,8 @@ def get_shipments(database: records.Connection) -> typing.Iterator[str]:
""" """
select select
[SDH].[SDHNUM_0] [SDH].[SDHNUM_0]
from [FY23TEST].[SDELIVERY] as [SDH] from [PROD].[SDELIVERY] as [SDH]
join [FY23TEST].[SORDER] as [SOH] join [PROD].[SORDER] as [SOH]
on [SOH].[SOHNUM_0] = [SDH].[SOHNUM_0] on [SOH].[SOHNUM_0] = [SDH].[SOHNUM_0]
where where
( (
@ -203,7 +201,7 @@ def get_shipments(database: records.Connection) -> typing.Iterator[str]:
) )
and [SDH].[SHIDAT_0] >= {d'2023-10-09'} and [SDH].[SHIDAT_0] >= {d'2023-10-09'}
and ( and (
[SOH].[XX4S_UDF2_0] = '' [SOH].[XX4S_UDF2_0] not like '943%'
or [SDH].[XX4S_943RDY_0] = 2 or [SDH].[XX4S_943RDY_0] = 2
) )
and ( and (
@ -267,7 +265,7 @@ def get_shipment(
,[GROWEI_0] ,[GROWEI_0]
,[CFMFLG_0] ,[CFMFLG_0]
,[SHIDAT_0] ,[SHIDAT_0]
from [FY23TEST].[zyumiddleware_shipment] as [SDH] from [PROD].[zyumiddleware_shipment_shandex] as [SDH]
where where
[SDH].[SDHNUM_0] = :shipment [SDH].[SDHNUM_0] = :shipment
""", """,
@ -549,7 +547,7 @@ class ShipmentDetail(X12):
"", "",
"", "",
self.gtin_or_upc_marker, self.gtin_or_upc_marker,
self.gtin_or_upc_code,#W04-15 self.gtin_or_upc_code.replace(' ',''),#W04-15
] ]
), ),
self.line( self.line(

View File

@ -34,10 +34,6 @@ X12_DIRECTORY = THIS_DIRECTORY / "incoming"
IMPORTS_DIRECTORY = THIS_DIRECTORY / "x3_imports" IMPORTS_DIRECTORY = THIS_DIRECTORY / "x3_imports"
EDI_997_DIRECTORY = THIS_DIRECTORY / "997_processing" EDI_997_DIRECTORY = THIS_DIRECTORY / "997_processing"
SHANDEX_947_FILENAME_RE = re.compile(
r"\A 947_YAMAMOTOYAMA_ \S+ [.]edi \Z", re.X | re.M | re.S
)
#TODO remove this and have a single production name? why is this one different
SHANDEX_947_FILENAME_RE = re.compile( SHANDEX_947_FILENAME_RE = re.compile(
r"\A 947_QTY_STASH-YAMAMOTOYAMA_ \S+ [.]edi \Z", re.X | re.M | re.S r"\A 947_QTY_STASH-YAMAMOTOYAMA_ \S+ [.]edi \Z", re.X | re.M | re.S
) )
@ -80,7 +76,7 @@ def main():
if SHANDEX_947_FILENAME_RE.match(edi_filename.name): if SHANDEX_947_FILENAME_RE.match(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)
combine_zscs() combine_zscs()
@ -154,7 +150,8 @@ def stock_movement_alert(itmref, qty, lot, status):
msg['Subject'] = 'New Stock Change from Shandex' msg['Subject'] = 'New Stock Change from Shandex'
msg['Precedence'] = 'bulk' msg['Precedence'] = 'bulk'
msg['From'] = 'x3report@stashtea.com' msg['From'] = 'x3report@stashtea.com'
msg['To'] = 'bleeson@stashtea.com'#TODO correct receipientscares msg['To'] = 'isenn@yamamotoyama.com, vgomez@yamamotoyama.com'
msg['CC'] = 'bleeson@stashtea.com'
emailtext = f'Item: {itmref}\nQty: {qty}\nLot: {lot}\nStatus: {DAMAGE_CODE_MAPPING[status]}\nReason: {DAMAGE_CODE_DESCRIPTIONS_MAPPING[status]}' emailtext = f'Item: {itmref}\nQty: {qty}\nLot: {lot}\nStatus: {DAMAGE_CODE_MAPPING[status]}\nReason: {DAMAGE_CODE_DESCRIPTIONS_MAPPING[status]}'
msg.attach(MIMEText(emailtext, 'plain')) msg.attach(MIMEText(emailtext, 'plain'))
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp: with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:
@ -222,37 +219,6 @@ class StockChangeSubDetail:
loc: str = "" loc: str = ""
sta: str = "A" sta: str = "A"
# def stojous(self, shipment, item) -> typing.List[str]:
# """
# Convert grouped lot quantities into individual STOJOU records to fit on stockchange
# """
# with yamamotoyama.get_connection('test') as database: #todo remove 'test'
# details = (
# database.query(
# """
# select
# 'S',
# 'A',
# [STJ].[PCU_0],
# cast(cast(-1*[STJ].[QTYSTU_0] as int) as nvarchar),
# [STJ].[LOT_0],
# '',
# ''
# from [FY23TEST].[STOJOU] [STJ] --TODO change to PROD
# where
# [STJ].[VCRNUM_0] = :sdhnum
# and [STJ].[ITMREF_0] = :itmref
# and [STJ].[LOT_0] = :lot
# and [STJ].[TRSTYP_0] = 4
# """,
# sdhnum=shipment,
# itmref=item,
# lot=self.lot,
# )
# .all()
# )
# return details
def convert_to_strings(self) -> typing.List[str]: def convert_to_strings(self) -> typing.List[str]:
""" """
@ -288,7 +254,7 @@ class StockChangeDetail:
itmref: str = "" itmref: str = ""
pcu: str = "" pcu: str = ""
pcustucoe: int = 1 #does this need a lookup? pcustucoe: int = 1 #does this need a lookup?
sta: str = "A" #todo this needs to flip based on the transaction A > R, A > Q, what about Q > A? sta: str = "A"
loctyp: str = "" loctyp: str = ""
loc: str = "" loc: str = ""
lot: str = "" lot: str = ""
@ -564,39 +530,6 @@ class StockChange:
if value: if value:
self._fill_info_from_shipment() self._fill_info_from_shipment()
# def _get_shipment_from_x3(self) -> records.Record:
# """
# Fetch shipment from X3 database.
# """
# with yamamotoyama.get_connection('test') as db_connection:#todo remove 'test'
# return db_connection.query(
# """
# select
# [SDH].[STOFCY_0],
# [SDH].[SDHNUM_0],
# [SDH].[SALFCY_0],
# [SDH].[BPCORD_0],
# [SDH].[CUR_0],
# [SDH].[SOHNUM_0]
# from [FY23TEST].[SDELIVERY] [SDH]--TODO change back to [PROD]
# where
# [SDH].[SDHNUM_0] = :shipment
# """,
# shipment=self.sdhnum,
# ).first()
# def _fill_info_from_shipment(self):
# """
# When we learn the SOHNUM, we can copy information from the sales order.
# """
# result = self._get_shipment_from_x3()
# self.header.stofcy = result.STOFCY_0
# self.header.sdhnum = result.SDHNUM_0
# self.header.salfcy = result.SALFCY_0
# self.header.bpcord = result.BPCORD_0
# self.header.cur = result.CUR_0
# self.header.sohnum = result.SOHNUM_0
def output(self, import_file: typing.TextIO): def output(self, import_file: typing.TextIO):
""" """

View File

@ -22,7 +22,7 @@ import yamamotoyama.x3_imports # type: ignore
THIS_DIRECTORY = pathlib.Path(__file__).parent THIS_DIRECTORY = pathlib.Path(__file__).parent
X12_DIRECTORY = THIS_DIRECTORY / "incoming" X12_DIRECTORY = THIS_DIRECTORY / "incoming"
SHANDEX_997_FILENAME_RE = re.compile( #TODO FIX REGEX SHANDEX_997_FILENAME_RE = re.compile(
r"\A 997_YAMAMOTOYAMA_ .* [.]edi \Z", re.X | re.M | re.S r"\A 997_YAMAMOTOYAMA_ .* [.]edi \Z", re.X | re.M | re.S
) )
@ -46,7 +46,7 @@ def tokens_from_edi_file(
with edi_filename.open(encoding="utf-8", newline="") as edi_file: with edi_filename.open(encoding="utf-8", newline="") as edi_file:
for record in edi_file.read().split("~"): for record in edi_file.read().split("~"):
fields = record.split("*") fields = record.split("*")
if fields[0] in { #TODO see if there are more fields used in vendor EDI if fields[0] in {
"ISA", "ISA",
"GS", "GS",
"ST", "ST",

View File

@ -11,10 +11,12 @@ import edi_997_inbound
import edi_944 import edi_944
import edi_947 import edi_947
import edi_846 import edi_846
import edi_867 import edi_867_to_table
import edi_997_outbound import edi_997_outbound
import update_shandex_dashboard import update_shandex_dashboard
import edi_943 import edi_943
import unprocessed_files_report
import import_867s
THIS_DIRECTORY = pathlib.Path(__file__).parent THIS_DIRECTORY = pathlib.Path(__file__).parent
X12_SHANDEX_OUTGOING = THIS_DIRECTORY / "outgoing" X12_SHANDEX_OUTGOING = THIS_DIRECTORY / "outgoing"
@ -28,22 +30,29 @@ def main():
#pick up files from Shandex #pick up files from Shandex
retrieve_x12_edi_files_shandex() retrieve_x12_edi_files_shandex()
#report on anything not handled
unprocessed_files_report.main()
#process all EDIs that started with Shandex #process all EDIs that started with Shandex
edi_997_inbound.main() edi_997_inbound.main()
edi_944.main() edi_944.main()
edi_947.main() edi_947.main()
#edi_846.main() edi_846.main()
edi_867.main() edi_867_to_table.main()
import_867s.main()
# process all EDIs that start with us # process all EDIs that start with us
edi_943.main() edi_943.main()
edi_997_outbound.main() edi_997_outbound.main()
#send them to Shandex #send them to Shandex
send_x12_edi_files_shandex()#TODO production changes send_x12_edi_files_shandex()
#update dashboard #update dashboard - no longer needed now that it has been moved to the staging database 2024-08-20
# update_shandex_dashboard.main() #update_shandex_dashboard.main()
#report on anything not handled
unprocessed_files_report.main()
@ -69,7 +78,7 @@ def send_x12_edi_files_shandex():
hostname=SHANDEX_SFTP_HOST, username=SHANDEX_SFTP_USERNAME, password=SHANDEX_SFTP_PASSWORD hostname=SHANDEX_SFTP_HOST, username=SHANDEX_SFTP_USERNAME, password=SHANDEX_SFTP_PASSWORD
) )
with ssh_client.open_sftp() as sftp_connection: with ssh_client.open_sftp() as sftp_connection:
sftp_connection.chdir("/Stash/Test/ToShandex") #TODO change to production folder sftp_connection.chdir("/Stash/Prod/ToShandex")
for filename in X12_SHANDEX_OUTGOING.glob("*.edi"): for filename in X12_SHANDEX_OUTGOING.glob("*.edi"):
sftp_connection.put(filename, str(filename.name)) sftp_connection.put(filename, str(filename.name))
shutil.move(filename, X12_SHANDEX_OUTGOING / "archive" / filename.name) shutil.move(filename, X12_SHANDEX_OUTGOING / "archive" / filename.name)
@ -84,11 +93,11 @@ def retrieve_x12_edi_files_shandex():
hostname=SHANDEX_SFTP_HOST, username=SHANDEX_SFTP_USERNAME, password=SHANDEX_SFTP_PASSWORD hostname=SHANDEX_SFTP_HOST, username=SHANDEX_SFTP_USERNAME, password=SHANDEX_SFTP_PASSWORD
) )
with ssh_client.open_sftp() as sftp_connection: with ssh_client.open_sftp() as sftp_connection:
sftp_connection.chdir("/Stash/Test/FromShandex") sftp_connection.chdir("/Stash/Prod/FromShandex")
for filename in sftp_connection.listdir(): for filename in sftp_connection.listdir():
if filename.endswith(".edi"): if filename.endswith(".edi"):
sftp_connection.get(filename, X12_SHANDEX_INCOMING / filename) sftp_connection.get(filename, X12_SHANDEX_INCOMING / filename)
new_filename = f"/Stash/Test/FromShandex/Archive/{filename}" new_filename = f"/Stash/Archive/{filename}"
sftp_connection.rename(filename, new_filename) sftp_connection.rename(filename, new_filename)

View File

@ -1,8 +1,5 @@
Process EDI documents between YU and Shandex Process EDI documents between YU and Shandex
-----------------------------------------------------------
EDI types accepted:
997 Functional acknowledgment Tell Shandex we received an EDI 997 Functional acknowledgment Tell Shandex we received an EDI
944 Stock transfer receipt Receive qty and lots from replenishment shipment 944 Stock transfer receipt Receive qty and lots from replenishment shipment
947 Inventory advice Tell Stash of Q and R status inventory 947 Inventory advice Tell Stash of Q and R status inventory
@ -10,15 +7,19 @@ EDI types accepted:
867 Resale report Tell Stash qty, price, and lot sold 867 Resale report Tell Stash qty, price, and lot sold
943 Stock transfer Tell Shandex qty and lots being shipped 943 Stock transfer Tell Shandex qty and lots being shipped
-----------------------------------------------------------
General operation:
master_controller takes EDI files from the Shandex FTP, processes them, and loads our EDI files. master_controller takes EDI files from the Shandex FTP, processes them, and loads our EDI files.
Sales come in via the 867 script and the shandex_dashboard script stores which files have been received. Individual edi_***.py files process specific types of transactions.
The corresponding control numbers can be matched to X3 Sales Deliveries in the YLICPLATE_0 field.
----------------------------------------------------------- Sales come in via 867 and the shandex_dashboard script stores which files have been received.
Currently runs in the 30 minute X3 recurring task The corresponding control numbers can be matched to X3 Sales Deliveries in the YLICPLATE_0 and YCLIPPERSHIP_0 fields.
To switch between PROD and DEV, database connections need to be passed 'test' -------------------
and the schema names in SQL queries need to be correct for the test folder being used. Issues
-------------------
867s will fail to process if a customer has not been mapped for them in the edi_867.py file.
These problems are logged in the staging database in staging.dbo.shandex_shipments. To reprocess,
add the customer code to the table and check it's is_sent field. Make sure to add the right code to
the dictionary in 867 processing as well.
944s need to be rerun in the edi_944.py script after validation, so that lots appear in the import file.

View File

@ -7,19 +7,13 @@ To reprocess a file, move it to the correct folder on the FTP
import shutil import shutil
import yamamotoyama import yamamotoyama
import pathlib import pathlib
import pprint
THIS_DIRECTORY = pathlib.Path(__file__).parent THIS_DIRECTORY = pathlib.Path(__file__).parent
RECEIVED_867_DIRECTORY = THIS_DIRECTORY / "processed_867s" RECEIVED_867_DIRECTORY = THIS_DIRECTORY / "processed_867s"
ARCHIVE = RECEIVED_867_DIRECTORY / "archive" ARCHIVE = RECEIVED_867_DIRECTORY / "archive"
INSERT_867s = """\
execute [PROD].[insert_shandex_867_data]
:ctrlnum,
:credat;
"""
def main(): def main():
""" """
Do it! Do it!
@ -41,14 +35,31 @@ def remove_completed_867s():
def import_received_867s(data): def import_received_867s(data):
with yamamotoyama.get_connection() as data_base: with yamamotoyama.get_connection() as data_base:
with data_base.transaction(): with data_base.transaction():
data_base.bulk_query(INSERT_867s, data) data_base.query(
"""
EXECUTE [PROD].[insert_shandex_867_data]
:ctrlnum,
:ylicplate,
:credat,
:customer_po,
:shandex_so
""",
ctrlnum=data['ctrlnum']+'-'+data['ylicplate'],
ylicplate=data['ylicplate'],
credat=data['credat'],
customer_po=data['customer_po'],
shandex_so=data['shandex_so'],
)
def process_received_867s(): def process_received_867s():
#pull out the control number on the GS line #pull out the control number on the GS line and the BOL# on PTD
for file in RECEIVED_867_DIRECTORY.iterdir(): for file in RECEIVED_867_DIRECTORY.iterdir():
control_number = '' control_number = ''
transaction_date = '' transaction_date = ''
picking_number = ''
customer_po = ''
shandex_so = ''
data_set = []
if file.name.endswith('.edi'): if file.name.endswith('.edi'):
with file.open(encoding="utf-8", newline="") as edi_file: with file.open(encoding="utf-8", newline="") as edi_file:
for record in edi_file.read().split("~"): for record in edi_file.read().split("~"):
@ -57,12 +68,26 @@ def process_received_867s():
control_number = fields[6] control_number = fields[6]
if fields[0] == 'BPT': if fields[0] == 'BPT':
transaction_date = fields[3] transaction_date = fields[3]
# REF*PO*3L49287E
if fields[0] == 'REF' and fields[1] == 'PO':
customer_po = fields[2]
# REF*IL*O0215276
if fields[0] == 'REF' and fields[1] == 'IL':
shandex_so = fields[2]
if fields[0] == "PTD" and len(fields) > 2:
picking_number = fields[5]
data = { data = {
"ctrlnum":control_number, "ctrlnum":control_number,
"credat":transaction_date "ylicplate":picking_number,
"credat":transaction_date,
"customer_po":customer_po,
"shandex_so":shandex_so,
} }
if data not in data_set:
data_set.append(data)
import_received_867s(data) import_received_867s(data)
if __name__ == "__main__": if __name__ == "__main__":
main() main()