Added uploads, part renaming, bulk data import acceptance
This commit is contained in:
@@ -663,6 +663,218 @@ def set_eda_from_capacitance(api: PartDB, part_id: int, *, max_wait_s: int = 12,
|
||||
if time.time() >= deadline: return False
|
||||
time.sleep(poll_every)
|
||||
|
||||
def accept_bulk_import_jobs(driver, base_url: str, lang: str, job_url: str = None, max_iterations: int = 100) -> Tuple[int, int, int]:
|
||||
"""
|
||||
Automates accepting bulk import jobs by:
|
||||
1. Finding and marking skipped parts (cards with "0 results found") as pending
|
||||
2. Finding the first selectable "Update part" button in a border-success card
|
||||
3. Clicking it and waiting for page load
|
||||
4. Clicking "Save" and waiting for page load
|
||||
5. Clicking "Save" again and waiting for page load
|
||||
6. Clicking "Complete" and waiting for page load
|
||||
7. Repeating until no more selectable buttons exist
|
||||
|
||||
Returns (successful_count, failed_count, skipped_count)
|
||||
"""
|
||||
# Navigate to the job/import page if URL provided
|
||||
if job_url:
|
||||
driver.get(job_url)
|
||||
time.sleep(1.5)
|
||||
|
||||
successful = 0
|
||||
failed = 0
|
||||
total_skipped = 0
|
||||
|
||||
for iteration in range(max_iterations):
|
||||
print(f"\n[Accept Jobs] Iteration {iteration + 1}/{max_iterations}")
|
||||
|
||||
# Scroll to top first to ensure we see all buttons
|
||||
try:
|
||||
driver.execute_script("window.scrollTo(0, 0);")
|
||||
time.sleep(0.5)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# First check for skipped parts (border-warning cards with "0 results found" or "No results found")
|
||||
# and mark them as skipped by clicking "Mark Pending"
|
||||
skipped_count = 0
|
||||
try:
|
||||
# Find cards with border-warning that have "0 results found" badge or "No results found" alert
|
||||
warning_cards = driver.find_elements(By.XPATH, "//div[contains(@class, 'card') and contains(@class, 'border-warning')]")
|
||||
|
||||
for card in warning_cards:
|
||||
try:
|
||||
# Check if it has "0 results found" badge or "No results found" message
|
||||
has_no_results = False
|
||||
try:
|
||||
badge = card.find_element(By.XPATH, ".//span[contains(@class, 'badge') and contains(@class, 'bg-info') and contains(., 'results found')]")
|
||||
if badge and '0 results' in badge.text:
|
||||
has_no_results = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not has_no_results:
|
||||
try:
|
||||
alert = card.find_element(By.XPATH, ".//div[contains(@class, 'alert-info') and contains(., 'No results found')]")
|
||||
if alert:
|
||||
has_no_results = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if has_no_results:
|
||||
# This card should be skipped, click "Mark Skipped" button if available
|
||||
try:
|
||||
mark_skipped_btn = card.find_element(By.XPATH, ".//button[contains(., 'Mark Skipped')]")
|
||||
if mark_skipped_btn and mark_skipped_btn.is_displayed():
|
||||
driver.execute_script("arguments[0].scrollIntoView({block:'center', behavior:'smooth'});", mark_skipped_btn)
|
||||
time.sleep(0.3)
|
||||
try:
|
||||
mark_skipped_btn.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", mark_skipped_btn)
|
||||
skipped_count += 1
|
||||
print(f"[Accept Jobs] Marked card as skipped (no results found)")
|
||||
time.sleep(0.5)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
if skipped_count > 0:
|
||||
print(f"[Accept Jobs] Marked {skipped_count} cards as pending (no results)")
|
||||
total_skipped += skipped_count
|
||||
time.sleep(1.0) # Wait after marking items as pending
|
||||
except Exception as e:
|
||||
print(f"[Accept Jobs] Error checking for skipped cards: {e}")
|
||||
|
||||
# Find all "Update part" buttons that are NOT disabled (no 'disabled' in class)
|
||||
update_button = None
|
||||
try:
|
||||
# Find <a> or <button> elements with "Update Part" text that don't have 'disabled' in their class
|
||||
# Must be within btn-group-vertical and must not have 'disabled' class
|
||||
possible_xpaths = [
|
||||
"//a[contains(@class, 'btn') and not(contains(@class, 'disabled')) and contains(., 'Update Part')]",
|
||||
"//a[contains(@class, 'btn') and not(contains(@class, 'disabled')) and contains(., 'Update part')]",
|
||||
"//button[contains(@class, 'btn') and not(contains(@class, 'disabled')) and contains(., 'Update Part')]",
|
||||
"//button[contains(@class, 'btn') and not(contains(@class, 'disabled')) and contains(., 'Update part')]",
|
||||
]
|
||||
|
||||
for xpath in possible_xpaths:
|
||||
try:
|
||||
elements = driver.find_elements(By.XPATH, xpath)
|
||||
for el in elements:
|
||||
# Double-check: must not have 'disabled' in class attribute
|
||||
class_attr = el.get_attribute('class') or ''
|
||||
if 'disabled' in class_attr.lower():
|
||||
continue
|
||||
if el.is_displayed() and el.is_enabled():
|
||||
# Found a valid button, use the first one
|
||||
update_button = el
|
||||
print(f"[Accept Jobs] Found button with text: '{el.text.strip()}' and class: '{class_attr}'")
|
||||
break
|
||||
if update_button:
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"[Accept Jobs] Error with xpath {xpath}: {e}")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Accept Jobs] Error finding buttons: {e}")
|
||||
break
|
||||
|
||||
if not update_button:
|
||||
print("[Accept Jobs] No more selectable 'Update part' buttons (without 'disabled' class) found. Done.")
|
||||
break
|
||||
|
||||
# Click the button and wait for page load
|
||||
try:
|
||||
# Scroll into view
|
||||
driver.execute_script("arguments[0].scrollIntoView({block:'center', behavior:'smooth'});", update_button)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Click the button
|
||||
try:
|
||||
update_button.click()
|
||||
except Exception:
|
||||
# Fallback to JS click
|
||||
driver.execute_script("arguments[0].click();", update_button)
|
||||
|
||||
print("[Accept Jobs] Clicked 'Update part' button, waiting for page load...")
|
||||
time.sleep(2.5) # Wait for page to load
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Accept Jobs] Failed to click button: {e}")
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
# Now on the same page (no tab switch) - click Save first time
|
||||
save_success = False
|
||||
try:
|
||||
print("[Accept Jobs] Looking for first 'Save' button...")
|
||||
ok = click_xpath_robust(driver, [
|
||||
"//button[@type='submit' and @id='part_base_save']",
|
||||
"//button[@type='submit' and contains(@name, 'save')]",
|
||||
"//button[normalize-space()='Save changes']",
|
||||
"//button[contains(normalize-space(), 'Save changes')]",
|
||||
"//button[normalize-space()='Save']",
|
||||
"//input[@type='submit' and contains(@value, 'Save')]",
|
||||
], total_timeout=15)
|
||||
|
||||
if ok:
|
||||
print("[Accept Jobs] First 'Save' clicked, waiting for page load...")
|
||||
time.sleep(2.5) # Wait for page to load
|
||||
|
||||
# Click Save second time
|
||||
print("[Accept Jobs] Looking for second 'Save' button...")
|
||||
ok = click_xpath_robust(driver, [
|
||||
"//button[@type='submit' and @id='part_base_save']",
|
||||
"//button[@type='submit' and contains(@name, 'save')]",
|
||||
"//button[normalize-space()='Save changes']",
|
||||
"//button[contains(normalize-space(), 'Save changes')]",
|
||||
"//button[normalize-space()='Save']",
|
||||
"//input[@type='submit' and contains(@value, 'Save')]",
|
||||
], total_timeout=15)
|
||||
|
||||
if ok:
|
||||
print("[Accept Jobs] Second 'Save' clicked, waiting for page load...")
|
||||
time.sleep(2.5) # Wait for page to load
|
||||
|
||||
# Click Complete
|
||||
print("[Accept Jobs] Looking for 'Complete' button...")
|
||||
ok = click_xpath_robust(driver, [
|
||||
"//button[normalize-space()='Complete']",
|
||||
"//button[contains(normalize-space(), 'Complete')]",
|
||||
"//input[@type='submit' and contains(@value, 'Complete')]",
|
||||
"//a[contains(normalize-space(), 'Complete')]",
|
||||
], total_timeout=15)
|
||||
|
||||
if ok:
|
||||
print("[Accept Jobs] 'Complete' clicked successfully!")
|
||||
save_success = True
|
||||
time.sleep(2.0) # Wait for completion to process
|
||||
else:
|
||||
print("[Accept Jobs] Failed to find/click 'Complete' button")
|
||||
else:
|
||||
print("[Accept Jobs] Failed to find/click second 'Save' button")
|
||||
else:
|
||||
print("[Accept Jobs] Failed to find/click first 'Save' button")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Accept Jobs] Error during save/complete sequence: {e}")
|
||||
|
||||
if save_success:
|
||||
successful += 1
|
||||
print(f"[Accept Jobs] Job accepted successfully! (Total: {successful})")
|
||||
else:
|
||||
failed += 1
|
||||
print(f"[Accept Jobs] Job failed! (Total failed: {failed})")
|
||||
|
||||
# Small delay before next iteration
|
||||
time.sleep(1.0)
|
||||
|
||||
print(f"\n[Accept Jobs] Complete: {successful} successful, {failed} failed, {total_skipped} skipped")
|
||||
return (successful, failed, total_skipped)
|
||||
|
||||
def update_part_from_providers_once(part_id: int, *, headless: bool = True) -> Tuple[bool, str]:
|
||||
"""
|
||||
Run the same 'Tools → Info providers → Update' flow once for a part.
|
||||
|
||||
Reference in New Issue
Block a user