feat: CCA pre-auth — select first member in multi-record popup, retry code/tooth/billed fills, stop instead of submitting incomplete lines
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -363,6 +363,20 @@ class AutomationCCAPreAuth:
|
||||
if not verified:
|
||||
return "ERROR: 'Verify Eligibility' button not found"
|
||||
|
||||
# Multiple records popup: pick the first member (top Select)
|
||||
try:
|
||||
sel_btn = WebDriverWait(self.driver, 12).until(
|
||||
EC.element_to_be_clickable((By.ID, "buttonSelectMember0"))
|
||||
)
|
||||
print("[CCA PreAuth step2] Multiple records popup — clicking top Select")
|
||||
try:
|
||||
sel_btn.click()
|
||||
except Exception:
|
||||
self.driver.execute_script("arguments[0].click();", sel_btn)
|
||||
time.sleep(2)
|
||||
except TimeoutException:
|
||||
print("[CCA PreAuth step2] No multiple-records popup")
|
||||
|
||||
print("[CCA PreAuth step2] Waiting for eligibility result...")
|
||||
try:
|
||||
WebDriverWait(self.driver, 25).until(
|
||||
@@ -681,108 +695,107 @@ class AutomationCCAPreAuth:
|
||||
|
||||
# ── Code: click td[2] to activate → find ServicesCODE by ID globally ──
|
||||
code_filled = False
|
||||
for xpath in [
|
||||
f"//div[@id='Services']//tr[@aria-rowindex='{row_idx}']/td[2]",
|
||||
f"//div[@id='Services']//div[contains(@class,'e-gridcontent')]//tbody/tr[{idx+1}]/td[2]",
|
||||
f"//div[@id='Services']//div[contains(@class,'e-content')]//tr[{idx+1}]/td[2]",
|
||||
]:
|
||||
try:
|
||||
code_cell = self.driver.find_element(By.XPATH, xpath)
|
||||
self.driver.execute_script(
|
||||
"arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", code_cell)
|
||||
code_cell.click()
|
||||
time.sleep(0.5)
|
||||
code_input = WebDriverWait(self.driver, 5).until(
|
||||
EC.presence_of_element_located((By.ID, "ServicesCODE")))
|
||||
code_input.send_keys(code)
|
||||
time.sleep(1.5)
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: code={code!r} entered")
|
||||
code_filled = True
|
||||
for _ca in range(1, 4):
|
||||
for xpath in [
|
||||
f"//div[@id='Services']//tr[@aria-rowindex='{row_idx}']/td[2]",
|
||||
f"//div[@id='Services']//div[contains(@class,'e-gridcontent')]//tbody/tr[{idx+1}]/td[2]",
|
||||
f"//div[@id='Services']//div[contains(@class,'e-content')]//tr[{idx+1}]/td[2]",
|
||||
]:
|
||||
try:
|
||||
code_cell = self.driver.find_element(By.XPATH, xpath)
|
||||
self.driver.execute_script(
|
||||
"arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", code_cell)
|
||||
code_cell.click()
|
||||
time.sleep(0.5)
|
||||
code_input = WebDriverWait(self.driver, 5).until(
|
||||
EC.presence_of_element_located((By.ID, "ServicesCODE")))
|
||||
code_input.send_keys(code)
|
||||
time.sleep(1.5)
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: code={code!r} entered")
|
||||
code_filled = True
|
||||
break
|
||||
except Exception as ce:
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: code attempt {_ca}/3 {ce.__class__.__name__}")
|
||||
continue
|
||||
|
||||
if code_filled:
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
time.sleep(1.5)
|
||||
|
||||
if not code_filled:
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: Code fill failed — skipping row")
|
||||
continue
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: Code fill failed")
|
||||
return (f"ERROR: failed to fill code for service line {idx+1} "
|
||||
f"(code={code}) — pre-auth NOT submitted")
|
||||
|
||||
# ── Tooth: click td[4] in Services grid → wait for input → clear → type ──
|
||||
# ── Tooth: click td[4] → wait for input → clear → type (retry on stale/not-ready) ──
|
||||
if tooth:
|
||||
try:
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
tooth_cell = None
|
||||
tooth_cell_xpaths = [
|
||||
f"//div[@id='Services']//tr[@aria-rowindex='{row_idx}']/td[4]",
|
||||
f"//div[@id='Services']//div[contains(@class,'e-gridcontent')]//tbody/tr[{idx+1}]/td[4]",
|
||||
f"//div[@id='Services']//div[contains(@class,'e-content')]//tr[{idx+1}]/td[4]",
|
||||
]
|
||||
# Re-find cell fresh each attempt to avoid StaleElement
|
||||
for xpath in tooth_cell_xpaths:
|
||||
try:
|
||||
tooth_cell = self.driver.find_element(By.XPATH, xpath)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if tooth_cell:
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
tooth_done = False
|
||||
time.sleep(0.8) # grid re-renders the row after the code is entered
|
||||
for _ta in range(1, 5):
|
||||
try:
|
||||
tooth_cell = None
|
||||
for xpath in [
|
||||
f"//div[@id='Services']//tr[@aria-rowindex='{row_idx}']/td[4]",
|
||||
f"//div[@id='Services']//div[contains(@class,'e-gridcontent')]//tbody/tr[{idx+1}]/td[4]",
|
||||
f"//div[@id='Services']//div[contains(@class,'e-content')]//tr[{idx+1}]/td[4]",
|
||||
]:
|
||||
try:
|
||||
tooth_cell = self.driver.find_element(By.XPATH, xpath)
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if not tooth_cell:
|
||||
raise Exception("tooth cell not found")
|
||||
self.driver.execute_script(
|
||||
"arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", tooth_cell)
|
||||
tooth_cell.click()
|
||||
time.sleep(0.3)
|
||||
if _ta >= 3:
|
||||
ActionChains(self.driver).double_click(tooth_cell).perform()
|
||||
else:
|
||||
tooth_cell.click()
|
||||
time.sleep(0.4)
|
||||
|
||||
# Wait until an input becomes active/focused inside the cell
|
||||
# Fresh lookup every time: prefer the pre-rendered ServicesTOOTH input
|
||||
tooth_input = None
|
||||
for _ in range(10):
|
||||
for _ in range(8):
|
||||
cands = [e for e in self.driver.find_elements(By.ID, "ServicesTOOTH")
|
||||
if e.is_displayed()]
|
||||
if cands:
|
||||
tooth_input = cands[0]
|
||||
break
|
||||
active = self.driver.switch_to.active_element
|
||||
tag = active.tag_name.lower() if active else ""
|
||||
if tag == "input":
|
||||
if active.tag_name.lower() == "input":
|
||||
tooth_input = active
|
||||
break
|
||||
# Try clicking again to trigger edit mode
|
||||
try:
|
||||
tooth_cell = self.driver.find_element(By.XPATH, tooth_cell_xpaths[0])
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
tooth_cell.click()
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.2)
|
||||
time.sleep(0.25)
|
||||
if not tooth_input:
|
||||
raise Exception("tooth input not active")
|
||||
|
||||
if tooth_input:
|
||||
# Clear → Type → Verify (with JS fallback)
|
||||
tooth_input.click()
|
||||
try:
|
||||
tooth_input.send_keys(Keys.CONTROL + "a")
|
||||
tooth_input.send_keys(Keys.DELETE)
|
||||
time.sleep(0.1)
|
||||
for ch in str(tooth):
|
||||
tooth_input.send_keys(ch)
|
||||
time.sleep(0.05)
|
||||
time.sleep(0.2)
|
||||
# Verify value was entered; fall back to JS inject if empty
|
||||
entered = tooth_input.get_attribute("value") or ""
|
||||
if entered.strip() == "":
|
||||
self.driver.execute_script(
|
||||
"arguments[0].value = arguments[1];"
|
||||
"arguments[0].dispatchEvent(new Event('input',{bubbles:true}));"
|
||||
"arguments[0].dispatchEvent(new Event('change',{bubbles:true}));",
|
||||
tooth_input, str(tooth))
|
||||
time.sleep(0.2)
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: tooth={tooth!r} entered")
|
||||
else:
|
||||
# Fallback: JS inject directly into active element
|
||||
active = self.driver.switch_to.active_element
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.2)
|
||||
if (tooth_input.get_attribute("value") or "").strip() != str(tooth):
|
||||
self.driver.execute_script(
|
||||
"arguments[0].value = arguments[1];"
|
||||
"arguments[0].focus();arguments[0].value = arguments[1];"
|
||||
"arguments[0].dispatchEvent(new Event('input',{bubbles:true}));"
|
||||
"arguments[0].dispatchEvent(new Event('change',{bubbles:true}));",
|
||||
active, str(tooth))
|
||||
tooth_input, str(tooth))
|
||||
time.sleep(0.2)
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: tooth={tooth!r} entered via JS fallback")
|
||||
else:
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: Tooth cell not found — skipping")
|
||||
except Exception as e:
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: Tooth fill failed: {e} — skipping tooth")
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: tooth={tooth!r} entered")
|
||||
tooth_done = True
|
||||
break
|
||||
except Exception as te:
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: tooth attempt {_ta}/4: {te.__class__.__name__}")
|
||||
time.sleep(1.0)
|
||||
if not tooth_done:
|
||||
return (f"ERROR: failed to fill tooth for service line {idx+1} "
|
||||
f"(code={code}) — pre-auth NOT submitted")
|
||||
|
||||
# ── Billed Amount: click TD cell to activate edit mode (turn yellow),
|
||||
# wait for input, delete default 0.00, type amount ──
|
||||
@@ -807,38 +820,52 @@ class AutomationCCAPreAuth:
|
||||
billed_cell.click()
|
||||
time.sleep(0.3)
|
||||
|
||||
# Wait until the input activates (cell turns yellow = edit mode)
|
||||
# Activate the editor and type. "element not interactable" happens when the
|
||||
# grid hasn't finished the previous row, so re-click and retry.
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
js_set = (
|
||||
"arguments[0].focus();arguments[0].value = arguments[1];"
|
||||
"arguments[0].dispatchEvent(new Event('input',{bubbles:true}));"
|
||||
"arguments[0].dispatchEvent(new Event('change',{bubbles:true}));")
|
||||
billed_input = None
|
||||
for _ in range(10):
|
||||
for attempt in range(1, 5):
|
||||
try:
|
||||
billed_input = WebDriverWait(self.driver, 1).until(
|
||||
EC.element_to_be_clickable((By.ID, "ServicesBILLED_AMOUNT")))
|
||||
cands = [e for e in self.driver.find_elements(By.ID, "ServicesBILLED_AMOUNT")
|
||||
if e.is_displayed()]
|
||||
if not cands:
|
||||
raise Exception("no visible ServicesBILLED_AMOUNT input")
|
||||
inp = cands[0]
|
||||
try:
|
||||
inp.send_keys(Keys.CONTROL + "a")
|
||||
inp.send_keys(Keys.DELETE)
|
||||
for ch in str(billed_str):
|
||||
inp.send_keys(ch)
|
||||
time.sleep(0.05)
|
||||
except Exception as ke:
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: typing failed ({ke.__class__.__name__}), using JS")
|
||||
self.driver.execute_script(js_set, inp, str(billed_str))
|
||||
time.sleep(0.2)
|
||||
if (inp.get_attribute("value") or "").strip() in ("", "0.00", "0"):
|
||||
self.driver.execute_script(js_set, inp, str(billed_str))
|
||||
time.sleep(0.2)
|
||||
billed_input = inp
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
billed_cell = self.driver.find_element(By.XPATH, billed_cell_xpaths[0])
|
||||
billed_cell.click()
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.2)
|
||||
except Exception as ae:
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: billed attempt {attempt}/4: {ae.__class__.__name__}")
|
||||
time.sleep(0.8)
|
||||
try:
|
||||
billed_cell = self.driver.find_element(By.XPATH, billed_cell_xpaths[0])
|
||||
self.driver.execute_script(
|
||||
"arguments[0].scrollIntoView({block:'nearest',inline:'nearest'});", billed_cell)
|
||||
if attempt >= 2:
|
||||
ActionChains(self.driver).double_click(billed_cell).perform()
|
||||
else:
|
||||
billed_cell.click()
|
||||
time.sleep(0.5)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if billed_input:
|
||||
billed_input.send_keys(Keys.DELETE)
|
||||
time.sleep(0.3)
|
||||
for ch in str(billed_str):
|
||||
billed_input.send_keys(ch)
|
||||
time.sleep(0.05)
|
||||
time.sleep(0.2)
|
||||
# Verify; fallback to JS if empty
|
||||
entered = billed_input.get_attribute("value") or ""
|
||||
if entered.strip() == "":
|
||||
self.driver.execute_script(
|
||||
"arguments[0].value = arguments[1];"
|
||||
"arguments[0].dispatchEvent(new Event('input',{bubbles:true}));"
|
||||
"arguments[0].dispatchEvent(new Event('change',{bubbles:true}));",
|
||||
billed_input, str(billed_str))
|
||||
time.sleep(0.2)
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: billed={billed_str!r} entered")
|
||||
else:
|
||||
print(f"[CCA PreAuth step3] Row {idx+1}: Billed Amount input not activated — skipping")
|
||||
|
||||
Reference in New Issue
Block a user