204 lines
6.6 KiB
Python
204 lines
6.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
# =============================================================================
|
|
# Nazev: forward_offer_outlook_v1.1.py
|
|
# Verze: 1.1
|
|
# Datum: 2026-06-16
|
|
# Popis: JNJ-native skript (MAPI / pywin32). Pro daneho lekare najde PUVODNI
|
|
# uvodni nabidku ve slozce Odeslane a vytvori jeji skutecny Outlook
|
|
# FORWARD (zachova original vcetne data 31.05.2026, formatu i hlavicky).
|
|
# PRIMARNE hleda zpravu PRIMO podle jednoznacneho EntryID (MAPI) pres
|
|
# GetItemFromID -> nalezeni "na prvni dobrou", nulova nejednoznacnost.
|
|
# FALLBACK (kdyz EntryID nesedne) = subjekt + datum + prijemce To.
|
|
# Forward predvyplni To (lekar) + CC (Kocourkova, Bartosova),
|
|
# volitelne prida kratky uvod a OTEVRE okno k rucni kontrole/odeslani.
|
|
# Pouziti: Spustit v JNJ Pythonu s nakonfigurovanym Outlookem (JNJ schranka).
|
|
# pip install pywin32 ; python forward_offer_outlook_v1.1.py
|
|
# Bezpecnost: ACTION="display" => jen otevre Forward, NEODESILA.
|
|
# Zmeny v1.1: primarni hledani podle EntryID (GetItemFromID) ziskaneho z JNJ
|
|
# SQLite (tabulka messages.entry_id). v1.0 hledalo jen heuristicky.
|
|
# =============================================================================
|
|
|
|
import sys
|
|
import datetime
|
|
import win32com.client # pywin32
|
|
|
|
# ----------------------------- KONFIGURACE -----------------------------------
|
|
|
|
SENDER_SMTP = "vbuzalka@its.jnj.com"
|
|
|
|
# Cile. entry_id = jednoznacny MAPI EntryID puvodni nabidky (z JNJ SQLite,
|
|
# tabulka messages.entry_id). Kdyz entry_id chybi/nesedne, pouzije se fallback
|
|
# podle subjektu+data+prijemce.
|
|
TARGETS = [
|
|
{
|
|
"email": "rastislav.hustak@fntt.sk",
|
|
"entry_id": "000000008431528824F96740840A72BAD506477D070092544C32292E3A46AC27E91F5A4CDB1100000007A91B00005BFD391558BBC54FA9172E1614A2FC13000530495B210000",
|
|
},
|
|
# {"email": "voska@nemocnice-horovice.cz", "entry_id": ""},
|
|
# {"email": "sercl@seznam.cz", "entry_id": ""},
|
|
# {"email": "petra.minarikova@uvn.cz", "entry_id": ""},
|
|
]
|
|
|
|
CC_RECIPIENTS = ["AKocourk@ITS.JNJ.com", "EBartoso@ITS.JNJ.com"]
|
|
|
|
# Fallback kriteria (kdyz EntryID nesedne):
|
|
SUBJECT_STARTSWITH = "nabidka spoluprace na klinickem hodnoceni"
|
|
ORIG_DATE = datetime.date(2026, 5, 31)
|
|
|
|
ADD_INTRO = True
|
|
INTRO_HTML = (
|
|
"<p>Dobry den,</p>"
|
|
"<p>dovoluji si Vam znovu preposlat nize uvedenou nabidku ze dne "
|
|
"31. kvetna 2026. Velmi bych ocenil Vase vyjadreni — a to "
|
|
"i v pripade, ze o ucast nemate zajem. Dekuji.</p>"
|
|
"<p>S pozdravem<br>MUDr. Vladimir Buzalka</p>"
|
|
"<hr>"
|
|
)
|
|
|
|
ACTION = "display" # "display" | "draft" | "send"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
OL_FOLDER_SENT = 5
|
|
OL_TO, OL_CC = 1, 2
|
|
PR_SMTP = "http://schemas.microsoft.com/mapi/proptag/0x39FE001E"
|
|
|
|
|
|
def norm(s):
|
|
import unicodedata
|
|
s = s or ""
|
|
s = unicodedata.normalize("NFKD", s)
|
|
s = "".join(c for c in s if not unicodedata.combining(c))
|
|
return " ".join(s.lower().split())
|
|
|
|
|
|
def smtp_of(recipient):
|
|
try:
|
|
return (recipient.PropertyAccessor.GetProperty(PR_SMTP) or "").lower()
|
|
except Exception:
|
|
try:
|
|
return (recipient.Address or "").lower()
|
|
except Exception:
|
|
return ""
|
|
|
|
|
|
def get_sent_folder(ns):
|
|
try:
|
|
for acct in ns.Accounts:
|
|
if (acct.SmtpAddress or "").lower() == SENDER_SMTP.lower():
|
|
return acct.DeliveryStore.GetDefaultFolder(OL_FOLDER_SENT)
|
|
except Exception:
|
|
pass
|
|
return ns.GetDefaultFolder(OL_FOLDER_SENT)
|
|
|
|
|
|
def get_by_entryid(ns, entry_id, store_id):
|
|
"""Nacte zpravu PRIMO podle EntryID. Vrati MailItem nebo None."""
|
|
if not entry_id:
|
|
return None
|
|
for args in ((entry_id, store_id), (entry_id,)):
|
|
try:
|
|
it = ns.GetItemFromID(*args)
|
|
if it is not None:
|
|
return it
|
|
except Exception:
|
|
continue
|
|
return None
|
|
|
|
|
|
def find_original_fallback(items, target_email):
|
|
"""Fallback: subjekt + datum 31.05.2026 + prijemce To."""
|
|
tgt = target_email.lower()
|
|
out = []
|
|
for it in items:
|
|
try:
|
|
if it.Class != 43:
|
|
continue
|
|
if norm(it.Subject)[: len(SUBJECT_STARTSWITH)] != SUBJECT_STARTSWITH:
|
|
continue
|
|
s = it.SentOn
|
|
if s is None or s.date() != ORIG_DATE:
|
|
continue
|
|
for r in it.Recipients:
|
|
if r.Type == OL_TO and smtp_of(r) == tgt:
|
|
out.append(it)
|
|
break
|
|
except Exception:
|
|
continue
|
|
return out
|
|
|
|
|
|
def make_forward(orig, email):
|
|
fwd = orig.Forward()
|
|
fwd.Recipients.Add(email).Type = OL_TO
|
|
for cc in CC_RECIPIENTS:
|
|
fwd.Recipients.Add(cc).Type = OL_CC
|
|
fwd.Recipients.ResolveAll()
|
|
if ADD_INTRO:
|
|
try:
|
|
fwd.HTMLBody = INTRO_HTML + fwd.HTMLBody
|
|
except Exception:
|
|
pass
|
|
return fwd
|
|
|
|
|
|
def main():
|
|
outlook = win32com.client.Dispatch("Outlook.Application")
|
|
ns = outlook.GetNamespace("MAPI")
|
|
sent = get_sent_folder(ns)
|
|
store_id = sent.Store.StoreID
|
|
items = None # lazy, jen kdyz bude potreba fallback
|
|
|
|
print("Slozka Odeslane:", sent.FolderPath)
|
|
print("Rezim ACTION :", ACTION)
|
|
print("=" * 60)
|
|
|
|
for t in TARGETS:
|
|
email = t["email"]
|
|
orig = get_by_entryid(ns, t.get("entry_id", ""), store_id)
|
|
how = "EntryID"
|
|
|
|
if orig is None:
|
|
# fallback heuristika
|
|
if items is None:
|
|
items = sent.Items
|
|
items.Sort("[SentOn]", True)
|
|
found = find_original_fallback(items, email)
|
|
if not found:
|
|
print(f"[!] {email}: NENALEZENO (EntryID nesedl ani fallback). Preskakuji.")
|
|
continue
|
|
if len(found) > 1:
|
|
print(f"[!] {email}: fallback nasel {len(found)} shod — nejednoznacne, preskakuji.")
|
|
continue
|
|
orig = found[0]
|
|
how = "fallback(subjekt+datum+prijemce)"
|
|
|
|
# kontrola, ze to je opravdu ta nabidka tomu lekari
|
|
try:
|
|
print(f" nalezeno pres {how}: \"{orig.Subject}\" | odeslano {orig.SentOn}")
|
|
except Exception:
|
|
pass
|
|
|
|
fwd = make_forward(orig, email)
|
|
|
|
if ACTION == "send":
|
|
fwd.Send()
|
|
print(f"[ODESLANO] {email}")
|
|
elif ACTION == "draft":
|
|
fwd.Save()
|
|
print(f"[KONCEPT ] {email}")
|
|
else:
|
|
fwd.Display()
|
|
print(f"[OTEVRENO] {email} — zkontroluj a posli rucne")
|
|
|
|
print("=" * 60)
|
|
print("Hotovo.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except Exception as e:
|
|
print("CHYBA:", e)
|
|
sys.exit(1)
|