diff --git a/skills/archived/tongtool-erp-automation/SKILL.md b/skills/archived/tongtool-erp-automation/SKILL.md new file mode 100644 index 0000000..243795e --- /dev/null +++ b/skills/archived/tongtool-erp-automation/SKILL.md @@ -0,0 +1,423 @@ +--- +name: tongtool-erp-automation +description: Automate Tongtool (通途) ERP using DrissionPage — login, CAPTCHA solving, tab switching, iframe navigation, and popup handling. +--- + +# Tongtool ERP Automation + +Use this skill when the user asks to log in, navigate, or extract data from the Tongtool ERP system (`tongtool.com`). + +## Setup +Ensure `DrissionPage` and `ddddocr` are installed. + +```python +from DrissionPage import ChromiumPage, ChromiumOptions +import ddddocr, os, shutil, time + +co = ChromiumOptions() +co.set_browser_path('/home/ubuntu/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome') +co.set_argument('--no-sandbox') +co.set_argument('--disable-dev-shm-usage') +co.headless() +co.auto_port() +driver = ChromiumPage(co) +``` + +## 1. Login — Two Methods + +### 1a. Headless DrissionPage Login + CAPTCHA (Server-side) +Target: `https://passport.tongtool.com/` +Credentials are usually `ozonezsjgw@163.com` / `111111`. + +```python +import re, requests + +def correct_ocr(raw_code): + """Fix common ddddocr misreads for Tongtool captcha.""" + return raw_code.translate(str.maketrans('oliOSsZz', '01105522')) + +def login(driver, user, pwd, max_retry=5): + for attempt in range(max_retry): + driver.get('https://passport.tongtool.com/') + time.sleep(3) + driver.ele('css:input[name="username"]').input(user) + driver.ele('css:input[name="password"]').input(pwd) + + # CAPTCHA: selector is img.pic-yzm + img = driver.ele('css:img.pic-yzm') + src = img.attr('src') + cookies = {c['name']: c['value'] for c in driver.cookies()} + resp = requests.get(src, cookies=cookies, timeout=10) + raw = ddddocr.DdddOcr(show_ad=False).classification(resp.content) + code = correct_ocr(raw) + driver.ele('css:input[name="captcha"]').input(code) + + driver.ele('css:button.btn-primary').click() + time.sleep(6) + + # Wait for redirect from /check to /member + for _ in range(5): + if 'member' in driver.url: + return True + time.sleep(2) + + # Check if still on login page + if 'passport' in driver.url: + body = driver.ele('tag:body').text + if '验证码输入错误' in body: + continue # retry + elif '密码' in body and '错误' in body: + raise ValueError('Wrong password') + return False +``` + +**Key points**: +- Captcha image selector: `img.pic-yzm` (not `@src^=/api/common/code`). +- Login button: `button.btn-primary` (not `text:立即登录`). +- After submit, URL lands on `/check` then redirects to `member.tongtool.com` — wait for `member` in URL. +- `correct_ocr()` fixes common ddddocr misreads (o→0, l→1, O→0, S→5, s→5, Z→2, z→2). + +### 1b. CDP-Based Login via Bridge (Alternative — Desktop-side) + +Use this when headless login is blocked by device verification or captcha. The Desktop Chrome is already a trusted device, so no captcha or device approval is needed. + +**Prerequisites**: A Desktop slot connected to Bridge with CDP enabled. Trigger `/cdp/snapshot` first to establish CDP session. + +**Key CDP evaluate response structure**: `r['result']['result']['value']` — Bridge returns `{ok: true, result: {result: {type: "...", value: ...}}}`. + +**Flow**: +1. Navigate: `POST /cdp/navigate` -> `https://passport.tongtool.com/` +2. Fill username via native setter: +`Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(inp, 'ozonezsjgw@163.com')` +3. Fill password via native setter +4. Click login button by text `立即登录` +5. Wait for redirect: `member.tongtool.com` +6. Navigate to ERP: `https://yijiety.tongtool.com/dashboard/homepage/index.htm` + +**Full Python helper**: +```python +def evaluate(js, slot='desktop-mq27qgb0'): + r = bridge_post('/cdp/evaluate', {'slot': slot, 'expression': js}) + if r.get('ok') and r.get('result') and r['result'].get('result'): + return r['result']['result'].get('value') + return r +``` + +## 2. Enter ERP +After successful login (`member.tongtool.com`), the simplest and most reliable way to enter the ERP is **direct navigation** — no tab switching needed. + +```python +# Method 1 (Recommended): Direct navigation +driver.get("https://yijiety.tongtool.com/dashboard/homepage/index.htm") +time.sleep(8) # SPA takes time to bootstrap + +# Verify by checking body text +body = driver.ele('tag:body').text +if '发货管理' in body: + print("✅ ERP loaded") +else: + print("❌ ERP not loaded") +``` + +**Deprecated**: The old "click 进入系统 then switch tab" approach is flaky in headless mode. Direct navigation is faster and more stable because session cookies are already set. + +## 3. TEMU Semi-Managed Order — Bulk Shipping Info & Remark +### Complete workflow: login → wait stock page → filter TEMU → read shipping → add remark + +**Typical scenario**: The user wants to find the cheapest shipping method for TEMU semi-managed orders in "等待配货" status and record it in each order's remark. + +**Critical finding**: For TEMU semi-managed orders in **等待配货** status, the system has **already auto-calculated and assigned the cheapest shipping method** (e.g., 湖北众信 EJET-SpeedX). There is NO need to trigger an explicit "运费试算" — the logistics provider, tracking method, and price are already visible in the order detail panel once you click the order row. + +### 3.1 Navigate to Wait Stock page +```python +driver.get("https://yijiety.tongtool.com/warehouse/assignStockSelfMaintenance/index.htm") +time.sleep(10) +``` + +### 3.2 Set warehouse via hidden input (custom `t_combo_box`) +The warehouse dropdown is a **custom `t_combo_box` component** that cannot be triggered by standard clicks. The reliable method is: + +```python +def set_warehouse(driver, warehouse_name): + """Set warehouse by manipulating hidden input + clicking the TD cell.""" + # Option A: use the hidden input directly + js = f""" + var hidden = document.querySelector('input[name="warehouseSelect"]'); + if (hidden) {{ + hidden.value = '{warehouse_name}'; + var event = new Event('change', {{ bubbles: true }}); + hidden.dispatchEvent(event); + return 'set via hidden input'; + }} + return 'hidden input not found'; + """ + result = driver.run_js(js) + time.sleep(2) + + # Option B: click the TD that contains the warehouse text + js2 = f""" + var tds = document.querySelectorAll('td'); + for (var i=0; i