Spaces:
Running
Running
Commit ·
7a82de2
1
Parent(s): c5b1794
Bug
Browse files
server.py
CHANGED
|
@@ -23,51 +23,14 @@ load_dotenv()
|
|
| 23 |
|
| 24 |
app = Flask(__name__)
|
| 25 |
app.config['SECRET_KEY'] = 'secret-key-for-hillside-automation'
|
| 26 |
-
|
| 27 |
-
|
|
|
|
| 28 |
|
| 29 |
bot_instance = None
|
| 30 |
session_data = {}
|
| 31 |
automation_thread = None
|
| 32 |
|
| 33 |
-
class GoogleDriveService:
|
| 34 |
-
def __init__(self):
|
| 35 |
-
self.creds = None
|
| 36 |
-
self.service = None
|
| 37 |
-
self.folder_id = os.getenv('GOOGLE_DRIVE_FOLDER_ID')
|
| 38 |
-
|
| 39 |
-
try:
|
| 40 |
-
from google.oauth2 import service_account
|
| 41 |
-
from googleapiclient.discovery import build
|
| 42 |
-
|
| 43 |
-
# --- Definitive Secure Credential Loading ---
|
| 44 |
-
base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
|
| 45 |
-
if not base64_creds:
|
| 46 |
-
raise ValueError("GDRIVE_SA_KEY_BASE64 secret not found.")
|
| 47 |
-
if not self.folder_id:
|
| 48 |
-
raise ValueError("GOOGLE_DRIVE_FOLDER_ID secret not found.")
|
| 49 |
-
|
| 50 |
-
creds_json = base64.b64decode(base64_creds).decode('utf-8')
|
| 51 |
-
creds_dict = json.loads(creds_json)
|
| 52 |
-
|
| 53 |
-
self.creds = service_account.Credentials.from_service_account_info(creds_dict, scopes=['https://www.googleapis.com/auth/drive'])
|
| 54 |
-
self.service = build('drive', 'v3', credentials=self.creds)
|
| 55 |
-
print("[G-Drive] Service initialized securely from secrets.")
|
| 56 |
-
except Exception as e:
|
| 57 |
-
print(f"[G-Drive] CRITICAL ERROR: Could not initialize Google Drive service: {e}")
|
| 58 |
-
|
| 59 |
-
def upload_file(self, filename, file_content):
|
| 60 |
-
if not self.service: return False
|
| 61 |
-
try:
|
| 62 |
-
from googleapiclient.http import MediaIoBaseUpload
|
| 63 |
-
file_metadata = {'name': filename, 'parents': [self.folder_id]}
|
| 64 |
-
media = MediaIoBaseUpload(io.BytesIO(file_content.encode('utf-8')), mimetype='text/csv', resumable=True)
|
| 65 |
-
self.service.files().create(body=file_metadata, media_body=media, fields='id').execute()
|
| 66 |
-
print(f"[G-Drive] File '{filename}' uploaded successfully.")
|
| 67 |
-
return True
|
| 68 |
-
except Exception as e:
|
| 69 |
-
print(f"[G-Drive] ERROR: File upload failed: {e}"); return False
|
| 70 |
-
|
| 71 |
class EmailService:
|
| 72 |
def __init__(self):
|
| 73 |
self.sender_email = os.getenv('EMAIL_SENDER')
|
|
@@ -89,6 +52,31 @@ class EmailService:
|
|
| 89 |
print(f"Email sent successfully to {', '.join(recipients)}"); return True
|
| 90 |
except Exception as e: print(f"Failed to send email: {e}"); return False
|
| 91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
email_service = EmailService()
|
| 93 |
drive_service = GoogleDriveService()
|
| 94 |
|
|
@@ -138,44 +126,43 @@ def generate_and_send_reports(session_id, results, is_crash_report=False, is_ter
|
|
| 138 |
|
| 139 |
@app.route('/')
|
| 140 |
def status_page():
|
| 141 |
-
APP_STATUS_HTML = """
|
| 142 |
-
<!DOCTYPE html><html lang="en"><head><title>API Status</title><style>
|
| 143 |
-
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f0f2f5;}
|
| 144 |
-
.status-box{text-align:center;padding:40px 60px;background:white;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,0.1);}
|
| 145 |
-
h1{font-size:24px;color:#333;margin-bottom:10px;} .indicator{font-size:18px;font-weight:600;padding:8px 16px;border-radius:20px;}
|
| 146 |
-
.active{color:#28a745;background-color:#e9f7ea;}
|
| 147 |
-
</style></head><body><div class="status-box"><h1>Hillside's Automation API</h1><div class="indicator active">● Active</div></div></body></html>
|
| 148 |
-
"""
|
| 149 |
return Response(APP_STATUS_HTML)
|
| 150 |
|
| 151 |
@socketio.on('connect')
|
| 152 |
def handle_connect():
|
| 153 |
-
print('Frontend connected.'); emit('email_list', {'emails': get_email_list()})
|
| 154 |
|
| 155 |
@socketio.on('initialize_session')
|
| 156 |
def handle_init(data):
|
| 157 |
-
session_id = 'user_session'
|
| 158 |
session_data[session_id] = {'csv_content': data['content'], 'emails': data['emails'], 'filename': data['filename']}
|
| 159 |
-
global bot_instance
|
| 160 |
if bot_instance: bot_instance.shutdown()
|
| 161 |
bot_instance = QuantumBot(socketio, app)
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
@socketio.on('start_login')
|
| 166 |
def handle_login(credentials):
|
| 167 |
if not bot_instance: return emit('error', {'message': 'Bot not initialized.'})
|
| 168 |
-
|
| 169 |
-
|
|
|
|
| 170 |
|
| 171 |
@socketio.on('submit_otp')
|
| 172 |
def handle_otp(data):
|
| 173 |
if not bot_instance: return emit('error', {'message': 'Bot not initialized.'})
|
| 174 |
-
|
|
|
|
| 175 |
emit('login_successful')
|
| 176 |
session_id = 'user_session'
|
| 177 |
-
socketio.start_background_task(target=run_automation_process,
|
| 178 |
-
else: emit('error', {'message': 'OTP
|
| 179 |
|
| 180 |
@socketio.on('terminate_process')
|
| 181 |
def handle_terminate():
|
|
|
|
| 23 |
|
| 24 |
app = Flask(__name__)
|
| 25 |
app.config['SECRET_KEY'] = 'secret-key-for-hillside-automation'
|
| 26 |
+
FRONTEND_ORIGIN = os.getenv('FRONTEND_URL', 'http://127.0.0.1:5500')
|
| 27 |
+
CORS(app, resources={r"/*": {"origins": FRONTEND_ORIGIN}})
|
| 28 |
+
socketio = SocketIO(app, cors_allowed_origins=FRONTEND_ORIGIN, async_mode='eventlet')
|
| 29 |
|
| 30 |
bot_instance = None
|
| 31 |
session_data = {}
|
| 32 |
automation_thread = None
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
class EmailService:
|
| 35 |
def __init__(self):
|
| 36 |
self.sender_email = os.getenv('EMAIL_SENDER')
|
|
|
|
| 52 |
print(f"Email sent successfully to {', '.join(recipients)}"); return True
|
| 53 |
except Exception as e: print(f"Failed to send email: {e}"); return False
|
| 54 |
|
| 55 |
+
class GoogleDriveService:
|
| 56 |
+
def __init__(self):
|
| 57 |
+
self.creds = None; self.service = None
|
| 58 |
+
self.folder_id = os.getenv('GOOGLE_DRIVE_FOLDER_ID')
|
| 59 |
+
try:
|
| 60 |
+
from google.oauth2 import service_account
|
| 61 |
+
from googleapiclient.discovery import build
|
| 62 |
+
base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
|
| 63 |
+
if not base64_creds or not self.folder_id: raise ValueError("Google Drive secrets not found.")
|
| 64 |
+
creds_json = base64.b64decode(base64_creds).decode('utf-8'); creds_dict = json.loads(creds_json)
|
| 65 |
+
self.creds = service_account.Credentials.from_service_account_info(creds_dict, scopes=['https://www.googleapis.com/auth/drive'])
|
| 66 |
+
self.service = build('drive', 'v3', credentials=self.creds)
|
| 67 |
+
print("[G-Drive] Service initialized securely from secrets.")
|
| 68 |
+
except Exception as e: print(f"[G-Drive] CRITICAL ERROR: Could not initialize Google Drive service: {e}")
|
| 69 |
+
def upload_file(self, filename, file_content):
|
| 70 |
+
if not self.service: return False
|
| 71 |
+
try:
|
| 72 |
+
from googleapiclient.http import MediaIoBaseUpload
|
| 73 |
+
file_metadata = {'name': filename, 'parents': [self.folder_id]}
|
| 74 |
+
media = MediaIoBaseUpload(io.BytesIO(file_content.encode('utf-8')), mimetype='text/csv', resumable=True)
|
| 75 |
+
self.service.files().create(body=file_metadata, media_body=media, fields='id').execute()
|
| 76 |
+
print(f"[G-Drive] File '{filename}' uploaded successfully.")
|
| 77 |
+
return True
|
| 78 |
+
except Exception as e: print(f"[G-Drive] ERROR: File upload failed: {e}"); return False
|
| 79 |
+
|
| 80 |
email_service = EmailService()
|
| 81 |
drive_service = GoogleDriveService()
|
| 82 |
|
|
|
|
| 126 |
|
| 127 |
@app.route('/')
|
| 128 |
def status_page():
|
| 129 |
+
APP_STATUS_HTML = """<!DOCTYPE html><html lang="en"><head><title>API Status</title><style>body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f0f2f5;}.status-box{text-align:center;padding:40px 60px;background:white;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,0.1);}h1{font-size:24px;color:#333;margin-bottom:10px;} .indicator{font-size:18px;font-weight:600;padding:8px 16px;border-radius:20px;}.active{color:#28a745;background-color:#e9f7ea;}</style></head><body><div class="status-box"><h1>Hillside's Automation API</h1><div class="indicator active">● Active</div></div></body></html>"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
return Response(APP_STATUS_HTML)
|
| 131 |
|
| 132 |
@socketio.on('connect')
|
| 133 |
def handle_connect():
|
| 134 |
+
print(f'Frontend connected. Allowed origin: {FRONTEND_ORIGIN}'); emit('email_list', {'emails': get_email_list()})
|
| 135 |
|
| 136 |
@socketio.on('initialize_session')
|
| 137 |
def handle_init(data):
|
| 138 |
+
session_id = 'user_session'; global bot_instance
|
| 139 |
session_data[session_id] = {'csv_content': data['content'], 'emails': data['emails'], 'filename': data['filename']}
|
|
|
|
| 140 |
if bot_instance: bot_instance.shutdown()
|
| 141 |
bot_instance = QuantumBot(socketio, app)
|
| 142 |
+
|
| 143 |
+
# --- Definitive Fix: Capture and send detailed error message ---
|
| 144 |
+
is_success, error_message = bot_instance.initialize_driver()
|
| 145 |
+
if is_success:
|
| 146 |
+
emit('bot_initialized')
|
| 147 |
+
else:
|
| 148 |
+
emit('error', {'message': f'Failed to initialize automation bot: {error_message}'})
|
| 149 |
|
| 150 |
@socketio.on('start_login')
|
| 151 |
def handle_login(credentials):
|
| 152 |
if not bot_instance: return emit('error', {'message': 'Bot not initialized.'})
|
| 153 |
+
is_success, error_message = bot_instance.login(credentials['username'], credentials['password'])
|
| 154 |
+
if is_success: emit('otp_required')
|
| 155 |
+
else: emit('error', {'message': f'Login failed: {error_message}'})
|
| 156 |
|
| 157 |
@socketio.on('submit_otp')
|
| 158 |
def handle_otp(data):
|
| 159 |
if not bot_instance: return emit('error', {'message': 'Bot not initialized.'})
|
| 160 |
+
is_success, error_message = bot_instance.submit_otp(data['otp'])
|
| 161 |
+
if is_success:
|
| 162 |
emit('login_successful')
|
| 163 |
session_id = 'user_session'
|
| 164 |
+
socketio.start_background_task(target=run_automation_process, args=(session_id,))
|
| 165 |
+
else: emit('error', {'message': f'OTP failed: {error_message}'})
|
| 166 |
|
| 167 |
@socketio.on('terminate_process')
|
| 168 |
def handle_terminate():
|
worker.py
CHANGED
|
@@ -6,9 +6,6 @@ import threading
|
|
| 6 |
from selenium import webdriver
|
| 7 |
from selenium.webdriver.chrome.service import Service as ChromeService
|
| 8 |
from selenium.webdriver.chrome.options import Options as ChromeOptions
|
| 9 |
-
from webdriver_manager.chrome import ChromeDriverManager
|
| 10 |
-
# --- Definitive Fix: Import ChromeType from its new, correct location ---
|
| 11 |
-
from webdriver_manager.core.os_manager import ChromeType
|
| 12 |
from selenium.webdriver.common.by import By
|
| 13 |
from selenium.webdriver.support.ui import WebDriverWait
|
| 14 |
from selenium.webdriver.support import expected_conditions as EC
|
|
@@ -31,23 +28,25 @@ class QuantumBot:
|
|
| 31 |
options.add_argument("--window-size=1920,1080")
|
| 32 |
options.add_argument("--no-sandbox")
|
| 33 |
options.add_argument("--disable-dev-shm-usage")
|
|
|
|
| 34 |
options.add_argument('--disable-blink-features=AutomationControlled')
|
| 35 |
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
| 36 |
options.add_experimental_option('useAutomationExtension', False)
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
|
|
|
| 40 |
|
| 41 |
self.driver = webdriver.Chrome(service=service, options=options)
|
| 42 |
|
| 43 |
self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
|
| 44 |
'source': "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
|
| 45 |
})
|
| 46 |
-
return True
|
| 47 |
except Exception as e:
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
return False
|
| 51 |
|
| 52 |
def micro_status(self, message):
|
| 53 |
print(f"[Bot Action] {message}")
|
|
@@ -69,10 +68,11 @@ class QuantumBot:
|
|
| 69 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
|
| 70 |
self.micro_status("Waiting for OTP screen...")
|
| 71 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "code1")))
|
| 72 |
-
return True
|
| 73 |
except Exception as e:
|
| 74 |
-
|
| 75 |
-
|
|
|
|
| 76 |
|
| 77 |
def submit_otp(self, otp):
|
| 78 |
try:
|
|
@@ -83,10 +83,11 @@ class QuantumBot:
|
|
| 83 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
|
| 84 |
self.micro_status("Verifying login success...")
|
| 85 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//span[text()='Payments']")))
|
| 86 |
-
return True
|
| 87 |
except Exception as e:
|
| 88 |
-
|
| 89 |
-
|
|
|
|
| 90 |
|
| 91 |
def process_patient_list(self, patient_list):
|
| 92 |
results = []
|
|
@@ -94,17 +95,11 @@ class QuantumBot:
|
|
| 94 |
if self.termination_event.is_set():
|
| 95 |
print("[Bot] Termination detected. Stopping process.")
|
| 96 |
break
|
| 97 |
-
|
| 98 |
with self.app.app_context():
|
| 99 |
-
self.socketio.emit('stats_update', {
|
| 100 |
-
'processed': len(results),
|
| 101 |
-
'remaining': len(patient_list) - len(results)
|
| 102 |
-
})
|
| 103 |
-
|
| 104 |
self.micro_status(f"Processing '{patient_name}' ({index + 1}/{len(patient_list)})...")
|
| 105 |
status = self._process_single_patient(patient_name)
|
| 106 |
results.append({'Name': patient_name, 'Status': status})
|
| 107 |
-
|
| 108 |
with self.app.app_context():
|
| 109 |
self.socketio.emit('log_update', {'name': patient_name, 'status': status})
|
| 110 |
return results
|
|
@@ -113,7 +108,6 @@ class QuantumBot:
|
|
| 113 |
try:
|
| 114 |
self.micro_status(f"Navigating to Void page for '{patient_name}'")
|
| 115 |
self.driver.get("https://gateway.quantumepay.com/credit-card/void")
|
| 116 |
-
|
| 117 |
search_successful = False
|
| 118 |
for attempt in range(15):
|
| 119 |
try:
|
|
@@ -127,16 +121,13 @@ class QuantumBot:
|
|
| 127 |
break
|
| 128 |
except Exception: time.sleep(1)
|
| 129 |
if not search_successful: raise Exception("Failed to search for patient.")
|
| 130 |
-
|
| 131 |
time.sleep(3)
|
| 132 |
self.micro_status("Opening transaction details...")
|
| 133 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, f"//tr[contains(., \"{patient_name}\")]//button[@data-v-b6b33fa0]"))).click()
|
| 134 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.LINK_TEXT, "Transaction Detail"))).click()
|
| 135 |
-
|
| 136 |
self.micro_status("Adding to Vault...")
|
| 137 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Add to Vault']"))).click()
|
| 138 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//div[@class='modal-footer']//button/span[normalize-space()='Confirm']"))).click()
|
| 139 |
-
|
| 140 |
try:
|
| 141 |
self.micro_status("Verifying success and saving...")
|
| 142 |
company_input = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.NAME, "company_name")))
|
|
|
|
| 6 |
from selenium import webdriver
|
| 7 |
from selenium.webdriver.chrome.service import Service as ChromeService
|
| 8 |
from selenium.webdriver.chrome.options import Options as ChromeOptions
|
|
|
|
|
|
|
|
|
|
| 9 |
from selenium.webdriver.common.by import By
|
| 10 |
from selenium.webdriver.support.ui import WebDriverWait
|
| 11 |
from selenium.webdriver.support import expected_conditions as EC
|
|
|
|
| 28 |
options.add_argument("--window-size=1920,1080")
|
| 29 |
options.add_argument("--no-sandbox")
|
| 30 |
options.add_argument("--disable-dev-shm-usage")
|
| 31 |
+
options.add_argument("--disable-gpu") # Added for stability in Linux environments
|
| 32 |
options.add_argument('--disable-blink-features=AutomationControlled')
|
| 33 |
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
| 34 |
options.add_experimental_option('useAutomationExtension', False)
|
| 35 |
|
| 36 |
+
# --- Definitive Fix: Use the explicit driver path installed by Docker ---
|
| 37 |
+
# This removes the dependency on webdriver-manager for production.
|
| 38 |
+
service = ChromeService(executable_path='/usr/bin/chromedriver')
|
| 39 |
|
| 40 |
self.driver = webdriver.Chrome(service=service, options=options)
|
| 41 |
|
| 42 |
self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
|
| 43 |
'source': "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
|
| 44 |
})
|
| 45 |
+
return True, None
|
| 46 |
except Exception as e:
|
| 47 |
+
error_message = f"Error during WebDriver initialization: {str(e)}"
|
| 48 |
+
print(f"CRITICAL ERROR in WebDriver Initialization: {error_message}")
|
| 49 |
+
return False, error_message
|
| 50 |
|
| 51 |
def micro_status(self, message):
|
| 52 |
print(f"[Bot Action] {message}")
|
|
|
|
| 68 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
|
| 69 |
self.micro_status("Waiting for OTP screen...")
|
| 70 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "code1")))
|
| 71 |
+
return True, None
|
| 72 |
except Exception as e:
|
| 73 |
+
error_message = f"Error during login process: {str(e)}"
|
| 74 |
+
print(f"[Bot] ERROR during login: {error_message}")
|
| 75 |
+
return False, error_message
|
| 76 |
|
| 77 |
def submit_otp(self, otp):
|
| 78 |
try:
|
|
|
|
| 83 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
|
| 84 |
self.micro_status("Verifying login success...")
|
| 85 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//span[text()='Payments']")))
|
| 86 |
+
return True, None
|
| 87 |
except Exception as e:
|
| 88 |
+
error_message = f"Error during OTP submission: {str(e)}"
|
| 89 |
+
print(f"[Bot] ERROR during OTP submission: {error_message}")
|
| 90 |
+
return False, error_message
|
| 91 |
|
| 92 |
def process_patient_list(self, patient_list):
|
| 93 |
results = []
|
|
|
|
| 95 |
if self.termination_event.is_set():
|
| 96 |
print("[Bot] Termination detected. Stopping process.")
|
| 97 |
break
|
|
|
|
| 98 |
with self.app.app_context():
|
| 99 |
+
self.socketio.emit('stats_update', {'processed': len(results), 'remaining': len(patient_list) - len(results)})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
self.micro_status(f"Processing '{patient_name}' ({index + 1}/{len(patient_list)})...")
|
| 101 |
status = self._process_single_patient(patient_name)
|
| 102 |
results.append({'Name': patient_name, 'Status': status})
|
|
|
|
| 103 |
with self.app.app_context():
|
| 104 |
self.socketio.emit('log_update', {'name': patient_name, 'status': status})
|
| 105 |
return results
|
|
|
|
| 108 |
try:
|
| 109 |
self.micro_status(f"Navigating to Void page for '{patient_name}'")
|
| 110 |
self.driver.get("https://gateway.quantumepay.com/credit-card/void")
|
|
|
|
| 111 |
search_successful = False
|
| 112 |
for attempt in range(15):
|
| 113 |
try:
|
|
|
|
| 121 |
break
|
| 122 |
except Exception: time.sleep(1)
|
| 123 |
if not search_successful: raise Exception("Failed to search for patient.")
|
|
|
|
| 124 |
time.sleep(3)
|
| 125 |
self.micro_status("Opening transaction details...")
|
| 126 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, f"//tr[contains(., \"{patient_name}\")]//button[@data-v-b6b33fa0]"))).click()
|
| 127 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.LINK_TEXT, "Transaction Detail"))).click()
|
|
|
|
| 128 |
self.micro_status("Adding to Vault...")
|
| 129 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Add to Vault']"))).click()
|
| 130 |
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//div[@class='modal-footer']//button/span[normalize-space()='Confirm']"))).click()
|
|
|
|
| 131 |
try:
|
| 132 |
self.micro_status("Verifying success and saving...")
|
| 133 |
company_input = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.NAME, "company_name")))
|