update: bump 'common' version to 0.1.18
This commit is contained in:
+10
-17
@@ -130,12 +130,10 @@
|
||||
|
||||
<script type="module">
|
||||
import { default as $, Rpc2, LogRecord } from '/';
|
||||
if (performance.getEntriesByType('navigation')[0].type === 'reload') await Rpc2.invoke('exit');
|
||||
if (performance.getEntriesByType('navigation')[0].type === 'reload') await Rpc2.notify('exit');
|
||||
|
||||
let error = null;
|
||||
let status = null;
|
||||
let { profiles, parameters } = await Rpc2.invoke('context');
|
||||
|
||||
let status = null;
|
||||
let date = new Date();
|
||||
let day = date.getDay() || 7;
|
||||
$('#datefrom').valueAsNumber = date.setHours(-24 * (day - 1)) - date.getTimezoneOffset() * 60 * 1000;
|
||||
@@ -160,7 +158,7 @@ $.set('#begin', 'click', async () => {
|
||||
parameters[element.id] = element.valueAsNumber;
|
||||
}
|
||||
|
||||
await Rpc2.invoke('begin', options, parameters);
|
||||
await Rpc2.invoke('begin', { params: [options, parameters] });
|
||||
break;
|
||||
case 'RUNNING':
|
||||
await Rpc2.invoke('pause');
|
||||
@@ -180,12 +178,12 @@ $.set('#begin', 'click', () => {
|
||||
|
||||
$.set('#cancel', 'click', async () => {
|
||||
$('#cancel').disabled = true;
|
||||
await Rpc2.invoke('cancel');
|
||||
await Rpc2.notify('cancel');
|
||||
});
|
||||
|
||||
$.set('#skip', 'click', async () => {
|
||||
$('#skip').disabled = true;
|
||||
await Rpcs.invoke('skip');
|
||||
await Rpc2.notify('skip');
|
||||
});
|
||||
|
||||
$.set('#logs', 'change', (e) => {
|
||||
@@ -219,12 +217,12 @@ for (let item of $.all("input[type='number']")) {
|
||||
}
|
||||
|
||||
while (await new Promise(o => setTimeout(o, 1000, true))) {
|
||||
let history = await Rpc2.invoke('history').catch(() => []);
|
||||
let history = await Rpc2.invoke('logs').catch(() => []);
|
||||
let logs = Array.from(history);
|
||||
|
||||
for (let record of logs) {
|
||||
if ($('#messages').childNodes.length >= 500) $('#messages').childNodes.item(0)?.remove();
|
||||
if (record.levelno >= 40) error = record;
|
||||
if (record.levelno >= 40) alert(`(${record.levelname}) ` + [record.msg, record.exc_text].filter(Boolean).join('\n'));
|
||||
let message = LogRecord.format(record);
|
||||
let node = document.createTextNode(new String(message).concat('\n'));
|
||||
$('#messages').appendChild(node);
|
||||
@@ -247,22 +245,17 @@ while (await new Promise(o => setTimeout(o, 1000, true))) {
|
||||
$('#begin > span.text').innerText = 'Pause';
|
||||
$('#begin').classList.add('pulse');
|
||||
|
||||
let progress = await Rpc2.invoke('progress').catch(() => new Object());
|
||||
let { task, number, index, limit } = progress;
|
||||
let { task, number, index, limit } = await Rpc2.invoke('progress').catch(() => new Object());
|
||||
$('#numberLabel').innerText = number ?? '';
|
||||
$('#progressLabel').innerText = limit ? `${task}, ${parseFloat((index / limit * 100).toFixed(2))}% (${index}/${limit})` : task;
|
||||
$('#progressLabel').innerText = limit ? `${task} — ${parseFloat((index / limit * 100).toFixed(2))}% (${index}/${limit})` : task;
|
||||
|
||||
let [t1, t2] = await Rpc2.invoke('uptime').catch(() => []);
|
||||
$('#uptimeLabel').innerText = Temporal.Duration.from({ seconds: t1 ?? 0 }).round({ largestUnit: 'hours' }).toLocaleString('en', { style: 'digital' });
|
||||
|
||||
let remaining = index && limit && t2 ? Math.floor((limit - index) / (index / t2)) : 0;
|
||||
$('#remainingLabel').innerHTML = remaining ? Temporal.Duration.from({ seconds: remaining }).round({ largestUnit: 'hours' }).toLocaleString('en') : '';
|
||||
$('#remainingLabel').innerText = remaining > 0 ? Temporal.Duration.from({ seconds: remaining }).round({ largestUnit: 'hours' }).toLocaleString('en') : '';
|
||||
break;
|
||||
case 'STANDBY':
|
||||
if (error !== null) {
|
||||
alert(`(${error.levelname}) ${error.msg}\n${error.exc_text ?? ''}`);
|
||||
error = null;
|
||||
}
|
||||
$('#begin > span.text').innerText = 'Resume';
|
||||
$('#begin').classList.remove('pulse');
|
||||
break;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import unicodedata
|
||||
import argparse
|
||||
import openpyxl
|
||||
import logging
|
||||
@@ -20,7 +19,7 @@ from enum import Enum
|
||||
from wakepy import keep
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from itertools import repeat, count
|
||||
from itertools import count
|
||||
from urllib3 import PoolManager
|
||||
|
||||
parser = argparse.ArgumentParser(description="Order Import")
|
||||
@@ -45,8 +44,8 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
class Status(Enum):
|
||||
IDLE = 0
|
||||
READY = 1
|
||||
RUNNING = 3
|
||||
STANDBY = 4
|
||||
RUNNING = 2
|
||||
STANDBY = 3
|
||||
|
||||
class Profile:
|
||||
def __init__(self, name, subdomain, token, remise, person=None, prefix=None, suffix=None):
|
||||
@@ -88,9 +87,7 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
t1.start()
|
||||
t2.start()
|
||||
|
||||
sp.set('begin', begin)
|
||||
sp.set('pause', pause)
|
||||
sp.set('resume', resume)
|
||||
sp.add(begin, pause, resume)
|
||||
sp.set('status', lambda: status.name)
|
||||
sp.set('uptime', lambda: [t1.delta()])
|
||||
|
||||
@@ -120,7 +117,7 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
|
||||
setup(driver, parameters)
|
||||
until(lambda x: 'loginProgress' in x.find_element(By.TAG_NAME, "body").get_attribute('class'), watch=False)
|
||||
logger.info('Waiting for authentication to complete...')
|
||||
logger.info('Waiting for authentication...')
|
||||
|
||||
if (account := parameters['account']) and (password := parameters['password']):
|
||||
try:
|
||||
@@ -186,8 +183,8 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
p = ProductInfo(file)
|
||||
driver.close()
|
||||
driver.switch_to.window(driver.window_handles[0])
|
||||
status = Status.READY
|
||||
logger.info('Done')
|
||||
status = Status.READY
|
||||
except Exception as e:
|
||||
logger.critical('Unable to load products', exc_info=e)
|
||||
return 4
|
||||
@@ -200,7 +197,7 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning('Error while fetching data from %s, retrying... (%d)', url, attempt, exc_info=e)
|
||||
assert attempt < retry
|
||||
assert attempt < retry, "Exceeded maximum retry attempts"
|
||||
|
||||
class Wait(Action):
|
||||
@classmethod
|
||||
@@ -247,18 +244,13 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
raise cls
|
||||
|
||||
flow = ActionFlow()
|
||||
flow.append(Wait)
|
||||
flow.append(Sleep)
|
||||
flow.append(Cancel)
|
||||
flow.append(Skip)
|
||||
|
||||
flow.stage(Wait, Sleep, Cancel, Skip)
|
||||
profile = None
|
||||
progress = { 'task': '' }
|
||||
selection = 0
|
||||
|
||||
sp.add(pairs=[ (k.lower(), v) for k, v in flow ])
|
||||
sp.set('actions', lambda: flow.capabilities())
|
||||
sp.set('cancel', lambda: flow.queue(Cancel))
|
||||
sp.set('skip', lambda: flow.queue(Skip))
|
||||
sp.set('progress', lambda: progress)
|
||||
|
||||
while not flow.react(Wait):
|
||||
@@ -303,7 +295,7 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
logger.info('Profile selected: %s', profile.name)
|
||||
logger.info('Date from %s to %s', df, dt)
|
||||
flow.allow(Cancel)
|
||||
flow.allow(Skip, False)
|
||||
flow.deter(Skip)
|
||||
|
||||
for page in count(1):
|
||||
try:
|
||||
@@ -388,17 +380,17 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
if 'error' in (category := fetch(f'{base}/categories/{o}.json?api_token={profile.token}')):
|
||||
error = category['error']
|
||||
code = category['code']
|
||||
logger.warning("Error while fetching 'category' (code: %s, message: %s); skipping", code, error)
|
||||
logger.warning("Error while fetching field 'category' (code: %s, message: %s); skipping", code, error)
|
||||
continue
|
||||
|
||||
if (client := clients.get(o := item['client_id'])) is None:
|
||||
if 'error' in (client := fetch(f'{base}/clients/{o}.json?api_token={profile.token}')):
|
||||
error = client['error']
|
||||
code = client['code']
|
||||
logger.warning("Error while fetching 'client' (code: %s, message: %s); skipping", code, error)
|
||||
logger.warning("Error while fetching field 'client' (code: %s, message: %s); skipping", code, error)
|
||||
continue
|
||||
|
||||
identity = client['external_id'] if client['company'] else profile.person
|
||||
identity = client['shortcut'] if client['company'] else profile.person
|
||||
date: str = item['issue_date']
|
||||
kind: str = item['kind']
|
||||
total = float(item['price_net'])
|
||||
@@ -453,7 +445,7 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
try:
|
||||
filename = f'Order-Import-{profile.name}-{datetime.now().strftime('%Y%m%d-%H%M%S-%f')}.xlsx'
|
||||
file = Path(parameters['directory']).joinpath(filename)
|
||||
logger.info('Saving excel file to %s', str(file))
|
||||
logger.info('Saving document at %s', str(file))
|
||||
flow.react(Wait)
|
||||
workbook.save(file)
|
||||
except Skip:
|
||||
@@ -461,12 +453,12 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
except Cancel:
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error('Error while saving excel file', exc_info=e)
|
||||
logger.error('Error while saving document', exc_info=e)
|
||||
status = Status.STANDBY
|
||||
continue
|
||||
|
||||
try:
|
||||
logger.info('Uploading...')
|
||||
logger.info('Uploading data...')
|
||||
progress.clear()
|
||||
progress['task'] = 'Task 3 of 4'
|
||||
t2.clear()
|
||||
@@ -477,29 +469,26 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
click(".product-import-img-box .import-img-radio:nth-child(2) .mm-radio-group > label:nth-child(2) .mm-radio-input", condition=None)
|
||||
click(".product-import-img-box .mm-selector-rendered")
|
||||
click(".mm-outside.mm-select-dropdown ul li:nth-child(%d) span" % (1 if options.get('draft') else 6))
|
||||
|
||||
flow.react(Wait)
|
||||
locate(".big-file-upload input", wait=False).send_keys(str(file))
|
||||
click(".product-import-img-footer button")
|
||||
click(".product-import-img-footer button.mm-button__primary")
|
||||
|
||||
while True:
|
||||
while not flow.react(Wait, Sleep):
|
||||
try:
|
||||
innerText = driver.find_element(By.CSS_SELECTOR, ".img-box-result-title").get_attribute('innerText')
|
||||
if innerText == '导入完成': break
|
||||
except:
|
||||
flow.react(Sleep)
|
||||
|
||||
click(".mm-notification-container .mm-icon-close")
|
||||
click(".product-import-img-footer button.mm-button__primary")
|
||||
err = locate(".mm-tbody table tbody tr:nth-child(1) td:nth-child(5) .okki-space-item:nth-child(1) button")
|
||||
err = locate(".mm-tbody table tbody tr:nth-child(1) td:nth-child(5) .okki-space-item:nth-child(1) button", wait=False)
|
||||
ok = locate(".mm-tbody table tbody tr:nth-child(1) td:nth-child(3) a", wait=False)
|
||||
break
|
||||
except: pass
|
||||
try: click(".product-import-img-footer button.mm-button__primary", wait=False)
|
||||
except: pass
|
||||
try: click(".list-header-wrap button", wait=False)
|
||||
except: pass
|
||||
|
||||
if err.get_attribute('disabled') is None:
|
||||
logger.warning('Incomplete import detected; downloaded 1 related document')
|
||||
err.click()
|
||||
click(err, condition=None)
|
||||
logger.warning('Incomplete import detected; downloading 1 related document')
|
||||
flow.react(Sleep)
|
||||
|
||||
click(".mm-tbody table tbody tr:nth-child(1) td:nth-child(3) a")
|
||||
click(ok, condition=None)
|
||||
flow.react(Sleep)
|
||||
logger.info('Done')
|
||||
except Skip:
|
||||
@@ -507,7 +496,7 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
except Cancel:
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error('Error while uploading excel file', exc_info=e)
|
||||
logger.error('Error while uploading document', exc_info=e)
|
||||
status = Status.STANDBY
|
||||
continue
|
||||
|
||||
@@ -542,6 +531,7 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
while index < len(data):
|
||||
try:
|
||||
attempts += 1
|
||||
if len(driver.window_handles) > 3: driver.close()
|
||||
driver.switch_to.window(driver.window_handles[2])
|
||||
item = data[index]
|
||||
kind = item['kind']
|
||||
@@ -551,21 +541,16 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
opportunity = None
|
||||
|
||||
if kind != 'vat':
|
||||
logger.info('[%d/%d] Skipping %s', index+1, len(data), number)
|
||||
index += 1
|
||||
attempts = 0
|
||||
continue
|
||||
logger.info('[%d/%d] Undesired invoice type; skipping %s', index+1, len(data), number)
|
||||
raise Skip()
|
||||
|
||||
if attempts > parameters['attempts']:
|
||||
logger.warning('Exhausted all allowed attempts; skipping %s', number)
|
||||
index += 1
|
||||
attempts = 0
|
||||
continue
|
||||
raise Skip()
|
||||
|
||||
progress['number'] = number
|
||||
progress['index'] = index
|
||||
flow.allow(Cancel)
|
||||
flow.allow(Skip)
|
||||
flow.allow(Skip, Cancel)
|
||||
flow.react(Wait)
|
||||
|
||||
try:
|
||||
@@ -597,17 +582,11 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
flow.react(Wait)
|
||||
click(".sticky .okki-space-item:nth-child(1) button")
|
||||
break
|
||||
except Skip:
|
||||
index += 1
|
||||
attempts = 0
|
||||
continue
|
||||
except Cancel:
|
||||
break
|
||||
except NoSuchElementException:
|
||||
logger.warning("Could not find invoice '%s'; skipping", number)
|
||||
index += 1
|
||||
attempts = 0
|
||||
continue
|
||||
raise Skip()
|
||||
except Action as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error("Error while looking up invoice '%s'", number, exc_info=e)
|
||||
status = Status.STANDBY
|
||||
@@ -638,16 +617,12 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
cell = locate(".virtual-list-wrap .vue-recycle-scroller .row-item > .cell:nth-child(3) .ow-serial-read-pretty_ellipsis", wait=False)
|
||||
if cell.text != match: continue
|
||||
link = locate(".virtual-list-wrap .vue-recycle-scroller .row-item > .cell:nth-child(6) a", wait=False)
|
||||
opportunity = unicodedata.normalize('NFKD', link.get_attribute('title'))
|
||||
break
|
||||
except Skip:
|
||||
index += 1
|
||||
attempts = 0
|
||||
continue
|
||||
except Cancel:
|
||||
opportunity = link.text
|
||||
break
|
||||
except NoSuchElementException:
|
||||
logger.warning("Could not find opportunity '%s'; skipping", match)
|
||||
logger.warning("Could not find opportunity '%s'", match)
|
||||
except Action as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error("Error while looking up opportunity '%s'", match, exc_info=e)
|
||||
status = Status.STANDBY
|
||||
@@ -659,24 +634,29 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
if opportunity is not None:
|
||||
try:
|
||||
flow.react(Wait)
|
||||
label = locate("label.paas-form-item-label")
|
||||
dropdown = locate("#rc_select_1")
|
||||
|
||||
for iteration in count(1):
|
||||
dropdown.click()
|
||||
dropdown.clear()
|
||||
dropdown.send_keys(opportunity)
|
||||
|
||||
menu = locate(".okki-select-dropdown")
|
||||
menuitems = menu.find_elements(By.CSS_SELECTOR, ".rc-virtual-list-holder-inner > div")
|
||||
|
||||
try:
|
||||
for menuitem in menuitems:
|
||||
if menuitem.get_attribute('label').strip().startswith(opportunity):
|
||||
if menuitem.get_attribute('label') == opportunity:
|
||||
click(menuitem)
|
||||
except Skip:
|
||||
index += 1
|
||||
attempts = 0
|
||||
continue
|
||||
except Cancel:
|
||||
break
|
||||
raise StopIteration()
|
||||
except StopIteration: break
|
||||
except: pass
|
||||
assert iteration < parameters['attempts'], "Exceeded maximum retry attempts"
|
||||
click(label, condition=None)
|
||||
except Action as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.warning('Could not select opportunity; skipping', exc_info=e)
|
||||
logger.warning("Could not select opportunity '%s'", opportunity, exc_info=e)
|
||||
|
||||
try:
|
||||
pagination = 10
|
||||
@@ -730,12 +710,8 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
raise Exception('Product list imcomplete; expected %d, got %d' % (len(positions), len(ids)))
|
||||
flow.react(Wait)
|
||||
click(button)
|
||||
except Skip:
|
||||
index += 1
|
||||
attempts = 0
|
||||
continue
|
||||
except Cancel:
|
||||
break
|
||||
except Action as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error('Error while modifying invoice', exc_info=e)
|
||||
status = Status.STANDBY
|
||||
@@ -745,30 +721,24 @@ def main(driver: WebDriver, logger = logging.getLogger('main')):
|
||||
click(".ow-box button.okki-btn-round", wait=False)
|
||||
flow.react(Sleep)
|
||||
except Exception as e:
|
||||
logger.warning('Unable to unset additional fees; skipping', exc_info=e)
|
||||
logger.warning('Unable to unset additional fees', exc_info=e)
|
||||
|
||||
try:
|
||||
flow.react(Wait)
|
||||
flow.allow(Cancel, False)
|
||||
flow.allow(Skip, False)
|
||||
flow.deter(Skip, Cancel)
|
||||
click(".sticky.bottom-0 button.okki-btn-primary", condition=None)
|
||||
flow.react(Sleep)
|
||||
driver.close()
|
||||
except Skip:
|
||||
pass
|
||||
except Cancel:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error('Error while saving document', exc_info=e)
|
||||
logger.error('Unexpected error', exc_info=e)
|
||||
status = Status.STANDBY
|
||||
continue
|
||||
finally:
|
||||
driver.close()
|
||||
|
||||
index += 1
|
||||
attempts = 0
|
||||
except Exception as e:
|
||||
logger.error('Unexpected error', exc_info=e)
|
||||
status = Status.STANDBY
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user