diff --git a/.gitignore b/.gitignore index c4812a3..e6b900b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .venv/ .vscode/ .workbooks/ -*.xlsx \ No newline at end of file +*.xlsx +*.xml \ No newline at end of file diff --git a/销售订单自动导入.py b/销售订单自动导入.py index ecf85f3..52cb83f 100644 --- a/销售订单自动导入.py +++ b/销售订单自动导入.py @@ -23,11 +23,14 @@ parser.add_argument('--vf-token', type=str, required=True) parser.add_argument('--xm-web-url', type=str, nargs='?', default='https://login.xiaoman.cn/login/') parser.add_argument('--vf-api-url', type=str, nargs='?', default='https://ultimatron-france.vosfactures.fr/') parser.add_argument('--outdir', type=str, nargs='?', default='.') +parser.add_argument('--per-page', type=int, nargs='?', default=5) +parser.add_argument('--currency', type=str, required=True) +parser.add_argument('--department', type=str, required=True) parser.add_argument('-a', '--automation', type=str, choices=['none', 'draft', 'final'], nargs='?', default='none') parser.add_argument('-e', '--encoding', type=str, nargs='?', default='utf-8') parser.add_argument('-o', '--output', type=str, nargs='?', default='') parser.add_argument('-t', '--timeout', type=int, nargs='?', default=60) -parser.add_argument('-i', '--interval', type=int, nargs='?', default=3) +parser.add_argument('-i', '--interval', type=int, nargs='?', default=5) args = parser.parse_args() date = datetime.now() @@ -88,12 +91,12 @@ def main(): rows = products[products.isin([value]).any(axis=1)] data = rows.to_dict(orient='list') try: return data.get(fieldname)[0] - except: return '' + except: return None def text(element, fieldname) -> str: child = element.find(fieldname) if bool(child.text): return child.text.strip() - else: return '' + else: return None clients = RelationMap(lambda x: text(x, 'client-id')) invoices = RelationMap(lambda x: text(x, 'number')) @@ -175,6 +178,7 @@ def main(): '订单号', '订单日期', '当前处理人', + '业绩归属部门', '客户编号', '币种', '产品名称', @@ -185,7 +189,6 @@ def main(): '单价', '数量', '产品描述', - '订单金额', ) for invoice in invoices.map.values(): @@ -199,14 +202,15 @@ def main(): id = lookup(code, '产品编号') product = lookup(code, '产品名称') description = lookup(code, '产品描述') - price = float(text(position, 'price-net')) + price = float(text(position, 'price-net') or '0') discount = float(text(position, 'discount-percent') or '0') - quantity = float(text(position, 'quantity')) + quantity = float(text(position, 'quantity') or '0') data.append('订单日期', issue_date) data.append('当前处理人', category) + data.append('业绩归属部门', args.department) data.append('客户编号', client) - data.append('币种', 'USD') + data.append('币种', args.currency) data.append('订单号', number) data.append('产品名称', product) data.append('产品编号', id) @@ -266,9 +270,12 @@ def main(): wait = WebDriverWait(parent, timeout=args.timeout) element = wait.until(EC.presence_of_element_located(locator)) - + # 查看元素 driver.execute_script("arguments[0].scrollIntoView({ block: 'center', inline: 'nearest' });", element) - element = wait.until(condition(locator)) + + if condition is not None: + wait = WebDriverWait(parent, timeout=args.timeout) + element = wait.until(condition(locator)) return element except StaleElementReferenceException: @@ -326,7 +333,7 @@ def main(): wait = WebDriverWait(driver, timeout=(args.timeout * 4)) wait.until(lambda x: x.find_element(By.CSS_SELECTOR, ".img-box-result-title").get_attribute('innerText') == '导入完成') # 查看导入结果 - locate(".mm-notification-container .mm-icon-close", wait=False).click() + locate(".mm-notification-container .mm-icon-close").click() locate(".product-import-img-footer button.mm-button__primary").click() locate(".mm-tbody table tbody tr:nth-child(1) td:nth-child(3) a").click() driver.switch_to.window(driver.window_handles[2]) @@ -334,82 +341,112 @@ def main(): print(f'[!!!!] 等待订单录入时发生了错误:{e}') return 84 + # 设置每页显示记录数 time.sleep(args.interval) #6 + driver.get(driver.current_url.replace('page_size%22%3A20%2C%22', f'page_size%22%3A{args.per_page}%2C%22')) + modified = [] while True: - links = driver.find_elements(By.CSS_SELECTOR, ".list-frame-table .virtual-list-os-target .order-name-wrap a.jump-link") - try: - for link in links: - link.click() + # 等待页面加载 + wait = WebDriverWait(driver, timeout=args.timeout) + wait.until(lambda x: 'nprogress-busy' not in x.find_element(By.TAG_NAME, 'html').get_attribute('class')) + time.sleep(args.interval) #7 + + for idx in range(args.per_page): + try: + links = driver.find_elements(By.CSS_SELECTOR, ".list-frame-table .virtual-list-os-target .row-items .cell[data-cci='1'] a") + element = links[idx] + except Exception as e: + print(f'[警告] 未找到记录: {e}') + break + + try: + driver.execute_script("arguments[0].click();", element) driver.switch_to.window(driver.window_handles[3]) # 编辑订单 locate(".component-detail-frame-header .okki-space-item:nth-child(1) button").click() warn = False - number = locate(".order-edit-header .edit-order-no span span:nth-child(1)").text + header = locate(".order-edit-header .edit-order-no span span:nth-child(1)") + number = header.text + invoice = invoices.map.get(number) + positions = invoice.find('positions').findall('*') - # 选择商机 - try: - dropdown = locate(".component-business-select .mm-selector-rendered") - dropdown.click() - for item in driver.find_elements(By.CSS_SELECTOR, ".mm-outside.mm-select-dropdown ul li span"): - if item.text.startswith(proformas.getValueOf(invoice)['number']): - driver.execute_script("arguments[0].scrollIntoView({ block: 'center', inline: 'nearest' });", item) - item.click() - break - except Exception as e: - print(f"[警告] 发票 '{number}' 录入商机时发生错误:{e}") - warn = True + if number not in modified: + # 选择商机 + try: + index = 1 + dropdown = locate(".component-business-select .mm-selector-rendered") + dropdown.click() - # 编辑运费 - try: - wrapper = driver.find_element(By.CSS_SELECTOR, ".order-edit-product-wrapper .virtual-list-os-target") - driver.execute_script("arguments[0].scrollIntoView({ block: 'center', inline: 'end' });", wrapper) + while True: + element = locate(f".mm-outside.mm-select-dropdown ul li:nth-child({index}) span") + index += 1 + if element.text.startswith(proformas.getValueOf(invoice)['number']): + driver.execute_script("arguments[0].scrollIntoView({ block: 'center', inline: 'nearest' });", element) + element.click() + break + except Exception as e: + print(f"[警告] {number}: 录入商机时发生错误:{e}") + warn = True - elements = wrapper.find_elements(By.CSS_SELECTOR, ".row-item") - elements.reverse() + # 编辑运费 + try: + wrapper = locate(".order-edit-product-wrapper .virtual-list-os-target .row-items", condition=None) + limit = len(positions) - for item in elements: - if item.find_element(By.CSS_SELECTOR, ".product-info-group-product-name input").get_attribute('value') == 'port': - target = locate("input[colindex='6']", parent=item) - source = locate("input[colindex='7']", parent=item) - # 填入含税成本价 - price = source.get_attribute('value') - target.send_keys(price) - break - except Exception as e: - print(f"[警告] 发票 '{number}' 编辑运费时发生错误:{e}") - warn = True + for idx in range(limit): + driver.execute_script("arguments[0].scrollTo(0, arguments[0].scrollHeight);", wrapper) + items = wrapper.find_elements(By.CSS_SELECTOR, "div.vue-recycle-scroller__item-wrapper div.vue-recycle-scroller__item-view") + driver.execute_script("arguments[0].scrollIntoView({ block: 'start', inline: 'end' });", items[idx]) + product = locate(f".product-info-group-product-name input", parent=items[-1], condition=None) + + if product.get_attribute('value') == 'port': + # 定位元素 + driver.execute_script("arguments[0].scrollTo(0, arguments[0].scrollHeight);", wrapper) + locate(".cell[data-cci='4'] input", parent=items[-1], condition=None) + locate(".cell[data-cci='5'] input", parent=items[-1], condition=None) + + target = locate(".cell[data-cci='6'] input", parent=items[-1], condition=None) + source = locate(".cell[data-cci='7'] input", parent=items[-1], condition=None) + # 填入含税成本价 + price = source.get_attribute('value') + target.send_keys(price) + break + except Exception as e: + print(f"[警告] {number}: 编辑运费时发生错误:{e}") + warn = True + + # 保存订单 + button = locate(".order-edit-footer button.okki-btn-primary", condition=None) + driver.execute_script("arguments[0].click();", button) + + time.sleep(args.interval) #8 + print(f"[信息] {number}: 修改完成") + modified.append(number) + + global success + success += 1 + + global warning + if warn: warning += 1 - # 保存订单 - locate(".order-edit-footer button.okki-btn-primary").click() # 关闭标签页 driver.close() driver.switch_to.window(driver.window_handles[2]) - - global success - success += 1 - - global warning - if warn: warning += 1 - except Exception as e: - print(f'[!!!!] 编辑订单时发生了错误:{e}') - return 85 + except Exception as e: + print(f'[!!!!] 编辑订单时发生了错误:{e}') + return 85 try: button = locate(".okki-pagination-next button", wait=False) - driver.execute_script("arguments[0].click()", button) - time.sleep(args.interval) #7 if bool(button.get_attribute('disabled')): raise Exception() + driver.execute_script("arguments[0].click()", button) except: print('[信息] 已经是最后一页') break - # 等待页面加载 - wait = WebDriverWait(driver, timeout=args.timeout) - wait.until(lambda x: 'nprogress-busy' not in x.find_element(By.TAG_NAME, 'html').get_attribute('class')) - return 0 class RelationMap: @@ -437,7 +474,7 @@ class FieldArray[K, V]: array = self.map.get(key) array.insert(self.index, value) - def newrow(self, padding=''): + def newrow(self, padding=None): for array in self.map.values(): if len(array) <= self.index: array.insert(self.index, padding) @@ -446,5 +483,5 @@ class FieldArray[K, V]: try: status = main() except KeyboardInterrupt: status = 145 -print(f'[信息] 已录入 {success} 个订单;收到 {warning} 条警告信息') +print(f'[信息] 已修改 {success} 个订单,其中包含 {warning} 条警告信息') print(f'[信息] 总耗时 {datetime.now() - date}')