Production updates
parent
bb8cf98dbb
commit
5c4b689a16
675
edi_846.py
675
edi_846.py
|
@ -1,19 +1,11 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Consume a 846 file from 3PLs, and translate into a
|
||||
inventory comparison report that could be used as a stock count?
|
||||
inventory comparison report
|
||||
For Shadex we also need to reply with a 997
|
||||
947 is warehouse advice, to alert us of damages or amount changes from something
|
||||
like a count
|
||||
|
||||
"""
|
||||
#what about serial numbers?
|
||||
#status on L line?
|
||||
#remove negative line, needs to look up stocou? not sure.
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
import dataclasses
|
||||
import datetime
|
||||
import decimal
|
||||
import functools
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
|
@ -22,6 +14,7 @@ import smtplib
|
|||
import pprint
|
||||
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.application import MIMEApplication
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
import records # type: ignore
|
||||
|
@ -33,9 +26,11 @@ THIS_DIRECTORY = pathlib.Path(__file__).parent
|
|||
X12_DIRECTORY = THIS_DIRECTORY / "incoming"
|
||||
IMPORTS_DIRECTORY = THIS_DIRECTORY / "x3_imports"
|
||||
EDI_997_DIRECTORY = THIS_DIRECTORY / "997_processing"
|
||||
EDI_846_ATTACHMENTS = THIS_DIRECTORY / "846_reports"
|
||||
EDI_846_ATTACHMENTS_ARCHIVE = EDI_846_ATTACHMENTS / "archive"
|
||||
|
||||
SHANDEX_846_FILENAME_RE = re.compile(
|
||||
r"\A 846_YAMAMOTOYAMA_ \S+ [.]edi \Z", re.X | re.M | re.S
|
||||
r"\A 846_STASH-YAMAMOTOYAMA_ \S+ [.]edi \Z", re.X | re.M | re.S
|
||||
)
|
||||
|
||||
SHANDEX_STATUS = {
|
||||
|
@ -49,11 +44,61 @@ def main():
|
|||
"""
|
||||
Do it!
|
||||
"""
|
||||
#read in the information from Shandex and store it
|
||||
for edi_filename in X12_DIRECTORY.iterdir():
|
||||
if SHANDEX_846_FILENAME_RE.match(edi_filename.name):
|
||||
process_file(edi_filename)
|
||||
shandex_inventory=process_file(edi_filename)
|
||||
# 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
|
||||
#pass date from EDI so we can subtract newer stock movements?
|
||||
x3_won_inventory=get_x3_won_inventory()
|
||||
#write out an excel file with the stock from Shandex with X3 next to it, then include anything missing
|
||||
compare_inventory(shandex_inventory, x3_won_inventory)
|
||||
stock_count_alert()
|
||||
|
||||
|
||||
def compare_inventory(shandex_inventory, x3_inventory):
|
||||
today = datetime.datetime.today()
|
||||
today = today.strftime('%Y-%m-%d')
|
||||
with open(EDI_846_ATTACHMENTS / f'inventory_comparison_{today}.csv', 'w', newline='') as outfile:
|
||||
outfile.write(','.join(['Site','Item','Description','Lot','X3 qty','Shandex qty']))#header
|
||||
outfile.write('\n')
|
||||
for record in x3_inventory:
|
||||
site = record["STOFCY_0"]
|
||||
item = record["ITMREF_0"]
|
||||
des = record["ITMDES1_0"]
|
||||
lot = record["LOT_0"]
|
||||
qty = str(record["QTY"])
|
||||
outfile.write(','.join([site, item, des, lot, qty]))
|
||||
outfile.write(',')
|
||||
if item in shandex_inventory:
|
||||
if lot in shandex_inventory[item]:
|
||||
outfile.write(str(shandex_inventory[item][lot]))
|
||||
# pprint.pprint(shandex_inventory[item])
|
||||
del shandex_inventory[item][lot]
|
||||
# pprint.pprint(shandex_inventory[item])
|
||||
if len(shandex_inventory[item]) == 0:
|
||||
# pprint.pprint('entire del')
|
||||
# pprint.pprint(shandex_inventory[item])
|
||||
del shandex_inventory[item]
|
||||
# pprint.pprint(shandex_inventory[item])
|
||||
else:
|
||||
outfile.write('0')#lot not found
|
||||
else:
|
||||
outfile.write('0')#item not found
|
||||
outfile.write('\n')
|
||||
#write the rest of shandex inventory
|
||||
if len(shandex_inventory) > 0:
|
||||
outfile.write('Shandex only')
|
||||
outfile.write('\n')
|
||||
for item in shandex_inventory:
|
||||
# pprint.pprint(item)
|
||||
for lot in shandex_inventory[item]:
|
||||
# pprint.pprint(lot)
|
||||
qty = str(shandex_inventory[item][lot])
|
||||
outfile.write(','.join([item, lot, qty]))
|
||||
outfile.write('\n')
|
||||
|
||||
|
||||
def tokens_from_edi_file(
|
||||
|
@ -68,9 +113,8 @@ def tokens_from_edi_file(
|
|||
if fields[0] in {
|
||||
"ISA",
|
||||
"GS"
|
||||
"N1",
|
||||
"N9",
|
||||
"SE",
|
||||
"ST",
|
||||
"RED",
|
||||
"GE",
|
||||
"IEA"
|
||||
}:
|
||||
|
@ -78,34 +122,107 @@ def tokens_from_edi_file(
|
|||
yield fields
|
||||
|
||||
|
||||
def gtin_lookup(gtin):
|
||||
with yamamotoyama.get_connection('test') as db_connection:#todo remove 'test'
|
||||
return db_connection.query(
|
||||
"""
|
||||
def get_x3_won_inventory():
|
||||
#TODO correct the dates used in stock_issues?
|
||||
with yamamotoyama.get_connection() as db_connection:
|
||||
return db_connection.query(
|
||||
"""
|
||||
with stock_issues as (
|
||||
select
|
||||
STJ.STOFCY_0,
|
||||
STJ.ITMREF_0,
|
||||
STJ.LOT_0,
|
||||
sum(STJ.QTYSTU_0) [QTYSTU_0]
|
||||
from PROD.STOJOU STJ
|
||||
where
|
||||
STJ.STOFCY_0 = 'WON'
|
||||
and STJ.IPTDAT_0 between getdate()-1 and getdate()
|
||||
group by
|
||||
STJ.STOFCY_0,
|
||||
STJ.ITMREF_0,
|
||||
STJ.LOT_0
|
||||
)
|
||||
select
|
||||
[ITM].[ITMREF_0],
|
||||
[ITM].[ITMDES1_0],
|
||||
[ITM].[EANCOD_0],
|
||||
[ITM].[ZCASEUPC_0]
|
||||
from [FY23TEST].[ITMMASTER] [ITM]--TODO change back to [PROD]
|
||||
join [FY23TEST].[ITMFACILIT] [ITF]
|
||||
on [ITM].[ITMREF_0] = [ITF].[ITMREF_0]
|
||||
and [ITF].[STOFCY_0] = 'WON'
|
||||
where
|
||||
[ITM].[ZCASEUPC_0] = :zcaseupc
|
||||
""",
|
||||
zcaseupc=gtin,
|
||||
).first()["ITMREF_0"]
|
||||
SLF.STOFCY_0,
|
||||
SLF.ITMREF_0,
|
||||
ITM.ITMDES1_0,
|
||||
SLF.LOT_0,
|
||||
cast(sum(SLF.AAACUMQTY_0 + SLF.QQQCUMQTY_0 + SLF.RRRCUMQTY_0 - coalesce(stock_issues.QTYSTU_0,0)) as integer) as QTY,
|
||||
SLF.AVC_0
|
||||
from PROD.STOLOTFCY SLF
|
||||
join PROD.ITMMASTER ITM on
|
||||
SLF.ITMREF_0 = ITM.ITMREF_0
|
||||
left join stock_issues
|
||||
on SLF.ITMREF_0 = stock_issues.ITMREF_0
|
||||
and SLF.STOFCY_0 = stock_issues.STOFCY_0
|
||||
and SLF.LOT_0 = stock_issues.LOT_0
|
||||
where
|
||||
SLF.STOFCY_0 = 'WON'
|
||||
group by
|
||||
SLF.STOFCY_0,
|
||||
SLF.ITMREF_0,
|
||||
ITM.ITMDES1_0,
|
||||
SLF.LOT_0,
|
||||
SLF.AVC_0
|
||||
order by 1, 2
|
||||
""",
|
||||
#startdate=edi_date,
|
||||
).all()
|
||||
|
||||
def gtin_lookup(gtin):
|
||||
with yamamotoyama.get_connection() as db_connection:
|
||||
itmref = db_connection.query(
|
||||
"""
|
||||
select
|
||||
[ITM].[ITMREF_0],
|
||||
[ITM].[ITMDES1_0],
|
||||
[ITM].[EANCOD_0],
|
||||
[ITM].[ZCASEUPC_0]
|
||||
from [PROD].[ITMMASTER] [ITM]
|
||||
join [PROD].[ITMFACILIT] [ITF]
|
||||
on [ITM].[ITMREF_0] = [ITF].[ITMREF_0]
|
||||
and [ITF].[STOFCY_0] = 'WON'
|
||||
where
|
||||
replace([ITM].[ZCASEUPC_0],' ','') = :zcaseupc
|
||||
""",
|
||||
zcaseupc=gtin,
|
||||
).first()
|
||||
if itmref is None:
|
||||
itmref = db_connection.query(
|
||||
"""
|
||||
select
|
||||
[ITM].[ITMREF_0],
|
||||
[ITM].[ITMDES1_0],
|
||||
[ITM].[EANCOD_0],
|
||||
[ITM].[ZCASEUPC_0]
|
||||
from [PROD].[ITMMASTER] [ITM]
|
||||
join [PROD].[ITMFACILIT] [ITF]
|
||||
on [ITM].[ITMREF_0] = [ITF].[ITMREF_0]
|
||||
and [ITF].[STOFCY_0] = 'WON'
|
||||
where
|
||||
replace([ITM].[EANCOD_0],' ','') = :zcaseupc
|
||||
""",
|
||||
zcaseupc=gtin,
|
||||
).first()["ITMREF_0"]
|
||||
else:
|
||||
itmref = itmref["ITMREF_0"]
|
||||
return itmref
|
||||
|
||||
|
||||
def stock_movement_alert(itmref, qty, lot, status):
|
||||
def stock_count_alert():
|
||||
msg = MIMEMultipart()
|
||||
msg['Subject'] = 'New Stock Change from Shandex'
|
||||
msg['Subject'] = 'New Stock Count from Shandex'
|
||||
msg['Precedence'] = 'bulk'
|
||||
msg['From'] = 'x3report@stashtea.com'
|
||||
msg['To'] = 'bleeson@stashtea.com'#TODO correct receipientscares
|
||||
emailtext = f'Item: {itmref}\nQty: {qty}\nLot: {lot}\nStatus: {DAMAGE_CODE_MAPPING[status]}\nReason: {DAMAGE_CODE_DESCRIPTIONS_MAPPING[status]}'
|
||||
emailtext = f'Attached.'
|
||||
msg.attach(MIMEText(emailtext, 'plain'))
|
||||
for file in EDI_846_ATTACHMENTS.iterdir():
|
||||
if file.name.endswith('.csv'):
|
||||
part = MIMEApplication(open(file, 'rb').read())
|
||||
part['Content-Disposition'] = f'attachment; filename="{file.name}"'
|
||||
msg.attach(part)
|
||||
shutil.move(file, EDI_846_ATTACHMENTS_ARCHIVE / file.name)
|
||||
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)
|
||||
|
@ -115,450 +232,52 @@ def process_file(edi_filename: pathlib.Path):
|
|||
"""
|
||||
Convert a specific EDI file into an import file.
|
||||
"""
|
||||
warehouse_stockchange = StockChange()
|
||||
vcrlin = 0
|
||||
shandex_inventory = {} #all inventory
|
||||
product = ''
|
||||
lot = ''
|
||||
qty = 0
|
||||
for fields in tokens_from_edi_file(edi_filename):
|
||||
if fields[0] == "G62":
|
||||
iptdat = fields[2]
|
||||
warehouse_stockchange.header.iptdat = datetime.datetime.strptime(
|
||||
iptdat, "%Y%m%d"
|
||||
).date()
|
||||
if fields[0] == 'W15':
|
||||
transaction_number = fields[2]
|
||||
if fields[0] == "W19":
|
||||
vcrlin += 1000
|
||||
# W19*AV*35*CA**UK*10077652082651***03022026C
|
||||
_, status, qty, uom, _, _, gtin, _, _, lot = fields[:10]
|
||||
if fields[0] == "BIA":
|
||||
advice_date = fields[4]
|
||||
if fields[0] == 'LIN':
|
||||
if product != '': #check loop entry
|
||||
if product not in shandex_inventory: #if we haven't seen the product yet add it
|
||||
# pprint.pprint('product was not found')
|
||||
shandex_inventory[product] = {lot : qty}
|
||||
# pprint.pprint(shandex_inventory)
|
||||
else: #we've seen this product, have we seen the lot
|
||||
if lot not in shandex_inventory[product]:#if not, add it
|
||||
shandex_inventory[product][lot] = qty
|
||||
else:
|
||||
shandex_inventory[product][lot] += qty #if we have add to it
|
||||
# pprint.pprint('product: ' + product)
|
||||
# pprint.pprint('lot: ' + lot)
|
||||
# pprint.pprint('qty: ' + str(qty))
|
||||
#LIN**SK*077652972160*LT*31052026A
|
||||
gtin = fields[3]
|
||||
lot = fields[5]
|
||||
product = gtin_lookup(gtin)
|
||||
stock_movement_alert(product, qty, lot, status)
|
||||
if status in EMAIL_ONLY_CODES:
|
||||
return
|
||||
warehouse_stockchange.header.vcrdes = DAMAGE_CODE_DESCRIPTIONS_MAPPING[status]
|
||||
subdetail = StockChangeSubDetail(
|
||||
qtypcu=int(qty),
|
||||
qtystu=int(qty),
|
||||
sta=DAMAGE_CODE_MAPPING[status],
|
||||
pcu=uom
|
||||
)
|
||||
detail_line = StockChangeDetail(
|
||||
vcrlin=vcrlin,
|
||||
itmref=product,
|
||||
pcu=uom,
|
||||
sta=DAMAGE_CODE_SOURCE_MAPPING[status],
|
||||
lot=lot
|
||||
)
|
||||
warehouse_stockchange.append(
|
||||
detail_line,
|
||||
subdetail,
|
||||
)
|
||||
time_stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
with yamamotoyama.x3_imports.open_import_file(
|
||||
IMPORTS_DIRECTORY / f"ZSCS_{transaction_number}_{time_stamp}.dat"
|
||||
) as import_file:
|
||||
warehouse_stockchange.output(import_file)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StockChangeSubDetail:
|
||||
"""
|
||||
Information that goes onto a stockchange sub-detail line, taken from ZPTHI template.
|
||||
"""
|
||||
|
||||
pcu: str = ""
|
||||
qtypcu: int = 0
|
||||
qtystu: int = 0
|
||||
loc: str = ""
|
||||
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]:
|
||||
"""
|
||||
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(
|
||||
[
|
||||
"S",
|
||||
fix_uom(self.pcu),
|
||||
self.qtypcu,
|
||||
self.qtystu,
|
||||
self.loc,
|
||||
self.sta,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StockChangeDetail:
|
||||
"""
|
||||
Information that goes on a stockchange detail line, taken from ZPTHI template.
|
||||
"""
|
||||
|
||||
vcrlin: int = 0
|
||||
itmref: str = ""
|
||||
pcu: str = ""
|
||||
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?
|
||||
loctyp: str = ""
|
||||
loc: str = ""
|
||||
lot: str = ""
|
||||
slo: str = ""
|
||||
sernum: str = ""
|
||||
palnum: str = ""
|
||||
ctrnum: str = ""
|
||||
qlyctldem: str= ""
|
||||
owner: str = "WON"
|
||||
subdetails: typing.List[StockChangeSubDetail] = dataclasses.field(
|
||||
default_factory=list
|
||||
)
|
||||
|
||||
def palnum_lookup(self, itmref, lot, status):
|
||||
"""
|
||||
Pick a palnum from X3 using the lot, location, and status
|
||||
It matters which one we use, best attempt is to get the largest one available
|
||||
"""
|
||||
with yamamotoyama.get_connection('test') as db_connection:#todo remove 'test'
|
||||
return db_connection.query(
|
||||
"""
|
||||
select top 1
|
||||
[STO].[STOFCY_0],
|
||||
[STO].[ITMREF_0],
|
||||
[STO].[LOT_0],
|
||||
[STO].[PALNUM_0],
|
||||
[STO].[QTYSTU_0]
|
||||
from [FY23TEST].[STOCK] [STO] --TODO change to PROD
|
||||
where
|
||||
[STO].[ITMREF_0] = :itmref
|
||||
and [STO].[STOFCY_0] = 'WON'
|
||||
and [STO].[LOT_0] = :lot
|
||||
and [STO].[STA_0] = :status
|
||||
order by
|
||||
[STO].[QTYSTU_0] desc
|
||||
""",
|
||||
itmref=itmref,
|
||||
lot=lot,
|
||||
status=status
|
||||
).first()["PALNUM_0"]
|
||||
|
||||
def gtin_lookup(self, gtin):
|
||||
with yamamotoyama.get_connection('test') as db_connection:#todo remove 'test'
|
||||
return db_connection.query(
|
||||
"""
|
||||
select
|
||||
[ITM].[ITMREF_0],
|
||||
[ITM].[ITMDES1_0],
|
||||
[ITM].[EANCOD_0],
|
||||
[ITM].[ZCASEUPC_0]
|
||||
from [FY23TEST].[ITMMASTER] [ITM]--TODO change back to [PROD]
|
||||
join [FY23TEST].[ITMFACILIT] [ITF]
|
||||
on [ITM].[ITMREF_0] = [ITF].[ITMREF_0]
|
||||
and [ITF].[STOFCY_0] = 'WON'
|
||||
where
|
||||
[ITM].[ZCASEUPC_0] = :zcaseupc
|
||||
""",
|
||||
zcaseupc=gtin,
|
||||
).first()["ITMREF_0"]
|
||||
|
||||
def append(self, subdetail: StockChangeSubDetail):
|
||||
"""
|
||||
Add subdetail
|
||||
"""
|
||||
subdetail.pcu = self.pcu
|
||||
self.subdetails.append(subdetail)
|
||||
|
||||
def check_subdetail_qty(self):
|
||||
"""
|
||||
Check for shortages by totaling up subdetail quantities.
|
||||
"""
|
||||
total_cases = 0
|
||||
for subdetail in self.subdetails:
|
||||
total_cases -= subdetail.qtypcu
|
||||
return abs(total_cases)
|
||||
|
||||
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
|
||||
|
||||
self.qty = self.check_subdetail_qty()
|
||||
return yamamotoyama.x3_imports.convert_to_strings(
|
||||
[
|
||||
"L",
|
||||
self.vcrlin,
|
||||
self.itmref,
|
||||
fix_uom(self.pcu),
|
||||
self.pcustucoe,
|
||||
self.sta,
|
||||
self.loctyp,
|
||||
self.loc,
|
||||
self.lot,
|
||||
self.slo,
|
||||
self.sernum,
|
||||
self.palnum_lookup(self.itmref, self.lot, self.sta),
|
||||
self.ctrnum,
|
||||
self.qlyctldem,
|
||||
self.owner
|
||||
]
|
||||
)
|
||||
|
||||
def __eq__(self, item: typing.Any) -> bool:
|
||||
"""
|
||||
Test for equality
|
||||
"""
|
||||
if isinstance(item, str):
|
||||
return self.itmref == item
|
||||
if isinstance(item, StockChangeDetail):
|
||||
return self.itmref == item.itmref
|
||||
return False
|
||||
|
||||
# def fill(self):#not needed for stockchanges
|
||||
# """
|
||||
# Set soplin & itmdes from itmref & sohnum
|
||||
# """
|
||||
|
||||
# def get() -> records.Record:
|
||||
# with yamamotoyama.get_connection() as database:
|
||||
# how_many = (
|
||||
# database.query(
|
||||
# """
|
||||
# select
|
||||
# count(*) as [how_many]
|
||||
# from [PROD].[SORDERP] as [SOP]
|
||||
# where
|
||||
# [SOP].[SOHNUM_0] = :sohnum
|
||||
# and [SOP].[ITMREF_0] = :itmref
|
||||
# """,
|
||||
# sohnum=self.sohnum,
|
||||
# itmref=self.itmref,
|
||||
# )
|
||||
# .first()
|
||||
# .how_many
|
||||
# )
|
||||
# if how_many == 1:
|
||||
# return database.query(
|
||||
# """
|
||||
# select top 1
|
||||
# [SOP].[SOPLIN_0]
|
||||
# ,[SOP].[ITMDES1_0]
|
||||
# ,[SOP].[SAU_0]
|
||||
# from [PROD].[SORDERP] as [SOP]
|
||||
# where
|
||||
# [SOP].[SOHNUM_0] = :sohnum
|
||||
# and [SOP].[ITMREF_0] = :itmref
|
||||
# order by
|
||||
# [SOP].[SOPLIN_0]
|
||||
# """,
|
||||
# sohnum=self.sohnum,
|
||||
# itmref=self.itmref,
|
||||
# ).first()
|
||||
|
||||
|
||||
# result = get()
|
||||
# self.soplin = result.SOPLIN_0
|
||||
# self.itmdes = result.ITMDES1_0
|
||||
# self.sau = result.SAU_0
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StockChangeHeader:
|
||||
"""
|
||||
Information that goes on a stockchange header, taken from ZSCS template.
|
||||
"""
|
||||
vcrnum: str = ""
|
||||
stofcy: str = "WON"
|
||||
iptdat: datetime.date = datetime.date(1753, 1, 1)
|
||||
vcrdes: str = ""
|
||||
pjt: str = ""
|
||||
trsfam: str = "CHX"
|
||||
|
||||
|
||||
def convert_to_strings(self) -> typing.List[str]:
|
||||
"""
|
||||
Convert to X3 import line
|
||||
"""
|
||||
return yamamotoyama.x3_imports.convert_to_strings(
|
||||
[
|
||||
"E",
|
||||
self.vcrnum,
|
||||
self.stofcy,
|
||||
self.iptdat.strftime("%Y%m%d"),
|
||||
self.vcrdes,
|
||||
self.pjt,
|
||||
self.trsfam,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class StockChangeDetailList:
|
||||
"""
|
||||
List of stockchange details
|
||||
"""
|
||||
|
||||
_details: typing.List[StockChangeDetail]
|
||||
_item_set: typing.Set[str]
|
||||
|
||||
def __init__(self):
|
||||
self._details = []
|
||||
self._item_set = set()
|
||||
|
||||
def append(
|
||||
self,
|
||||
stockchange_detail: StockChangeDetail,
|
||||
stockchange_subdetail: StockChangeSubDetail,
|
||||
):
|
||||
"""
|
||||
Append
|
||||
"""
|
||||
itmref = stockchange_detail.itmref
|
||||
if itmref in self._item_set:
|
||||
for detail in self._details:
|
||||
if detail == itmref:
|
||||
detail.subdetails.append(stockchange_subdetail)
|
||||
return
|
||||
self._item_set.add(itmref)
|
||||
#stockchange_detail.fill()
|
||||
stockchange_detail.append(stockchange_subdetail)
|
||||
self._details.append(stockchange_detail)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._details)
|
||||
|
||||
|
||||
class StockChange:
|
||||
"""
|
||||
Warehouse stockchange, both header & details
|
||||
"""
|
||||
|
||||
header: StockChangeHeader
|
||||
details: StockChangeDetailList
|
||||
_sdhnum: str
|
||||
|
||||
def __init__(self):
|
||||
self.header = StockChangeHeader()
|
||||
self._sdhnum = ""
|
||||
self.details = StockChangeDetailList()
|
||||
|
||||
def append(
|
||||
self,
|
||||
stockchange_detail: StockChangeDetail,
|
||||
stockchange_subdetail: StockChangeSubDetail,
|
||||
):
|
||||
"""
|
||||
Add detail information.
|
||||
"""
|
||||
self.details.append(stockchange_detail, stockchange_subdetail)
|
||||
|
||||
@property
|
||||
def sdhnum(self):
|
||||
"""
|
||||
shipment number
|
||||
"""
|
||||
return self._sdhnum
|
||||
|
||||
@sdhnum.setter
|
||||
def sdhnum(self, value: str):
|
||||
if self._sdhnum != value:
|
||||
self._sdhnum = value
|
||||
if value:
|
||||
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):
|
||||
"""
|
||||
Output entire order 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())
|
||||
for subdetail in detail.subdetails:
|
||||
#shipment = detail.sdhnum
|
||||
#item = detail.itmref
|
||||
output(subdetail.convert_to_strings())
|
||||
# for record in subdetail.stojous(shipment, item):
|
||||
# output(subdetail.convert_to_strings())
|
||||
# output(record)
|
||||
qty = 0
|
||||
if fields[0] == "QTY":#product should already exist
|
||||
# QTY*33*0
|
||||
# QTY*20*16
|
||||
# QTY*QH*0
|
||||
qty += int(fields[2])
|
||||
if fields[0] == "SE":#end of file
|
||||
# pprint.pprint('final add')
|
||||
# pprint.pprint(shandex_inventory)
|
||||
# pprint.pprint('product: ' + product)
|
||||
# pprint.pprint('lot: ' + lot)
|
||||
# pprint.pprint('qty: ' + str(qty))
|
||||
if product is not None: #check loop entry
|
||||
if product not in shandex_inventory: #if we haven't seen the product yet add it
|
||||
shandex_inventory[product] = {lot : qty}
|
||||
else: #we've seen this product, have we seen the lot
|
||||
if lot not in shandex_inventory[product]:#if not, add it
|
||||
shandex_inventory[product][lot] = qty
|
||||
else:
|
||||
shandex_inventory[product][lot] += qty #if we have add to it
|
||||
return shandex_inventory
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
12
edi_867.py
12
edi_867.py
|
@ -69,6 +69,18 @@ X3_CUSTOMER_MAPPING = {
|
|||
'JIVA1000_JIVABC' : 'JIVA0002',
|
||||
'WELL1000_WELL' : 'WELL0002',
|
||||
'WELL1000_WELCAL' : 'WELL0003',
|
||||
'AMAZ1200_YYC4' : 'AMAZ0210',
|
||||
'AMAZ1200_YVR2' : 'AMAZ0014',
|
||||
'AMAZ1200_YYZ4' : 'AMAZ0049',
|
||||
'AMAZ1200_YXU1' : 'AMAZ0189',
|
||||
'AMAZ1200_YOW3' : 'AMAZ0176',
|
||||
'PARA1100_0000' : 'PARA0004',
|
||||
'AMAZ1200_YVR4' : 'AMAZ0099',
|
||||
'AMAZ1200_YHM1' : 'AMAZ0169',
|
||||
'AMAZ1200_YYZ7' :'AMAZ0100',
|
||||
'PURI1000_PURIBC' : 'PURI0003',
|
||||
'PURI1000_PURIAB' : 'PURI0004',
|
||||
'AMAZ1200_YEG2' : 'AMAZ0179',
|
||||
}
|
||||
|
||||
def main():
|
||||
|
|
16
edi_943.py
16
edi_943.py
|
@ -42,7 +42,7 @@ def main():
|
|||
"""
|
||||
Do it!
|
||||
"""
|
||||
with yamamotoyama.get_connection() as database:
|
||||
with yamamotoyama.get_connection('test') as database:
|
||||
shipments = list(get_shipments(database))
|
||||
for shipment in shipments:
|
||||
write_943(database, shipment)
|
||||
|
@ -127,7 +127,7 @@ def write_943(database: records.Connection, shipment: str):
|
|||
with database.transaction() as _:
|
||||
database.query(
|
||||
"""
|
||||
update [PROD].[SDELIVERY]
|
||||
update [FY23TEST].[SDELIVERY]
|
||||
set [XX4S_943RDY_0] = 1
|
||||
where [SOHNUM_0] = :shipment
|
||||
""",
|
||||
|
@ -136,7 +136,7 @@ def write_943(database: records.Connection, shipment: str):
|
|||
order = get_order_for_shipment(database, shipment)
|
||||
database.query(
|
||||
"""
|
||||
update [PROD].[SORDER]
|
||||
update [FY23TEST].[SORDER]
|
||||
set [XX4S_UDF2_0] = :sent_message
|
||||
where [SOHNUM_0] = :order
|
||||
""",
|
||||
|
@ -153,7 +153,7 @@ def get_shipment_destination(database: records.Connection, shipment: str) -> str
|
|||
"""
|
||||
select
|
||||
[SDH].[BPCORD_0]
|
||||
from [PROD].[SDELIVERY] as [SDH]
|
||||
from [FY23TEST].[SDELIVERY] as [SDH]
|
||||
where
|
||||
[SDH].[SDHNUM_0] = :shipment
|
||||
""",
|
||||
|
@ -172,7 +172,7 @@ def get_order_for_shipment(database: records.Connection, shipment: str) -> str:
|
|||
"""
|
||||
select
|
||||
[SDH].[SOHNUM_0]
|
||||
from [PROD].[SDELIVERY] as [SDH]
|
||||
from [FY23TEST].[SDELIVERY] as [SDH]
|
||||
where
|
||||
[SDH].[SDHNUM_0] = :shipment
|
||||
""",
|
||||
|
@ -191,8 +191,8 @@ def get_shipments(database: records.Connection) -> typing.Iterator[str]:
|
|||
"""
|
||||
select
|
||||
[SDH].[SDHNUM_0]
|
||||
from [PROD].[SDELIVERY] as [SDH]
|
||||
join [PROD].[SORDER] as [SOH]
|
||||
from [FY23TEST].[SDELIVERY] as [SDH]
|
||||
join [FY23TEST].[SORDER] as [SOH]
|
||||
on [SOH].[SOHNUM_0] = [SDH].[SOHNUM_0]
|
||||
where
|
||||
(
|
||||
|
@ -267,7 +267,7 @@ def get_shipment(
|
|||
,[GROWEI_0]
|
||||
,[CFMFLG_0]
|
||||
,[SHIDAT_0]
|
||||
from [PROD].[zyumiddleware_shipment] as [SDH]
|
||||
from [FY23TEST].[zyumiddleware_shipment] as [SDH]
|
||||
where
|
||||
[SDH].[SDHNUM_0] = :shipment
|
||||
""",
|
||||
|
|
|
@ -63,7 +63,7 @@ def validation_alert(sdhnum):
|
|||
msg['Subject'] = 'New Receipt from Shandex'
|
||||
msg['Precedence'] = 'bulk'
|
||||
msg['From'] = 'x3report@stashtea.com'
|
||||
#msg['To'] = 'isenn@yamamotoyama.com'
|
||||
msg['To'] = 'isenn@yamamotoyama.com'
|
||||
msg['CC'] = 'bleeson@stashtea.com'
|
||||
emailtext = f'A Shandex receipt for {sdhnum} could not be loaded into X3 because the shipment is not validated.'
|
||||
msg.attach(MIMEText(emailtext, 'plain'))
|
||||
|
|
22
edi_947.py
22
edi_947.py
|
@ -39,7 +39,7 @@ SHANDEX_947_FILENAME_RE = re.compile(
|
|||
)
|
||||
#TODO remove this and have a single production name? why is this one different
|
||||
SHANDEX_947_FILENAME_RE = re.compile(
|
||||
r"\A STASH_TEST_947 \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 +80,7 @@ def main():
|
|||
if SHANDEX_947_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)
|
||||
#shutil.move(edi_filename, EDI_997_DIRECTORY / edi_filename.name)
|
||||
combine_zscs()
|
||||
|
||||
|
||||
|
@ -130,7 +130,7 @@ def tokens_from_edi_file(
|
|||
|
||||
|
||||
def gtin_lookup(gtin):
|
||||
with yamamotoyama.get_connection('test') as db_connection:#todo remove 'test'
|
||||
with yamamotoyama.get_connection() as db_connection:
|
||||
return db_connection.query(
|
||||
"""
|
||||
select
|
||||
|
@ -138,8 +138,8 @@ def gtin_lookup(gtin):
|
|||
[ITM].[ITMDES1_0],
|
||||
[ITM].[EANCOD_0],
|
||||
[ITM].[ZCASEUPC_0]
|
||||
from [FY23TEST].[ITMMASTER] [ITM]--TODO change back to [PROD]
|
||||
join [FY23TEST].[ITMFACILIT] [ITF]
|
||||
from [PROD].[ITMMASTER] [ITM]
|
||||
join [PROD].[ITMFACILIT] [ITF]
|
||||
on [ITM].[ITMREF_0] = [ITF].[ITMREF_0]
|
||||
and [ITF].[STOFCY_0] = 'WON'
|
||||
where
|
||||
|
@ -183,7 +183,7 @@ def process_file(edi_filename: pathlib.Path):
|
|||
product = gtin_lookup(gtin)
|
||||
stock_movement_alert(product, qty, lot, status)
|
||||
if status in EMAIL_ONLY_CODES:
|
||||
return
|
||||
continue
|
||||
warehouse_stockchange.header.vcrdes = DAMAGE_CODE_DESCRIPTIONS_MAPPING[status]
|
||||
subdetail = StockChangeSubDetail(
|
||||
qtypcu=int(qty),
|
||||
|
@ -307,7 +307,7 @@ class StockChangeDetail:
|
|||
Pick a palnum from X3 using the lot, location, and status
|
||||
It matters which one we use, best attempt is to get the largest one available
|
||||
"""
|
||||
with yamamotoyama.get_connection('test') as db_connection:#todo remove 'test'
|
||||
with yamamotoyama.get_connection() as db_connection:
|
||||
result = db_connection.query(
|
||||
"""
|
||||
select top 1
|
||||
|
@ -316,7 +316,7 @@ class StockChangeDetail:
|
|||
[STO].[LOT_0],
|
||||
[STO].[PALNUM_0],
|
||||
[STO].[QTYSTU_0]
|
||||
from [FY23TEST].[STOCK] [STO] --TODO change to PROD
|
||||
from [PROD].[STOCK] [STO]
|
||||
where
|
||||
[STO].[ITMREF_0] = :itmref
|
||||
and [STO].[STOFCY_0] = 'WON'
|
||||
|
@ -335,7 +335,7 @@ class StockChangeDetail:
|
|||
raise NotImplementedError
|
||||
|
||||
def gtin_lookup(self, gtin):
|
||||
with yamamotoyama.get_connection('test') as db_connection:#todo remove 'test'
|
||||
with yamamotoyama.get_connection() as db_connection:
|
||||
return db_connection.query(
|
||||
"""
|
||||
select
|
||||
|
@ -343,8 +343,8 @@ class StockChangeDetail:
|
|||
[ITM].[ITMDES1_0],
|
||||
[ITM].[EANCOD_0],
|
||||
[ITM].[ZCASEUPC_0]
|
||||
from [FY23TEST].[ITMMASTER] [ITM]--TODO change back to [PROD]
|
||||
join [FY23TEST].[ITMFACILIT] [ITF]
|
||||
from [PROD].[ITMMASTER] [ITM]
|
||||
join [PROD].[ITMFACILIT] [ITF]
|
||||
on [ITM].[ITMREF_0] = [ITF].[ITMREF_0]
|
||||
and [ITF].[STOFCY_0] = 'WON'
|
||||
where
|
||||
|
|
Loading…
Reference in New Issue