tb-order-sync 0.4.1 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -0
- package/CHANGELOG.md +67 -0
- package/README.md +15 -1
- package/bin/postinstall.js +21 -0
- package/bin/runtime.js +228 -0
- package/bin/tb.js +11 -108
- package/build.py +1 -0
- package/cli/dashboard.py +167 -29
- package/cli/setup.py +209 -28
- package/config/settings.py +73 -7
- package/connectors/tencent_docs.py +87 -0
- package/package.json +3 -2
- package/services/daemon_service.py +12 -4
- package/services/gross_profit_service.py +23 -6
- package/services/refund_match_service.py +43 -10
- package/sync_service.spec +1 -0
- package/utils/sheet_selector.py +125 -0
- package//345/220/257/345/212/250.bat +25 -6
- package//345/277/253/351/200/237/345/274/200/345/247/213.txt +16 -9
package/cli/dashboard.py
CHANGED
|
@@ -14,10 +14,25 @@ from rich.panel import Panel
|
|
|
14
14
|
from rich.table import Table
|
|
15
15
|
from rich.text import Text
|
|
16
16
|
|
|
17
|
-
from config.settings import Settings, get_settings
|
|
17
|
+
from config.settings import APP_VERSION, Settings, get_settings
|
|
18
18
|
from services.daemon_service import DaemonService
|
|
19
19
|
from services.state_service import StateService
|
|
20
20
|
|
|
21
|
+
_LOGO_LINES = [
|
|
22
|
+
("████████╗██████╗ ██████╗ ██████╗ ██████╗ ███████╗██████╗", "bold #8ecae6"),
|
|
23
|
+
("╚══██╔══╝██╔══██╗ ██╔═══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗", "bold #6ccff6"),
|
|
24
|
+
(" ██║ ██████╔╝ ██║ ██║██████╔╝██║ ██║█████╗ ██████╔╝", "bold #38bdf8"),
|
|
25
|
+
(" ██║ ██╔══██╗ ██║ ██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗", "bold #22d3ee"),
|
|
26
|
+
(" ██║ ██████╔╝ ╚██████╔╝██║ ██║██████╔╝███████╗██║ ██║", "bold #2dd4bf"),
|
|
27
|
+
(" ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝", "bold #86efac"),
|
|
28
|
+
]
|
|
29
|
+
_MODAL_ICONS = {
|
|
30
|
+
"success": ("●", "#10b981"),
|
|
31
|
+
"warning": ("●", "#f59e0b"),
|
|
32
|
+
"error": ("●", "#ef4444"),
|
|
33
|
+
"info": ("●", "#38bdf8"),
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
|
|
22
37
|
class DashboardApp:
|
|
23
38
|
"""Interactive terminal UI for daily operations."""
|
|
@@ -57,38 +72,35 @@ class DashboardApp:
|
|
|
57
72
|
last_run = self._state_svc.load_last_run(quiet=True)
|
|
58
73
|
autostart_status = self._daemon.autostart_status()
|
|
59
74
|
|
|
60
|
-
|
|
61
|
-
subtitle = Text("Scheduler Console", style="bold #8ecae6")
|
|
62
|
-
header = Panel(
|
|
63
|
-
Align.center(Group(title, subtitle)),
|
|
64
|
-
border_style="#219ebc",
|
|
65
|
-
box=box.HEAVY,
|
|
66
|
-
padding=(1, 2),
|
|
67
|
-
)
|
|
75
|
+
header = self._build_header(daemon_status, last_run)
|
|
68
76
|
|
|
69
77
|
runtime_panel = Panel(
|
|
70
78
|
self._build_runtime_table(),
|
|
71
79
|
title="[bold #023047]运行配置[/bold #023047]",
|
|
72
80
|
border_style="#8ecae6",
|
|
73
81
|
box=box.ROUNDED,
|
|
82
|
+
padding=(1, 2),
|
|
74
83
|
)
|
|
75
84
|
daemon_panel = Panel(
|
|
76
85
|
self._build_daemon_table(daemon_status, autostart_status),
|
|
77
86
|
title="[bold #023047]守护进程[/bold #023047]",
|
|
78
87
|
border_style="#90be6d" if daemon_status.running else "#f4a261",
|
|
79
88
|
box=box.ROUNDED,
|
|
89
|
+
padding=(1, 2),
|
|
80
90
|
)
|
|
81
91
|
state_panel = Panel(
|
|
82
92
|
self._build_state_table(state, last_run),
|
|
83
93
|
title="[bold #023047]同步状态[/bold #023047]",
|
|
84
94
|
border_style="#ffb703",
|
|
85
95
|
box=box.ROUNDED,
|
|
96
|
+
padding=(1, 2),
|
|
86
97
|
)
|
|
87
98
|
config_panel = Panel(
|
|
88
99
|
self._build_config_table(),
|
|
89
100
|
title="[bold #023047]接入状态[/bold #023047]",
|
|
90
101
|
border_style="#fb8500" if self._is_config_ready() else "#d62828",
|
|
91
102
|
box=box.ROUNDED,
|
|
103
|
+
padding=(1, 2),
|
|
92
104
|
)
|
|
93
105
|
|
|
94
106
|
actions = Panel(
|
|
@@ -96,6 +108,7 @@ class DashboardApp:
|
|
|
96
108
|
title="[bold #023047]操作台[/bold #023047]",
|
|
97
109
|
border_style="#219ebc",
|
|
98
110
|
box=box.ROUNDED,
|
|
111
|
+
padding=(1, 2),
|
|
99
112
|
)
|
|
100
113
|
|
|
101
114
|
footer = Panel(
|
|
@@ -116,6 +129,63 @@ class DashboardApp:
|
|
|
116
129
|
footer,
|
|
117
130
|
)
|
|
118
131
|
|
|
132
|
+
def _build_header(self, daemon_status, last_run) -> Panel:
|
|
133
|
+
logo = Text(justify="center")
|
|
134
|
+
for line, style in _LOGO_LINES:
|
|
135
|
+
logo.append(line, style=style)
|
|
136
|
+
logo.append("\n")
|
|
137
|
+
logo.append("Tencent Docs Order Sync Console", style="bold #e0fbfc")
|
|
138
|
+
|
|
139
|
+
hero = Group(
|
|
140
|
+
Align.center(logo),
|
|
141
|
+
Align.right(self._build_version_badge()),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
badges = Table.grid(expand=True)
|
|
145
|
+
badges.add_column(justify="center")
|
|
146
|
+
badges.add_column(justify="center")
|
|
147
|
+
badges.add_column(justify="center")
|
|
148
|
+
badges.add_row(
|
|
149
|
+
self._build_badge("运行模式", f"{self._settings.gross_profit_mode.value} / {self._settings.refund_match_mode.value}", "#0ea5e9"),
|
|
150
|
+
self._build_badge("守护状态", "运行中" if daemon_status.running else "未运行", "#10b981" if daemon_status.running else "#f59e0b"),
|
|
151
|
+
self._build_badge("最近结果", self._last_run_label(last_run), "#22c55e" if last_run and last_run.success else "#ef4444" if last_run else "#64748b"),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
subtitle = Text("多表格同步与退款标记服务", style="bold white", justify="center")
|
|
155
|
+
hint = Text("输入编号执行任务,所有结果与失败原因会在控制台内直接返回", style="dim", justify="center")
|
|
156
|
+
|
|
157
|
+
body = Group(
|
|
158
|
+
hero,
|
|
159
|
+
Align.center(subtitle),
|
|
160
|
+
Align.center(hint),
|
|
161
|
+
badges,
|
|
162
|
+
)
|
|
163
|
+
return Panel(
|
|
164
|
+
body,
|
|
165
|
+
border_style="#219ebc",
|
|
166
|
+
box=box.HEAVY,
|
|
167
|
+
padding=(1, 2),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def _build_badge(label: str, value: str, color: str) -> Panel:
|
|
172
|
+
inner = Table.grid(padding=(0, 1))
|
|
173
|
+
inner.add_column(justify="center")
|
|
174
|
+
inner.add_row(Text(label, style="bold white"))
|
|
175
|
+
inner.add_row(Text(value, style="bold white"))
|
|
176
|
+
return Panel(inner, border_style=color, box=box.ROUNDED, padding=(0, 1))
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def _build_version_badge() -> Panel:
|
|
180
|
+
text = Text(f"v{APP_VERSION}", style="bold #0f172a", justify="center")
|
|
181
|
+
return Panel(text, border_style="#94d2bd", box=box.ROUNDED, padding=(0, 2), title="版本")
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def _last_run_label(last_run) -> str:
|
|
185
|
+
if last_run is None:
|
|
186
|
+
return "暂无记录"
|
|
187
|
+
return "成功" if last_run.success else "失败"
|
|
188
|
+
|
|
119
189
|
def _build_runtime_table(self) -> Table:
|
|
120
190
|
table = Table(box=None, show_header=False, pad_edge=False)
|
|
121
191
|
table.add_column(style="bold white")
|
|
@@ -168,9 +238,9 @@ class DashboardApp:
|
|
|
168
238
|
return table
|
|
169
239
|
|
|
170
240
|
def _build_action_table(self) -> Table:
|
|
171
|
-
table = Table(box=box.SIMPLE_HEAVY, expand=True)
|
|
172
|
-
table.add_column("编号", justify="center", style="bold #
|
|
173
|
-
table.add_column("动作", style="bold white")
|
|
241
|
+
table = Table(box=box.SIMPLE_HEAVY, expand=True, row_styles=["none", "dim"])
|
|
242
|
+
table.add_column("编号", justify="center", style="bold #38bdf8", width=6)
|
|
243
|
+
table.add_column("动作", style="bold white", width=18)
|
|
174
244
|
table.add_column("说明", style="#023047")
|
|
175
245
|
table.add_row("1", "执行全部任务", "毛利计算 + 退款匹配")
|
|
176
246
|
table.add_row("2", "模拟执行", "全部任务 dry-run,不写入表格")
|
|
@@ -228,7 +298,7 @@ class DashboardApp:
|
|
|
228
298
|
from cli.commands import execute_tasks
|
|
229
299
|
|
|
230
300
|
results = execute_tasks(self._settings, task, dry_run=dry_run)
|
|
231
|
-
table = Table(box=box.SIMPLE_HEAVY, expand=True)
|
|
301
|
+
table = Table(box=box.SIMPLE_HEAVY, expand=True, row_styles=["none", "dim"])
|
|
232
302
|
table.add_column("任务", style="bold cyan")
|
|
233
303
|
table.add_column("结果", justify="center")
|
|
234
304
|
table.add_column("读取", justify="right")
|
|
@@ -251,8 +321,19 @@ class DashboardApp:
|
|
|
251
321
|
failure_table.add_column("失败原因", style="white")
|
|
252
322
|
for item in failures:
|
|
253
323
|
failure_table.add_row(item.task_name.value, item.error_message or "")
|
|
254
|
-
body = Group(
|
|
255
|
-
|
|
324
|
+
body = Group(
|
|
325
|
+
self._build_modal_summary("执行完成,但存在失败项", style="error"),
|
|
326
|
+
table,
|
|
327
|
+
Panel(failure_table, title="失败详情", border_style="red", box=box.ROUNDED, padding=(1, 2)),
|
|
328
|
+
)
|
|
329
|
+
self._pause_with_panel(body, title="执行结果", border_style="#ef4444")
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
body = Group(
|
|
333
|
+
self._build_modal_summary("执行完成,结果已落地", style="success"),
|
|
334
|
+
table,
|
|
335
|
+
)
|
|
336
|
+
self._pause_with_panel(body, title="执行结果", border_style="#219ebc")
|
|
256
337
|
|
|
257
338
|
def _daemon_action(self, action: str) -> None:
|
|
258
339
|
if action in {"start", "autostart-enable"} and not self._ensure_config():
|
|
@@ -271,14 +352,47 @@ class DashboardApp:
|
|
|
271
352
|
else:
|
|
272
353
|
status = self._daemon.autostart_status()
|
|
273
354
|
|
|
274
|
-
|
|
355
|
+
style = "success"
|
|
356
|
+
border = "#90be6d"
|
|
357
|
+
if "失败" in status.message or "未找到" in status.message:
|
|
358
|
+
style = "error"
|
|
359
|
+
border = "#ef4444"
|
|
360
|
+
elif "未启用" in status.message or "未运行" in status.message:
|
|
361
|
+
style = "warning"
|
|
362
|
+
border = "#f59e0b"
|
|
363
|
+
|
|
364
|
+
body = Group(
|
|
365
|
+
self._build_modal_summary(status.message, style=style),
|
|
366
|
+
self._build_kv_table({
|
|
367
|
+
"动作": action,
|
|
368
|
+
"目标": getattr(status, "target", "") or "-",
|
|
369
|
+
}),
|
|
370
|
+
)
|
|
371
|
+
self._pause_with_panel(body, title="守护结果", border_style=border)
|
|
275
372
|
|
|
276
373
|
def _show_log_tail(self) -> None:
|
|
277
374
|
content = self._daemon.read_log_tail(lines=40)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
375
|
+
if content:
|
|
376
|
+
log_panel = Panel(
|
|
377
|
+
Text(content, style="#e5e7eb"),
|
|
378
|
+
title=f"后台日志 · {self._daemon.log_file.name}",
|
|
379
|
+
border_style="#ffb703",
|
|
380
|
+
box=box.ROUNDED,
|
|
381
|
+
padding=(1, 2),
|
|
382
|
+
)
|
|
383
|
+
body = Group(
|
|
384
|
+
self._build_modal_summary("以下为最近 40 行后台日志", style="info"),
|
|
385
|
+
log_panel,
|
|
386
|
+
)
|
|
387
|
+
else:
|
|
388
|
+
body = Group(
|
|
389
|
+
self._build_modal_summary("后台日志暂时为空", style="warning"),
|
|
390
|
+
self._build_kv_table({
|
|
391
|
+
"日志文件": self._daemon.log_file.name,
|
|
392
|
+
"日志目录": str(self._daemon.log_file.parent),
|
|
393
|
+
}),
|
|
394
|
+
)
|
|
395
|
+
self._pause_with_panel(body, title="后台日志", border_style="#ffb703")
|
|
282
396
|
|
|
283
397
|
def _run_setup(self, *, check: bool) -> None:
|
|
284
398
|
from cli.setup import cmd_setup
|
|
@@ -300,14 +414,14 @@ class DashboardApp:
|
|
|
300
414
|
def _ensure_config(self) -> bool:
|
|
301
415
|
if self._is_config_ready():
|
|
302
416
|
return True
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
)
|
|
417
|
+
body = Group(
|
|
418
|
+
self._build_modal_summary("腾讯文档必填配置尚未完成", style="error"),
|
|
419
|
+
self._build_kv_table({
|
|
420
|
+
"建议动作": "先运行 tb setup",
|
|
421
|
+
"后续检查": "配置完成后运行 tb check",
|
|
422
|
+
}),
|
|
310
423
|
)
|
|
424
|
+
self._pause_with_panel(body, title="配置未完成", border_style="#ef4444")
|
|
311
425
|
return False
|
|
312
426
|
|
|
313
427
|
def _is_config_ready(self) -> bool:
|
|
@@ -322,11 +436,35 @@ class DashboardApp:
|
|
|
322
436
|
]
|
|
323
437
|
return all(bool(value.strip()) for value in fields)
|
|
324
438
|
|
|
325
|
-
def _pause_with_panel(self,
|
|
439
|
+
def _pause_with_panel(self, body, *, title: str, border_style: str) -> None:
|
|
326
440
|
self.console.clear()
|
|
327
|
-
self.console.print(
|
|
441
|
+
self.console.print(
|
|
442
|
+
Panel(
|
|
443
|
+
body,
|
|
444
|
+
title=title,
|
|
445
|
+
border_style=border_style,
|
|
446
|
+
box=box.ROUNDED,
|
|
447
|
+
padding=(1, 2),
|
|
448
|
+
)
|
|
449
|
+
)
|
|
328
450
|
self._wait()
|
|
329
451
|
|
|
452
|
+
def _build_modal_summary(self, message: str, *, style: str) -> Panel:
|
|
453
|
+
icon, color = _MODAL_ICONS[style]
|
|
454
|
+
text = Text(justify="center")
|
|
455
|
+
text.append(f"{icon} ", style=f"bold {color}")
|
|
456
|
+
text.append(message, style="bold white")
|
|
457
|
+
return Panel(text, border_style=color, box=box.ROUNDED, padding=(0, 1))
|
|
458
|
+
|
|
459
|
+
@staticmethod
|
|
460
|
+
def _build_kv_table(rows: dict[str, str]) -> Table:
|
|
461
|
+
table = Table(box=box.SIMPLE_HEAVY, expand=True, row_styles=["none", "dim"])
|
|
462
|
+
table.add_column("项目", style="bold cyan", width=18)
|
|
463
|
+
table.add_column("内容", style="white")
|
|
464
|
+
for key, value in rows.items():
|
|
465
|
+
table.add_row(key, value)
|
|
466
|
+
return table
|
|
467
|
+
|
|
330
468
|
def _wait(self) -> None:
|
|
331
469
|
self.console.input("[dim]按回车返回控制台[/dim]")
|
|
332
470
|
|