z230
This commit is contained in:
+84
-28
@@ -4,6 +4,8 @@ from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
from playwright.sync_api import sync_playwright, TimeoutError as PWTimeout
|
||||
import tkinter as tk
|
||||
from tkinter import simpledialog
|
||||
|
||||
load_dotenv(Path(__file__).parent / ".env")
|
||||
|
||||
@@ -23,7 +25,11 @@ SELECT_ROLE_URL = (
|
||||
|
||||
STUDY_NAME = "42847922MDD3003"
|
||||
SITE_GROUP = "CZE"
|
||||
FORM_NAME = "Date of Visit"
|
||||
FORM_NAMES = [
|
||||
"Date of Visit",
|
||||
"Vital Signs",
|
||||
"Interim Investigator Signature",
|
||||
]
|
||||
REPORT_ID = 92 # _EDC Std Rpt - Data Listing (Data Stream)
|
||||
|
||||
|
||||
@@ -55,6 +61,21 @@ def dbg(page, label):
|
||||
# Login
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _ask_otp_popup():
|
||||
"""Zobrazí GUI dialog pro zadání OKTA OTP kódu."""
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
root.lift()
|
||||
root.attributes("-topmost", True)
|
||||
otp = simpledialog.askstring(
|
||||
"OKTA MFA",
|
||||
"Zadej OTP kód z OKTA (6 číslic):",
|
||||
parent=root,
|
||||
)
|
||||
root.destroy()
|
||||
return (otp or "").strip()
|
||||
|
||||
|
||||
def do_login(page, context):
|
||||
print("Přihlašuji se do iMedidata...")
|
||||
page.goto(LOGIN_URL)
|
||||
@@ -74,7 +95,10 @@ def do_login(page, context):
|
||||
# OKTA MFA?
|
||||
if _okta_mfa_present(page):
|
||||
print("\n*** OKTA MFA vyžadována! ***")
|
||||
otp = input("Zadej OTP kód z OKTA (6 číslic): ").strip()
|
||||
otp = _ask_otp_popup()
|
||||
if not otp:
|
||||
print("CHYBA: OTP nebylo zadáno.")
|
||||
sys.exit(1)
|
||||
_fill_otp(page, otp)
|
||||
# Čekáme na zpracování OTP a redirect zpět na iMedidata
|
||||
wait_load(page, 3000)
|
||||
@@ -136,7 +160,11 @@ def _fill_otp(page, otp):
|
||||
def go_to_select_role(page):
|
||||
"""Přejde na SelectRole stránku a vrátí True pokud jsme tam skutečně."""
|
||||
print(f"Navigace na SelectRole...")
|
||||
page.goto(SELECT_ROLE_URL)
|
||||
try:
|
||||
page.goto(SELECT_ROLE_URL)
|
||||
except Exception:
|
||||
# Rave dělá server-side redirect (ERR_ABORTED) — zkontrolujeme URL až po načtení
|
||||
pass
|
||||
wait_load(page, 1500)
|
||||
dbg(page, "select-role")
|
||||
return "login" not in page.url.lower() and "okta" not in page.url.lower()
|
||||
@@ -267,36 +295,54 @@ def set_site_group_param(page):
|
||||
dbg(page, "after-site-group")
|
||||
|
||||
|
||||
def set_form_param(page):
|
||||
"""Rozbalí Form panel, vyhledá Date of Visit a zaškrtne ho."""
|
||||
print(f" Parametr Form: {FORM_NAME}")
|
||||
def set_form_param(page, form_name):
|
||||
"""Rozbalí Form panel (pokud je zavřený) a zaškrtne formulář.
|
||||
Panel je SingleSelection=1, takže nový výběr automaticky odznačí předchozí."""
|
||||
print(f" Parametr Form: {form_name}")
|
||||
|
||||
page.click('#PromptsBox_fm2_ShowHideBtn')
|
||||
page.wait_for_timeout(2000)
|
||||
# Otevřít panel jen pokud je zavřený (kontrola přes style.display)
|
||||
is_closed = page.locator('#PromptsBox_fm2_div').evaluate('el => el.style.display') == 'none'
|
||||
if is_closed:
|
||||
page.click('#PromptsBox_fm2_ShowHideBtn')
|
||||
page.wait_for_timeout(2000)
|
||||
|
||||
# Vyplnit search a odeslat Enterem — výsledek je okamžitý
|
||||
page.wait_for_selector('#PromptsBox_fm2_SearchTxt', timeout=10_000)
|
||||
page.fill('#PromptsBox_fm2_SearchTxt', FORM_NAME)
|
||||
page.locator('#PromptsBox_fm2_SearchTxt').press('Enter')
|
||||
page.wait_for_timeout(800)
|
||||
# Po předchozím stažení je panel v "locked" módu.
|
||||
# 1. klik na tužku → vymaže výběr, tlačítko se změní na oko
|
||||
# 2. klik na oko → načte seznam všech formulářů
|
||||
if page.locator('#PromptsBox_fm2_PageModeBtn').is_visible():
|
||||
page.click('#PromptsBox_fm2_PageModeBtn') # tužka → oko
|
||||
page.wait_for_timeout(1000)
|
||||
page.click('#PromptsBox_fm2_PageModeBtn') # oko → načte formuláře
|
||||
page.wait_for_timeout(2000)
|
||||
|
||||
# Zaškrtneme první (jediný) výsledek
|
||||
cbs = page.query_selector_all('input[id^="PromptsBox_fm2_FrontEndCBList_"]')
|
||||
if cbs:
|
||||
if not cbs[0].is_checked():
|
||||
cbs[0].click()
|
||||
print(f" '{FORM_NAME}' zaškrtnuto")
|
||||
wait_load(page, 500)
|
||||
# Vyhledat formulář — klik zajistí focus, Enter spustí ajaxSelectionGridSearchBoxOnKeypress
|
||||
search = page.locator('#PromptsBox_fm2_SearchTxt')
|
||||
search.wait_for(state='visible', timeout=10_000)
|
||||
search.click()
|
||||
search.fill(form_name)
|
||||
search.press('Enter')
|
||||
|
||||
# Počkáme až AJAX přepíše DOM se seznamem výsledků
|
||||
cb_locator = page.locator('input[id^="PromptsBox_fm2_FrontEndCBList_"]').first
|
||||
try:
|
||||
cb_locator.wait_for(state='visible', timeout=8_000)
|
||||
except PWTimeout:
|
||||
print(f" VAROVÁNÍ: '{form_name}' nenalezen nebo timeout!")
|
||||
return
|
||||
|
||||
print(f" VAROVÁNÍ: '{FORM_NAME}' nenalezen!")
|
||||
# SingleSelection=1: klik na nový checkbox automaticky odznačí předchozí
|
||||
# Locator se vyhodnotí čerstvě — žádný stale element handle
|
||||
if not cb_locator.is_checked():
|
||||
cb_locator.click()
|
||||
print(f" '{form_name}' zaškrtnuto")
|
||||
wait_load(page, 500)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Submit a download
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def submit_and_download(page, context):
|
||||
def submit_and_download(page, context, form_name):
|
||||
print("Odesílám report (čekám na nové okno)...")
|
||||
|
||||
with context.expect_page() as new_page_info:
|
||||
@@ -350,7 +396,8 @@ def submit_and_download(page, context):
|
||||
# Save as Unicode: necháme nezaškrtnuté (default)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M")
|
||||
filename = f"{timestamp}_EDC_MDD3003_DataListing.csv"
|
||||
form_slug = form_name.replace(" ", "")
|
||||
filename = f"{timestamp}_EDC_MDD3003_{form_slug}_DataListing.csv"
|
||||
output_path = DOWNLOAD_DIR / filename
|
||||
|
||||
print("Stahuji CSV...")
|
||||
@@ -364,6 +411,13 @@ def submit_and_download(page, context):
|
||||
download = dl_info.value
|
||||
download.save_as(str(output_path))
|
||||
print(f"\nHotovo! Soubor uložen: {output_path}")
|
||||
|
||||
try:
|
||||
new_page.close()
|
||||
print("Stahovací okno zavřeno.")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return output_path
|
||||
|
||||
|
||||
@@ -409,17 +463,19 @@ def run():
|
||||
# Krok 6: otevření reportu
|
||||
open_report(page)
|
||||
|
||||
# Krok 7: nastavení parametrů
|
||||
# Krok 7: nastavení parametrů (Study a Site Group jednou, Form v smyčce)
|
||||
print("Nastavuji parametry reportu...")
|
||||
set_study_param(page)
|
||||
set_site_group_param(page)
|
||||
set_form_param(page)
|
||||
|
||||
# Krok 8: odeslání a stažení
|
||||
output = submit_and_download(page, context)
|
||||
# Krok 8: smyčka přes formuláře
|
||||
for form_name in FORM_NAMES:
|
||||
print(f"\n=== Stahuji formulář: {form_name} ===")
|
||||
set_form_param(page, form_name)
|
||||
submit_and_download(page, context, form_name)
|
||||
|
||||
input("\nZmáčkni Enter pro zavření prohlížeče...")
|
||||
browser.close()
|
||||
print("Prohlížeč zavřen.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user