Added uploads, part renaming, bulk data import acceptance

This commit is contained in:
2025-12-17 13:57:47 +11:00
parent aaa1f7520a
commit ae9e1d6e7e
14 changed files with 3325 additions and 11 deletions

View File

@@ -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.