z230
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# forward_offer_outlook_v1.0.py
|
||||
|
||||
**Verze:** 1.0 · **Datum:** 2026-06-16
|
||||
|
||||
JNJ-native skript (pywin32 / MAPI). V odeslané poště Outlooku najde **původní
|
||||
úvodní nabídku** odeslanou konkrétnímu lékaři dne **31.05.2026** a vytvoří její
|
||||
**skutečný Outlook Forward** — zachová originál včetně data, formátování i
|
||||
hlavičky (tj. to, co `vbcz-email` `.eml` udělat nedokáže).
|
||||
|
||||
## Spuštění (na JNJ stroji s Outlookem)
|
||||
```
|
||||
pip install pywin32 # jednorázově
|
||||
python forward_offer_outlook_v1.0.py
|
||||
```
|
||||
|
||||
## Co dělá
|
||||
1. Otevře MAPI namespace, najde složku **Odeslané** účtu `vbuzalka@its.jnj.com`.
|
||||
2. Pro každého lékaře v `TARGETS` najde původní e-mail podle:
|
||||
- subjekt začíná „Nabídka spolupráce na klinickém hodnocení…" (odliší od
|
||||
připomínek `[2. připomínka]` a odpovědí `RE:`),
|
||||
- datum odeslání = **31.05.2026**,
|
||||
- příjemce **To** = e-mail lékaře.
|
||||
3. Zavolá `.Forward()` → předvyplní **To** (lékař) + **CC** (Kocourková,
|
||||
Bartošová), volitelně přidá krátký úvod a podle `ACTION`:
|
||||
- `display` (default) — jen **otevře okno** Forwardu, NEODesílá,
|
||||
- `draft` — uloží do Konceptů,
|
||||
- `send` — odešle.
|
||||
|
||||
## Konfigurace (nahoře ve skriptu)
|
||||
- `TARGETS` — seznam adres. **Defaultně jen Hušták** (odladění); ostatní
|
||||
(Voska, Šerclová, Mináříková) odkomentovat až po ověření.
|
||||
- `CC_RECIPIENTS` — Kocourková + Bartošová.
|
||||
- `ADD_INTRO` / `INTRO_HTML` — krátký úvod nad přeposlaným originálem
|
||||
(`False` = čisté přeposlání bez textu navíc).
|
||||
- `ACTION` — `display` / `draft` / `send`.
|
||||
- `SUBJECT_STARTSWITH`, `ORIG_DATE` — kritéria pro nalezení originálu.
|
||||
|
||||
## Pozn.
|
||||
- Porovnání subjektu je bez diakritiky a malými písmeny (robustní vůči
|
||||
„prípravku"/„přípravku").
|
||||
- Když nenajde právě jednu shodu, lékaře **přeskočí** a vypíše varování
|
||||
(nehádá).
|
||||
- `display` nevyvolává Outlookový „program se snaží odeslat" dialog —
|
||||
odeslání je vždy na tobě.
|
||||
- Pokud by JNJ Outlook měl JNJ schránku jako jiný než výchozí účet, skript
|
||||
si složku Odeslané najde podle `SENDER_SMTP`.
|
||||
```
|
||||
```
|
||||
@@ -0,0 +1,175 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# =============================================================================
|
||||
# Nazev: forward_offer_outlook_v1.0.py
|
||||
# Verze: 1.0
|
||||
# Datum: 2026-06-16
|
||||
# Popis: JNJ-native skript. Pres MAPI (Outlook, pywin32) najde v odeslane
|
||||
# poste PUVODNI uvodni nabidku ("Nabidka spoluprace na klinickem
|
||||
# hodnoceni pripravku icotrokinra...") odeslanou konkretnimu lekari
|
||||
# dne 31.05.2026 a vytvori jeji FORWARD (skutecny Outlook Forward,
|
||||
# tj. zachova original vcetne data, formatovani i hlavicky).
|
||||
# Forward predvyplni prijemce (lekar) + CC (Kocourkova, Bartosova),
|
||||
# volitelne prida kratky uvod a OTEVRE okno k rucni kontrole/odeslani.
|
||||
# Pouziti: Spustit v JNJ Pythonu, kde je nakonfigurovany Outlook s JNJ schrankou.
|
||||
# Vyzaduje pywin32: pip install pywin32
|
||||
# python forward_offer_outlook_v1.0.py
|
||||
# Bezpecnost: ACTION = "display" => jen otevre Forward, NEODESILA.
|
||||
# "draft" => ulozi do Konceptu. "send" => odesle (uvazene zapnout).
|
||||
# =============================================================================
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
import win32com.client # pywin32
|
||||
|
||||
# ----------------------------- KONFIGURACE -----------------------------------
|
||||
|
||||
# JNJ schranka (odesilatel puvodnich nabidek). Pouzije se jeji slozka Odeslane.
|
||||
SENDER_SMTP = "vbuzalka@its.jnj.com"
|
||||
|
||||
# Komu forwardovat. Pro odladeni zatim JEN Hustak; ostatni odkomentuj az to klapne.
|
||||
TARGETS = [
|
||||
"rastislav.hustak@fntt.sk",
|
||||
# "voska@nemocnice-horovice.cz",
|
||||
# "sercl@seznam.cz",
|
||||
# "petra.minarikova@uvn.cz",
|
||||
]
|
||||
|
||||
# CC na kazdy forward (nas lokalni tym).
|
||||
CC_RECIPIENTS = ["AKocourk@ITS.JNJ.com", "EBartoso@ITS.JNJ.com"]
|
||||
|
||||
# Identifikace puvodniho e-mailu:
|
||||
# - subjekt zacina na (po ocisteni) tento text (odlisi nabidku od pripominek/RE)
|
||||
SUBJECT_STARTSWITH = "nabidka spoluprace na klinickem hodnoceni"
|
||||
# - datum odeslani originalu
|
||||
ORIG_DATE = datetime.date(2026, 5, 31)
|
||||
|
||||
# Volitelny kratky uvod nad forwardovanym originalem.
|
||||
# ADD_INTRO = False => ciste preposlani bez jakehokoli textu navic.
|
||||
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>"
|
||||
)
|
||||
|
||||
# Co s vytvorenym forwardem: "display" | "draft" | "send"
|
||||
ACTION = "display"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
OL_FOLDER_SENT = 5 # olFolderSentMail
|
||||
OL_TO, OL_CC = 1, 2 # olTo, olCC
|
||||
PR_SMTP = "http://schemas.microsoft.com/mapi/proptag/0x39FE001E"
|
||||
|
||||
|
||||
def norm(s):
|
||||
"""male pismena bez diakritiky pro porovnani subjektu"""
|
||||
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):
|
||||
"""Slozka Odeslane prislusneho uctu (dle SENDER_SMTP), fallback default."""
|
||||
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 find_original(items, target_email):
|
||||
"""Najde puvodni nabidku: subjekt + datum 31.05.2026 + prijemce To."""
|
||||
tgt = target_email.lower()
|
||||
matches = []
|
||||
for it in items:
|
||||
try:
|
||||
if it.Class != 43: # olMail
|
||||
continue
|
||||
if norm(it.Subject)[: len(SUBJECT_STARTSWITH)] != SUBJECT_STARTSWITH:
|
||||
continue
|
||||
sent = it.SentOn
|
||||
if sent is None or sent.date() != ORIG_DATE:
|
||||
continue
|
||||
for r in it.Recipients:
|
||||
if r.Type == OL_TO and smtp_of(r) == tgt:
|
||||
matches.append(it)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
return matches
|
||||
|
||||
|
||||
def main():
|
||||
outlook = win32com.client.Dispatch("Outlook.Application")
|
||||
ns = outlook.GetNamespace("MAPI")
|
||||
sent = get_sent_folder(ns)
|
||||
items = sent.Items
|
||||
items.Sort("[SentOn]", True) # nejnovejsi prvni
|
||||
|
||||
print("Slozka Odeslane:", sent.FolderPath)
|
||||
print("Rezim ACTION :", ACTION)
|
||||
print("=" * 60)
|
||||
|
||||
for email in TARGETS:
|
||||
found = find_original(items, email)
|
||||
if not found:
|
||||
print(f"[!] {email}: PUVODNI NABIDKA NENALEZENA (subjekt/datum/prijemce). Preskakuji.")
|
||||
continue
|
||||
if len(found) > 1:
|
||||
print(f"[!] {email}: nalezeno {len(found)} shod — nejednoznacne, preskakuji (over rucne).")
|
||||
continue
|
||||
|
||||
orig = found[0]
|
||||
fwd = orig.Forward() # SKUTECNY Outlook Forward (zachova original + datum)
|
||||
|
||||
# prijemce
|
||||
fwd.Recipients.Add(email).Type = OL_TO
|
||||
for cc in CC_RECIPIENTS:
|
||||
fwd.Recipients.Add(cc).Type = OL_CC
|
||||
fwd.Recipients.ResolveAll()
|
||||
|
||||
# volitelny uvod nad forward blokem
|
||||
if ADD_INTRO:
|
||||
try:
|
||||
fwd.HTMLBody = INTRO_HTML + fwd.HTMLBody
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if ACTION == "send":
|
||||
fwd.Send()
|
||||
print(f"[ODESLANO] {email} (subjekt: {fwd.Subject})")
|
||||
elif ACTION == "draft":
|
||||
fwd.Save()
|
||||
print(f"[KONCEPT ] {email} (subjekt: {fwd.Subject})")
|
||||
else: # display
|
||||
fwd.Display()
|
||||
print(f"[OTEVRENO] {email} (subjekt: {fwd.Subject}) — zkontroluj a posli rucne")
|
||||
|
||||
print("=" * 60)
|
||||
print("Hotovo.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
print("CHYBA:", e)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,45 @@
|
||||
# forward_offer_outlook_v1.1.py
|
||||
|
||||
**Verze:** 1.1 · **Datum:** 2026-06-16
|
||||
|
||||
JNJ-native skript (pywin32 / MAPI). Pro daného lékaře vytvoří **skutečný Outlook
|
||||
Forward** jeho původní úvodní nabídky (zachová originál včetně data 31.05.2026,
|
||||
formátování i hlavičky).
|
||||
|
||||
## Změna oproti v1.0
|
||||
- **Primárně hledá zprávu přímo podle jednoznačného `EntryID`** (MAPI) přes
|
||||
`Namespace.GetItemFromID(entry_id, store_id)` → nalezení „na první dobrou",
|
||||
nulová nejednoznačnost.
|
||||
- `EntryID` se bere z **JNJ SQLite** (`messages.entry_id`). Pro Huštáka je už
|
||||
předvyplněný v `TARGETS`.
|
||||
- **Fallback** (když EntryID nesedne) = původní heuristika subjekt + datum
|
||||
31.05.2026 + příjemce To.
|
||||
|
||||
## Spuštění (JNJ stroj s Outlookem)
|
||||
```
|
||||
pip install pywin32
|
||||
python forward_offer_outlook_v1.1.py
|
||||
```
|
||||
|
||||
## Konfigurace
|
||||
- `TARGETS` — list `{"email", "entry_id"}`. Defaultně jen **Hušták**
|
||||
(ostatní zakomentované; doplň jim EntryID ze SQLite, jinak poběží fallback).
|
||||
- `CC_RECIPIENTS` — Kocourková + Bartošová.
|
||||
- `ADD_INTRO` / `INTRO_HTML` — krátký úvod nad forwardem (`False` = čistý forward).
|
||||
- `ACTION` — `display` (default, jen otevře) / `draft` / `send`.
|
||||
|
||||
## Kde vzít EntryID dalších lékařů
|
||||
JNJ SQLite, tabulka `messages`:
|
||||
```sql
|
||||
SELECT entry_id, subject, received_at, source
|
||||
FROM messages
|
||||
WHERE source LIKE '%<FC...msg-nazev>%';
|
||||
```
|
||||
(`message_id` je tam uložený jako `entryid:` + EntryID; sloupec `entry_id`
|
||||
obsahuje čistý EntryID. Internet Message-ID `<…@…>` v datech NENÍ —
|
||||
`.msg` jsou X-Unsent drafty, RFC Message-ID přiděluje až Exchange při odeslání.)
|
||||
|
||||
## Pozn.
|
||||
- `GetItemFromID` zkusí variantu se `store_id` i bez něj.
|
||||
- `display` nevyvolá Outlookový „program se snaží odeslat" dialog.
|
||||
- Když se nenajde ani EntryID, ani jednoznačný fallback → lékaře přeskočí.
|
||||
@@ -0,0 +1,203 @@
|
||||
# -*- 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)
|
||||
Reference in New Issue
Block a user