diff --git a/apps/SeleniumService/selenium_CCA_claimSubmitWorker.py b/apps/SeleniumService/selenium_CCA_claimSubmitWorker.py index 291cdc7d..de3d34bf 100644 --- a/apps/SeleniumService/selenium_CCA_claimSubmitWorker.py +++ b/apps/SeleniumService/selenium_CCA_claimSubmitWorker.py @@ -343,6 +343,20 @@ class AutomationCCAClaimSubmit: 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 Claim 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 Claim step2] No multiple-records popup") + # Wait for eligibility result print("[CCA Claim step2] Waiting for eligibility result...") try: @@ -449,11 +463,43 @@ class AutomationCCAClaimSubmit: # Extract Encounter ID from the FIRST row (newest claim is at the top) claim_number = None - # Try table cells first (Encounter ID is the first column) - for sel in [ + # Pick the latest claim for this member: rows matching DOB, highest Encounter ID + try: + def _variants(d): + d = self._format_dob(d) if d else "" + if not d: + return [] + m, dd, y = d.split("/") + return list({d, f"{int(m)}/{int(dd)}/{y}", f"{m}/{dd}/{y[-2:]}"}) + dos_v = _variants(self.serviceDate) + dob_v = _variants(self.dateOfBirth) + rows = self.driver.execute_script(""" + return Array.from(document.querySelectorAll('table tbody tr')).map(function(tr){ + return Array.from(tr.querySelectorAll('td')).map(function(td){return td.innerText.trim();}); + }).filter(function(c){return c.length;}); + """) or [] + # Rows for this member (by DOB); the largest Encounter ID is the latest claim + id_rows = [c for c in rows if re.match(r'\d{10,}', c[0])] + dob_rows = [c for c in id_rows if any(v in " | ".join(c) for v in dob_v)] + if not dob_rows: + # DOB not shown/matched — fall back to the service date + dob_rows = [c for c in id_rows if any(v in " | ".join(c) for v in dos_v)] + if dob_rows: + best = max(dob_rows, key=lambda c: int(re.match(r'\d+', c[0]).group(0))) + claim_number = re.match(r'\d+', best[0]).group(0) + print(f"[CCA Claim step4] Encounter ID {claim_number} = latest of " + f"{len(dob_rows)} row(s) for DOB {dob_v[:1]}: {best}") + else: + print(f"[CCA Claim step4] No row matched DOB={dob_v} or DOS={dos_v}; " + f"falling back to top row. Rows seen: {rows[:6]}") + except Exception as e: + print(f"[CCA Claim step4] Row matching failed: {e!r}") + + # Fallback: top row (Encounter ID is the first column) + for sel in ([] if claim_number else [ (By.XPATH, "//table//tbody//tr[1]/td[1]"), (By.XPATH, "//*[contains(@class,'wbx-table-results')]//tr[1]/td[1]"), - ]: + ]): try: cell = WebDriverWait(self.driver, 8).until( EC.presence_of_element_located(sel)) @@ -806,27 +852,56 @@ class AutomationCCAClaimSubmit: print(f"[CCA Claim grid] Filled {input_id}={value!r}") return True except Exception as e: - print(f"[CCA Claim grid] Fill {input_id} failed: {e}") + print(f"[CCA Claim grid] Fill {input_id} failed: {e!r}") return False + def click_cell_and_fill_retry(col_expr, input_id, value, attempts=3): + """Retry when the editor input doesn't appear after the cell click + (the grid sometimes hasn't finished committing the previous row).""" + from selenium.webdriver.common.action_chains import ActionChains + for attempt in range(1, attempts + 1): + if click_cell_and_fill(col_expr, input_id, value): + return True + print(f"[CCA Claim grid] {input_id} attempt {attempt}/{attempts} failed") + time.sleep(1.5) + if attempt >= 2: + # Escape any stray popup/editor and try a double-click on the cell + try: + ActionChains(self.driver).send_keys(Keys.ESCAPE).perform() + cell = self.driver.find_element( + By.XPATH, + f"(//div[contains(@class,'e-gridcontent')]//tr)[{row_num}]/td[{col_expr}]") + ActionChains(self.driver).double_click(cell).perform() + time.sleep(0.5) + field = self.driver.find_element(By.ID, input_id) + field.send_keys(Keys.CONTROL + "a") + field.send_keys(Keys.DELETE) + field.send_keys(str(value)) + print(f"[CCA Claim grid] Filled {input_id}={value!r} via double-click") + return True + except Exception as e: + print(f"[CCA Claim grid] Double-click fallback failed: {e!r}") + return False + # CODE - code_ok = click_cell_and_fill(col_map["CODE"], "ServicesCODE", code) + code_ok = click_cell_and_fill_retry(col_map["CODE"], "ServicesCODE", code) + time.sleep(1.0) # let the grid finish its code lookup before the next cell # TOOTH — skip if empty tooth_ok = True if tooth: - tooth_ok = click_cell_and_fill(col_map["TOOTH"], "ServicesTOOTH", tooth) + tooth_ok = click_cell_and_fill_retry(col_map["TOOTH"], "ServicesTOOTH", tooth) # Surfaces — skip if empty if surface_chars: surf_ids = ["ServicesSURF1","ServicesSURF2","ServicesSURF3", "ServicesSURF4","ServicesSURF5"] for si, char in enumerate(surface_chars[:5]): - click_cell_and_fill(col_map[f"SURF{si+1}"], surf_ids[si], char) + click_cell_and_fill_retry(col_map[f"SURF{si+1}"], surf_ids[si], char) # Billed Amount # QTY and Service Date auto-fill after CODE is entered and this cell is clicked - billed_ok = click_cell_and_fill(col_map["BILLED_AMOUNT"], "ServicesBILLED_AMOUNT", billed_str) + billed_ok = click_cell_and_fill_retry(col_map["BILLED_AMOUNT"], "ServicesBILLED_AMOUNT", billed_str) if not (code_ok and tooth_ok and billed_ok): missing = [name for name, ok in @@ -954,23 +1029,36 @@ class AutomationCCAClaimSubmit: # ---- Handle confirmation modal popup ---- print("[CCA Claim step3] Waiting for confirmation modal...") - time.sleep(1.5) modal_confirmed = False - for sel in [ + modal_selectors = [ # Most specific: Submit/Confirm/Yes button inside an active modal (By.XPATH, "//div[contains(@class,'modal') and contains(@class,'in')]//button[contains(text(),'Submit') or contains(text(),'Confirm') or contains(text(),'Yes')]"), (By.XPATH, "//div[@class='modal-footer']//button[contains(text(),'Submit') or contains(text(),'Confirm') or contains(text(),'Yes')]"), (By.XPATH, "//button[@ng-click and (contains(@ng-click,'confirm') or contains(@ng-click,'Confirm') or contains(@ng-click,'submit') or contains(@ng-click,'Submit'))]"), (By.XPATH, "//div[contains(@class,'modal')]//button[not(contains(@class,'cancel')) and not(contains(text(),'Cancel')) and not(contains(text(),'No'))]"), - ]: - try: - btn = WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable(sel)) - btn.click() - modal_confirmed = True - print(f"[CCA Claim step3] Clicked modal confirm via {sel}") - break - except Exception: - continue + ] + # Poll for the modal's own button first (patterns 1-2). The looser patterns (3-4) + # also match the page's own "Submit Claim" button behind the popup, so they are + # only tried after the modal-specific ones have had time to appear. + start = time.monotonic() + while time.monotonic() - start < 8 and not modal_confirmed: + active = modal_selectors[:2] if time.monotonic() - start < 5 else modal_selectors + for sel in active: + try: + btn = next((b for b in self.driver.find_elements(*sel) + if b.is_displayed() and b.is_enabled()), None) + if btn: + btn.click() + modal_confirmed = True + print(f"[CCA Claim step3] Clicked modal confirm via {sel}") + break + except Exception: + continue + if not modal_confirmed: + time.sleep(0.3) + + if not modal_confirmed: + time.sleep(0.3) if not modal_confirmed: print("[CCA Claim step3] No confirmation modal found — may have auto-submitted") @@ -978,7 +1066,7 @@ class AutomationCCAClaimSubmit: # Wait for final confirmation page print("[CCA Claim step3] Waiting for submission confirmation...") try: - WebDriverWait(self.driver, 30).until( + WebDriverWait(self.driver, 10).until( lambda d: any(x in d.find_element(By.TAG_NAME, "body").text.lower() for x in [ "claim submitted", "claim number", "confirmation", "success", "has been submitted", "claim id", "assigned the number",