--- name: tongtool-y2-lowest-shipping description: Batch process TEMU semi-managed orders in Tongtool ERP to calculate the lowest shipping price, skipping printed/shipped orders, and saving remarks using Web UI + API fallback. --- # Tongtool TEMU Lowest Shipping Calculator & Remark This skill automates the process of finding the lowest shipping method for TEMU semi-managed orders in Tongtool, skipping orders that are already processed, utilizing the TEMU platform API for calculation, and saving the result robustly using a Web UI + API Fallback mechanism. ## Prerequisites - Python 3 - `DrissionPage` - `ddddocr` - `requests` ## Crucial Platform Integration Discoveries & Pitfalls ### 1. Tongtool Open API V3 `ordersQuery` Signature Bug When querying `/openapi/tongtool/ordersQuery`, passing date filters (`saleDate`, `payDate`, `updatedDate`, etc.) frequently results in a **`519 sign fail`** or **`525 cannot be all empty`** error. The official V3 signature logic is broken for this specific endpoint when url-encoding date parameters. **Workaround:** Fetch orders from pre-existing local caches (e.g., scraping results), or query by specific `orderId`, or query strictly by `orderStatus: "1"` (Processing) without any date parameters. ### 2. Business Logic for Skipping Orders (Order Status Rules) Do **not** perform shipping calculations for orders if they meet any of these criteria based on the API `ordersQuery` details response: * `orderStatus` is `shipped` (物流商发货), `completed` (签收), or `cancelled`. * `printCompleteTime` has a value (通途已打印). * `despatchCompleteTime` has a value (通途已发货). * The order already has a remark containing `[H201试算]` (assuming it isn't an error marker). ### 3. Robust Remark Writing (Web UI + API Fallback) Because Tongtool's Web UI is highly asynchronous, `textarea` injection and Javascript `.click()` on the save button occasionally fails silently. The robust pipeline must: 1. Write the remark via Web UI layout injection. 2. Wait a few seconds array. 3. Verify via API (`/openapi/tongtool/ordersRemarkQuery`) using `orderIdKey`. 4. If missing, Fallback to API write (`/openapi/tongtool/addOrderRemark`). ## The Pipeline Script This script logs in, filters and sorts target orders (ascending by the last 4 digits of the order ID), extracts the cheapest price via the shipping analysis popup, and securely writes it back. ```python import time, json, re, hashlib, requests, os from DrissionPage import ChromiumPage, ChromiumOptions import ddddocr ACCESS_KEY = "b4163dfec4654fd480c1cadeafea8ae7" SECRET_KEY = "0fe4f51c1b8840e6bd70394ce6ec74f638a25524decb45b2adbb7ae5d56bfcdf" PARTNER_OPEN_ID = "ec1d0a0ac9bceca98ed62e1504351371" BASE_URL = "https://open.tongtool.com/apiv3-service" SUCCESS_PREFIX = '[H201试算]' ERROR_REMARK = '[H201试算]出错没找到最低价' def get_token_and_sign(): r1 = requests.get( f"https://open.tongtool.com/open-platform-service/devApp/appToken?accessKey={ACCESS_KEY}&secretAccessKey={SECRET_KEY}", headers={"Content-Type": "application/json"} ) app_token = r1.json()["datas"] timestamp = str(int(time.time() * 1000)) raw_sign = f"app_token{app_token}timestamp{timestamp}{SECRET_KEY}" sign = hashlib.md5(raw_sign.encode()).hexdigest() return {"app_token": app_token, "timestamp": timestamp, "sign": sign} def api_call(path, body): _params = get_token_and_sign() headers = {"Content-Type": "application/json", "api_version": "3.0"} for _ in range(3): try: resp = requests.post(f"{BASE_URL}{path}", params=_params, headers=headers, json=body, timeout=20) return resp.json() except: time.sleep(2) return {} def filter_and_sort_orders(raw_orders_list, limit=5): """Filter out completed/shipped/printed orders, and sort by ID last 4 digits""" unique_orders = list(set(raw_orders_list)) valid_pool = [] for oid in unique_orders: res = api_call("/openapi/tongtool/ordersQuery", { "merchantId": PARTNER_OPEN_ID, "pageNo": "1", "pageSize": "100", "orderId": oid }) if res.get("code") != 200 or not res.get("datas", {}).get("array"): continue info = res["datas"]["array"][0] # 1. Skip based on status and timestamps if info.get("orderStatus") in ["shipped", "completed", "cancelled"]: continue if info.get("printCompleteTime"): continue if info.get("despatchCompleteTime"): continue # 2. Skip already processed ones marks_res = api_call("/openapi/tongtool/ordersRemarkQuery", { "merchantId": PARTNER_OPEN_ID, "orderIdKey": info.get("orderIdKey"), "pageNo": "1", "pageSize": "10" }) already_success = False if marks_res.get("code") == 200 and marks_res.get("datas"): for m in marks_res["datas"].get("array", []): text = m.get("orderRemark", "") if SUCCESS_PREFIX in text and '出错' not in text: already_success = True break if already_success: continue valid_pool.append(oid) # Sort ascending by the last 4 digits def last_4(o): m = re.search(r'(\\d{4})$', o) return int(m.group(1)) if m else 9999 valid_pool.sort(key=last_4) return valid_pool[:limit] def calculate_temu_lowest_shipping_and_remark(order_id: str, driver): def nuke_popups(page): try: page.run_js(""" document.querySelectorAll('.layui-layer-shade, .layui-layer, .modal, .overlay, .jclose-blue, .close, [title="关闭"], .layui-layer-close').forEach(el => { try { el.remove(); } catch(e){} }); """) except: pass driver.get("https://yijiety.tongtool.com/dashboard/homepage/index.htm") time.sleep(4) nuke_popups(driver) search_url = ( f"https://yijiety.tongtool.com/search/order.htm" f"?search_text_value={order_id}&search_mark=salesRecordNumber&source=global" ) driver.get(search_url) time.sleep(8) nuke_popups(driver) def open_shipping_popup(): driver.run_js(""" for (const el of document.querySelectorAll('*')) { if (el.textContent.trim() === '仓储物流' && el.offsetHeight > 0) { el.click(); return true; } } return false; """) time.sleep(3) driver.run_js(""" for (const el of document.querySelectorAll('a, button, span')) { if (el.textContent.trim() === '编辑') { const r = el.getBoundingClientRect(); if (r.width > 0 && r.height > 0 && r.height < 80) { el.click(); return true; } } } return false; """) time.sleep(4) clicked_analysis = driver.run_js(""" for (const a of document.querySelectorAll('a')) { if (a.textContent.trim() === '邮寄方式解析') { a.click(); return true; } } return false; """) if not clicked_analysis: return False time.sleep(6) return driver.run_js("return !!document.querySelector('div[windowid] #tabpanelDiv');") if not open_shipping_popup(): return None def extract_temu_cheapest(): try: driver.run_js(""" const popups = document.querySelectorAll('div[windowid]'); let popup = null; for (const p of popups) { if (p.querySelector('#tabpanelDiv')) { popup = p; break; } } if (!popup) return; const tab = popup.querySelector('li[tabtitle="TEMU平台接口试算"]'); if (tab) tab.click(); """) time.sleep(4) driver.run_js(""" const popups = document.querySelectorAll('div[windowid]'); let popup = null; for (const p of popups) { if (p.querySelector('#tabpanelDiv')) { popup = p; break; } } if (!popup) return; const temuDiv = popup.querySelector('#temuShippingMethod'); if (!temuDiv || temuDiv.classList.contains('hide')) return; for (const b of temuDiv.querySelectorAll('a, button')) { if (b.textContent.includes('查询') || b.textContent.includes('试算')) { b.click(); return; } } """) time.sleep(8) prices = driver.run_js(""" const popup = Array.from(document.querySelectorAll('div[windowid]')).find(p => p.querySelector('#tabpanelDiv')); if (!popup) return []; const temuDiv = popup.querySelector('#temuShippingMethod') || popup; const out = []; for (const tr of temuDiv.querySelectorAll('tr')) { const tds = tr.querySelectorAll('td'); if (tds.length < 3) continue; const rowText = tr.textContent || ''; let priceMatch = rowText.match(/\\$\\s*(\\d+\\.\\d{2})/); let currency = '$'; if (!priceMatch) { priceMatch = rowText.match(/[¥¥]\\s*(\\d+\\.\\d{2})/); currency = '¥'; } if (!priceMatch) continue; // Skip disabled options ("未启用") entirely if (rowText.includes('未启用')) continue; const price = parseFloat(priceMatch[1]); if (isNaN(price) || price <= 0) continue; let carrier = '', method = ''; for (const td of tds) { const t = td.textContent.trim(); if (!t) continue; if (t.includes(priceMatch[0])) continue; if (!carrier) { carrier = t; continue; } if (!method) { method = t; break; } } if (!carrier && !method) continue; out.push({carrier, method, price, raw: priceMatch[0], currency}); } return out; """) if not prices: return None return min(prices, key=lambda x: x['price']) except Exception as e: return None cheapest = extract_temu_cheapest() driver.run_js(""" document.querySelectorAll('.jclose-blue, .close, [title="关闭"], div[windowid] .layui-layer-close').forEach(b => { try { b.click(); } catch(e){} }); document.querySelectorAll('div[windowid], .layui-layer, .layui-layer-shade').forEach(e => e.remove()); """) time.sleep(2) if cheapest: c = (cheapest.get('carrier') or '').strip() m = (cheapest.get('method') or '').strip() label = f"{c}>>{m}" if c and m else (c or m) remark = f"{SUCCESS_PREFIX}{label} {cheapest['currency']}{cheapest['price']:.2f}" # --- NEW LOGIC: Update order's actual shipping method in Tongtool based on lowest price --- # NOTE: Tongtool automatically handles binding the top-level logistics provider (e.g. 湖北众信 for US East, 易世通达 for US West). # We MUST NOT change the top-level provider. We only select the correct last-mile option (USPS, GOFO, Swift, etc.) associated with that existing provider. def get_best_method_code(method_str): # Method mapping: match common last-mile carrier names method_upper = method_str.upper() sub_method = None if "USPS" in method_upper: sub_method = "USPS" elif "GOFO" in method_upper: sub_method = "GOFO" elif "SWIFT" in method_upper: sub_method = "SwiftX" if "SWIFTX" in method_upper else "Swift" elif "SPEED" in method_upper: sub_method = "SpeedX" if "SPEEDX" in method_upper else "Speed" else: return method_str return sub_method def update_shipping_method(driver, best_sub_method): if not best_sub_method: return False try: # Check current selected method first current_method_full = driver.run_js(""" // The shipping method text input const inputs = document.querySelectorAll('input.layui-input'); for (const input of inputs) { if (input.value.includes('>>')) { return input.value; } } return ""; """) if current_method_full and best_sub_method.upper() in current_method_full.upper(): return True # Already correct # Parse current provider to stay within it (e.g. "湖北众信" or "易世通达") current_provider = None if current_method_full: if "湖北众信" in current_method_full: current_provider = "湖北众信" elif "易世通达" in current_method_full: current_provider = "易世通达" if not current_provider: # If we can't extract the current provider, we can't safely ensure we stay within it. return False # Find dropdown trigger and click it opened = driver.run_js(""" const inputs = document.querySelectorAll('input.layui-input'); for(const input of inputs) { if(input.value.includes('>>') || input.placeholder.includes('方式')) { input.click(); return true; } } return false; """) if not opened: return False time.sleep(1) # Click the option (MUST contain both the current provider and the new best sub_method) clicked = driver.run_js(f""" const items = document.querySelectorAll('dd'); for (const item of items) {{ const text = item.textContent.toUpperCase(); if (text.includes('{current_provider}') && text.includes('{best_sub_method.upper()}')) {{ item.click(); return true; }} }} return false; """) if clicked: # Save changes for the entire order driver.run_js(""" for (const b of document.querySelectorAll('button')) { if (b.textContent.trim() === '保存' && !b.closest('#orderRemarkForm')) { b.click(); return true; } } return false; """) time.sleep(2) return True return False except Exception as e: return False target_sub_method = get_best_method_code(m) update_shipping_method(driver, target_sub_method) # ----------------------------------------------------------------------------------------- else: remark = ERROR_REMARK remark_btn = driver.ele('css:#addOrderRemark') if remark_btn: a_tag = remark_btn.ele('tag:a') if a_tag: a_tag.click() time.sleep(2) textarea = driver.ele('tag:textarea') if textarea: js = f''' var ta = document.querySelector('textarea'); if(ta) {{ ta.value = '{remark}'; ta.dispatchEvent(new Event('input', {{bubbles: true}})); ta.dispatchEvent(new Event('change', {{bubbles: true}})); }} ''' driver.run_js(js) time.sleep(1) for a in driver.eles('tag:a'): if a.text.strip() == '保存': a.click(by_js=True) time.sleep(3) break return remark # Example usage integrating verify & fallback... # order_id_key can be fetched via generic ordersQuery beforehand. # remark = calculate_temu_lowest_shipping_and_remark(order_id, driver) # -> Verify -> api_call("/openapi/tongtool/ordersRemarkQuery", ...) # -> Fallback -> api_call("/openapi/tongtool/addOrderRemark", ...) ```