Update WEHAGO comparison data and tools

This commit is contained in:
b17301
2026-05-09 02:49:34 +09:00
parent b0b4647bb4
commit 7ceee2f897
44 changed files with 6296 additions and 135 deletions
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\click_left_code_9225.py" %*
+69
View File
@@ -0,0 +1,69 @@
import json
import os
import sys
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
target = sys.argv[1] if len(sys.argv) > 1 else "110"
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
info = driver.execute_script(
r"""
const code = arguments[0];
const out = [];
for (const cell of document.querySelectorAll('.rg-data-cell, td[class*=rg-data-cell], [class*=rg-data-cell], td')) {
const r = cell.getBoundingClientRect();
const text = (cell.textContent || '').trim();
if (text === code && r.left < 220 && r.top > 145 && r.width > 0 && r.height > 0) {
out.push({left:r.left, top:r.top, width:r.width, height:r.height, text});
}
}
return out.sort((a,b)=>a.top-b.top)[0] || null;
""",
target,
)
print("target", target, "info", json.dumps(info, ensure_ascii=False))
if info:
x = info["left"] + info["width"] / 2
y = info["top"] + info["height"] / 2
for typ in ["mouseMoved", "mousePressed", "mouseReleased"]:
payload = {"type": typ, "x": x, "y": y, "button": "left", "clickCount": 1}
if typ == "mouseMoved":
payload["button"] = "none"
driver.execute_cdp_cmd("Input.dispatchMouseEvent", payload)
time.sleep(0.5)
visible = driver.execute_script(
r"""
const rows = [];
const colorScore = (color) => {
const m = String(color || '').match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (!m) return 0;
const r = Number(m[1]), g = Number(m[2]), b = Number(m[3]);
return Math.max(0, b - r) + Math.max(0, g - r) + (b > 120 ? 20 : 0);
};
for (const cell of document.querySelectorAll('.rg-data-cell, td[class*=rg-data-cell], [class*=rg-data-cell], td')) {
const r = cell.getBoundingClientRect();
const code = (cell.textContent || '').trim();
if (!/^\d{3,6}$/.test(code) || r.left > 220 || r.top < 145 || r.width <= 0 || r.height <= 0) continue;
let node = cell, score = 0, cls = '';
while (node && node !== document.body) {
const style = window.getComputedStyle(node);
score = Math.max(score, colorScore(style.backgroundColor));
cls += ' ' + String(node.className || '');
node = node.parentElement;
}
if (/selected|select|focus|current|active|checked/i.test(cls)) score += 80;
rows.push({code, top:r.top, score, cls: cls.slice(0,120)});
}
rows.sort((a,b)=>(b.score-a.score)||(a.top-b.top));
return rows.slice(0,8);
"""
)
print(json.dumps(visible, ensure_ascii=False, indent=2))
driver.quit()
@@ -0,0 +1,5 @@
@echo off
set "PYTHONIOENCODING=utf-8"
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\click_left_search_button_9225.py"
+27
View File
@@ -0,0 +1,27 @@
import json
import os
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
for typ in ["mouseMoved", "mousePressed", "mouseReleased"]:
driver.execute_cdp_cmd(
"Input.dispatchMouseEvent",
{"type": typ, "x": 130, "y": 188, "button": "left" if typ != "mouseMoved" else "none", "clickCount": 1},
)
time.sleep(0.8)
items = driver.execute_script(
r"""
return Array.from(document.querySelectorAll('input,button,[role=button],a,div')).map(el=>{
const r=el.getBoundingClientRect(); const st=getComputedStyle(el);
return {tag:el.tagName,id:el.id||'',cls:String(el.className||'').slice(0,80),text:(el.textContent||'').trim().slice(0,80),value:el.value||'',placeholder:el.getAttribute('placeholder')||'',title:el.getAttribute('title')||'',left:Math.round(r.left),top:Math.round(r.top),width:Math.round(r.width),height:Math.round(r.height),display:st.display,vis:st.visibility};
}).filter(x=>x.width>0&&x.height>0&&x.display!=='none'&&x.vis!=='hidden'&&x.top<400&&x.left<700).sort((a,b)=>(a.top-b.top)||(a.left-b.left)).slice(0,200);
"""
)
print(json.dumps(items, ensure_ascii=False, indent=2))
driver.quit()
+437
View File
@@ -0,0 +1,437 @@
from __future__ import annotations
import argparse
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Iterable
from openpyxl import Workbook
from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.utils import get_column_letter
BASE_DIR = Path(__file__).resolve().parent.parent
if str(BASE_DIR) not in sys.path:
sys.path.insert(0, str(BASE_DIR))
from main import engine
from wehago_compare import (
_get_cached_status_rows_by_range,
_get_cached_voucher_sections_by_range,
discover_available_years,
)
EXPORT_DIR = BASE_DIR / "static" / "exports"
HEADER_FILL = PatternFill(fill_type="solid", fgColor="1F2937")
HEADER_FONT = Font(color="FFFFFF", bold=True)
SUMMARY_FILL = PatternFill(fill_type="solid", fgColor="E5E7EB")
RECHECK_FILL = PatternFill(fill_type="solid", fgColor="FEF3C7")
UNMATCHED_FILL = PatternFill(fill_type="solid", fgColor="FDE68A")
ERP_UNMATCHED_FILL = PatternFill(fill_type="solid", fgColor="DBEAFE")
def clean(value: Any) -> str:
return "" if value is None else str(value).strip()
def amount(value: Any) -> float:
text = clean(value).replace(",", "")
if not text:
return 0.0
try:
return float(text)
except ValueError:
return 0.0
def autosize(ws) -> None:
widths: dict[int, int] = {}
for row in ws.iter_rows():
for cell in row:
value = "" if cell.value is None else str(cell.value)
widths[cell.column] = min(max(widths.get(cell.column, 0), len(value) + 2), 42)
for col_idx, width in widths.items():
ws.column_dimensions[get_column_letter(col_idx)].width = width
def style_sheet(ws) -> None:
ws.freeze_panes = "A2"
ws.auto_filter.ref = ws.dimensions
for cell in ws[1]:
cell.fill = HEADER_FILL
cell.font = HEADER_FONT
cell.alignment = Alignment(horizontal="center", vertical="center")
for row in ws.iter_rows(min_row=2):
status_value = clean(row[0].value)
if status_value == "Recheck":
fill = RECHECK_FILL
elif status_value == "Unmatched":
fill = UNMATCHED_FILL
elif status_value == "ERP Unmatched":
fill = ERP_UNMATCHED_FILL
else:
fill = None
if fill:
for cell in row:
cell.fill = fill
for cell in row:
cell.alignment = Alignment(vertical="top", wrap_text=True)
autosize(ws)
def append_rows(ws, headers: list[str], rows: Iterable[list[Any]]) -> None:
ws.append(headers)
for row in rows:
ws.append(row)
style_sheet(ws)
def append_rows_write_only(workbook: Workbook, title: str, headers: list[str], rows: Iterable[list[Any]]) -> None:
ws = workbook.create_sheet(title)
header_row: list[WriteOnlyCell] = []
for value in headers:
cell = WriteOnlyCell(ws, value=value)
cell.fill = HEADER_FILL
cell.font = HEADER_FONT
cell.alignment = Alignment(horizontal="center", vertical="center")
header_row.append(cell)
ws.append(header_row)
for row in rows:
ws.append(list(row))
def flatten_groups(
groups: list[dict[str, Any]],
card_label: str,
only_statuses: set[str] | None = None,
) -> list[list[Any]]:
rows: list[list[Any]] = []
for group_index, group in enumerate(groups, start=1):
summary = group.get("summary", {})
link_count = len(summary.get("erp_group_keys") or summary.get("wehago_group_keys") or [])
group_rows = group.get("rows", [])
for row_index, row in enumerate(group_rows, start=1):
status_label = clean(row.get("status_label"))
if only_statuses and status_label not in only_statuses:
continue
rows.append(
[
card_label,
group_index,
row_index,
status_label,
summary.get("fiscal_year"),
clean(summary.get("ledger_date")),
clean(summary.get("proof_date")),
clean(summary.get("voucher_no")),
clean(summary.get("draft_no")),
clean(summary.get("ledger_accounts")),
clean(summary.get("voucher_accounts")),
clean(summary.get("ledger_vendors")),
clean(summary.get("voucher_vendors")),
clean(summary.get("review_reason")),
len(group_rows),
link_count,
clean(row.get("ledger_date")),
clean(row.get("proof_date")),
clean(row.get("voucher_no")),
clean(row.get("draft_no")),
clean(row.get("ledger_account_name")),
clean(row.get("voucher_account_name")),
clean(row.get("ledger_vendor")),
clean(row.get("voucher_vendor")),
amount(row.get("ledger_debit")),
amount(row.get("ledger_credit")),
amount(row.get("voucher_debit")),
amount(row.get("voucher_credit")),
clean(row.get("ledger_desc")),
clean(row.get("voucher_desc")),
]
)
return rows
def iter_card_issue_rows(groups: list[dict[str, Any]], card_kind: str) -> list[list[Any]]:
issues: list[list[Any]] = []
for group in groups:
summary = group.get("summary", {})
parent_year = summary.get("fiscal_year")
parent_date = clean(summary.get("ledger_date") or summary.get("proof_date"))
parent_voucher_no = clean(summary.get("voucher_no"))
parent_draft_no = clean(summary.get("draft_no"))
for row in group.get("rows", []):
status_label = clean(row.get("status_label"))
if card_kind == "wehago" and status_label not in {"ERP Unmatched", "Recheck"}:
continue
if card_kind == "voucher" and status_label not in {"Unmatched", "Recheck"}:
continue
issues.append(
[
status_label,
parent_year,
parent_date,
parent_voucher_no,
parent_draft_no,
clean(summary.get("ledger_accounts")),
clean(summary.get("voucher_accounts")),
clean(summary.get("ledger_vendors")),
clean(summary.get("voucher_vendors")),
clean(summary.get("review_reason")),
clean(row.get("ledger_date")),
clean(row.get("proof_date")),
clean(row.get("voucher_no")),
clean(row.get("draft_no")),
clean(row.get("ledger_account_name")),
clean(row.get("voucher_account_name")),
clean(row.get("ledger_vendor")),
clean(row.get("voucher_vendor")),
amount(row.get("ledger_debit")),
amount(row.get("ledger_credit")),
amount(row.get("voucher_debit")),
amount(row.get("voucher_credit")),
clean(row.get("ledger_desc")),
clean(row.get("voucher_desc")),
]
)
return issues
def build_relation_rows(
wehago_groups: list[dict[str, Any]],
voucher_groups: list[dict[str, Any]],
) -> list[list[Any]]:
rows: list[list[Any]] = []
for group in wehago_groups:
summary = group.get("summary", {})
erp_keys = summary.get("erp_group_keys") or set()
if len(erp_keys) <= 1:
continue
rows.append(
[
"WEHAGO 1 : ERP N",
summary.get("fiscal_year"),
clean(summary.get("ledger_date")),
clean(summary.get("proof_date")),
clean(summary.get("voucher_no")),
clean(summary.get("draft_no")),
clean(summary.get("ledger_accounts")),
clean(summary.get("voucher_accounts")),
len(erp_keys),
len(group.get("rows", [])),
clean(summary.get("review_reason")),
" | ".join(sorted(clean(key) for key in erp_keys if clean(key))),
]
)
for group in voucher_groups:
summary = group.get("summary", {})
wehago_keys = summary.get("wehago_group_keys") or set()
if len(wehago_keys) <= 1:
continue
rows.append(
[
"ERP 1 : WEHAGO N",
summary.get("fiscal_year"),
clean(summary.get("ledger_date")),
clean(summary.get("proof_date")),
clean(summary.get("voucher_no")),
clean(summary.get("draft_no")),
clean(summary.get("ledger_accounts")),
clean(summary.get("voucher_accounts")),
len(wehago_keys),
len(group.get("rows", [])),
clean(summary.get("review_reason")),
" | ".join(sorted(clean(key) for key in wehago_keys if clean(key))),
]
)
return rows
def export_voucher_card_diff(start_year: int, end_year: int) -> Path:
EXPORT_DIR.mkdir(parents=True, exist_ok=True)
workbook = Workbook(write_only=True)
detail_headers = [
"카드",
"그룹순번",
"행순번",
"행상태",
"연도",
"부모 WEHAGO 일자",
"부모 ERP 일자",
"부모 전표번호",
"부모 가전표번호",
"부모 WEHAGO 계정",
"부모 ERP 계정",
"부모 WEHAGO 거래처",
"부모 ERP 거래처",
"부모 검증근거",
"부모 그룹 행수",
"연결된 반대 그룹 수",
"행 WEHAGO 일자",
"행 ERP 일자",
"행 전표번호",
"행 가전표번호",
"행 WEHAGO 계정",
"행 ERP 계정",
"행 WEHAGO 거래처",
"행 ERP 거래처",
"행 WEHAGO 차변",
"행 WEHAGO 대변",
"행 ERP 차변",
"행 ERP 대변",
"행 WEHAGO 적요",
"행 ERP 적요",
]
all_wehago_match_rows: list[list[Any]] = []
all_wehago_unmatched_rows: list[list[Any]] = []
all_voucher_match_rows: list[list[Any]] = []
all_voucher_unmatched_rows: list[list[Any]] = []
diff_summary_rows: list[list[Any]] = []
relation_rows_all: list[list[Any]] = []
for year in range(start_year, end_year + 1):
rows_by_status = _get_cached_status_rows_by_range(engine, year, year)
sections = _get_cached_voucher_sections_by_range(engine, year, year, rows_by_status)
wehago_groups = sections.get("voucher_matched", [])
voucher_groups = sections.get("erp_voucher_matched", [])
wehago_unmatched_groups = sections.get("voucher_unmatched", [])
wehago_issue_rows = iter_card_issue_rows(wehago_groups, "wehago")
voucher_issue_rows = iter_card_issue_rows(voucher_groups, "voucher")
relation_rows = build_relation_rows(wehago_groups, voucher_groups)
wehago_multi = sum(1 for row in relation_rows if row[0] == "WEHAGO 1 : ERP N")
voucher_multi = sum(1 for row in relation_rows if row[0] == "ERP 1 : WEHAGO N")
all_wehago_match_rows.extend(flatten_groups(wehago_groups, "WEHAGO"))
all_wehago_unmatched_rows.extend(flatten_groups(wehago_unmatched_groups, "WEHAGO Unmatched"))
all_voucher_match_rows.extend(flatten_groups(voucher_groups, "Voucher"))
all_voucher_unmatched_rows.extend(flatten_groups(voucher_groups, "Voucher", {"Unmatched", "Recheck"}))
relation_rows_all.extend(relation_rows)
diff_summary_rows.append(
[
"요약",
year,
len(wehago_groups),
len(wehago_unmatched_groups),
len(voucher_groups),
len(voucher_issue_rows),
len(wehago_groups) - len(voucher_groups),
wehago_multi,
voucher_multi,
len(wehago_issue_rows),
len(voucher_issue_rows),
"WEHAGO 카드는 WEHAGO 전표 기준 그룹 수, Voucher 카드는 ERP 전표 기준 그룹 수입니다.",
]
)
append_rows_write_only(
workbook,
"1_WEHAGO_Match",
detail_headers,
all_wehago_match_rows or [["WEHAGO", "", "", "", "", "", "", "", "", "", "", "", "", "데이터가 없습니다.", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]],
)
append_rows_write_only(
workbook,
"2_WEHAGO_Unmatched",
detail_headers,
all_wehago_unmatched_rows or [["WEHAGO Unmatched", "", "", "", "", "", "", "", "", "", "", "", "", "데이터가 없습니다.", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]],
)
append_rows_write_only(
workbook,
"3_Voucher_Match",
detail_headers,
all_voucher_match_rows or [["Voucher", "", "", "", "", "", "", "", "", "", "", "", "", "데이터가 없습니다.", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]],
)
append_rows_write_only(
workbook,
"4_Voucher_Unmatched",
detail_headers,
all_voucher_unmatched_rows or [["Voucher", "", "", "", "", "", "", "", "", "", "", "", "", "Voucher 카드 안에서 Unmatched/Recheck로 남은 행이 없습니다.", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""]],
)
diff_headers = [
"구분",
"연도",
"WEHAGO 매치 그룹 수",
"WEHAGO Unmatched 그룹 수",
"Voucher 매치 그룹 수",
"Voucher unmatched 행 수",
"WEHAGO-Voucher 그룹 수 차이",
"WEHAGO 1:ERP N 건수",
"ERP 1:WEHAGO N 건수",
"WEHAGO 카드 내 ERP Unmatched/Recheck 행 수",
"Voucher 카드 내 Unmatched/Recheck 행 수",
"설명",
]
diff_rows: list[list[Any]] = []
diff_rows.append(diff_headers)
diff_rows.extend(diff_summary_rows)
if diff_summary_rows:
total_wehago = sum(int(row[2]) for row in diff_summary_rows)
total_wehago_unmatched = sum(int(row[3]) for row in diff_summary_rows)
total_voucher = sum(int(row[4]) for row in diff_summary_rows)
total_voucher_unmatched = sum(int(row[5]) for row in diff_summary_rows)
total_diff = sum(int(row[6]) for row in diff_summary_rows)
total_wehago_multi = sum(int(row[7]) for row in diff_summary_rows)
total_voucher_multi = sum(int(row[8]) for row in diff_summary_rows)
total_wehago_issues = sum(int(row[9]) for row in diff_summary_rows)
total_voucher_issues = sum(int(row[10]) for row in diff_summary_rows)
diff_rows.append(
[
"전체",
f"{start_year}~{end_year}",
total_wehago,
total_wehago_unmatched,
total_voucher,
total_voucher_unmatched,
total_diff,
total_wehago_multi,
total_voucher_multi,
total_wehago_issues,
total_voucher_issues,
"그룹 수 차이는 한쪽 전표 1건이 반대쪽 여러 전표와 연결되는 경우에서 주로 발생합니다.",
]
)
diff_rows.append([])
diff_rows.append(["관계 유형", "연도", "WEHAGO 일자", "ERP 일자", "전표번호", "가전표번호", "WEHAGO 계정", "ERP 계정", "연결된 반대 그룹 수", "그룹 행 수", "검증 근거", "연결된 그룹 키"])
diff_rows.extend(relation_rows_all or [["", "", "", "", "", "", "", "", "", "", "수 차이를 만든 다대일/일대다 사례가 없습니다.", ""]])
diff_ws = workbook.create_sheet("5_Count_Diff")
for index, row in enumerate(diff_rows, start=1):
if index in {1, len(diff_summary_rows) + 3}:
cells: list[WriteOnlyCell] = []
for value in row:
cell = WriteOnlyCell(diff_ws, value=value)
cell.fill = HEADER_FILL
cell.font = HEADER_FONT
cell.alignment = Alignment(horizontal="center", vertical="center")
cells.append(cell)
diff_ws.append(cells)
else:
diff_ws.append(row)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = EXPORT_DIR / f"voucher_card_diff_{start_year}_{end_year}_{timestamp}.xlsx"
workbook.save(output_path)
return output_path
def parse_args() -> argparse.Namespace:
years = discover_available_years()
default_start = min(years) if years else datetime.now().year
default_end = max(years) if years else default_start
parser = argparse.ArgumentParser(description="WEHAGO/Voucher 카드 차이 분석 엑셀 생성")
parser.add_argument("--start-year", type=int, default=default_start)
parser.add_argument("--end-year", type=int, default=default_end)
return parser.parse_args()
def main() -> None:
args = parse_args()
start_year = min(args.start_year, args.end_year)
end_year = max(args.start_year, args.end_year)
output_path = export_voucher_card_diff(start_year, end_year)
print(output_path)
if __name__ == "__main__":
main()
+316
View File
@@ -0,0 +1,316 @@
from __future__ import annotations
import argparse
import sys
from collections import Counter, defaultdict, deque
from datetime import datetime
from pathlib import Path
from typing import Any
from openpyxl import Workbook
from openpyxl.cell import WriteOnlyCell
from openpyxl.styles import Alignment, Font, PatternFill
BASE_DIR = Path(__file__).resolve().parent.parent
if str(BASE_DIR) not in sys.path:
sys.path.insert(0, str(BASE_DIR))
from main import engine
from wehago_compare import _get_cached_status_rows_by_range, _get_cached_voucher_sections_by_range
EXPORT_DIR = BASE_DIR / "static" / "exports"
HEADER_FILL = PatternFill(fill_type="solid", fgColor="111827")
HEADER_FONT = Font(color="FFFFFF", bold=True)
POS_FILL = PatternFill(fill_type="solid", fgColor="DCFCE7")
NEG_FILL = PatternFill(fill_type="solid", fgColor="DBEAFE")
def clean(value: Any) -> str:
return "" if value is None else str(value).strip()
def amount(value: Any) -> float:
text = clean(value).replace(",", "")
if not text:
return 0.0
try:
return float(text)
except ValueError:
return 0.0
def write_row(ws, values: list[Any], fill: PatternFill | None = None, header: bool = False) -> None:
row = []
for value in values:
cell = WriteOnlyCell(ws, value=value)
cell.alignment = Alignment(vertical="top", wrap_text=True)
if header:
cell.fill = HEADER_FILL
cell.font = HEADER_FONT
cell.alignment = Alignment(horizontal="center", vertical="center")
elif fill:
cell.fill = fill
row.append(cell)
ws.append(row)
def summarize_group(summary: dict[str, Any], side: str) -> dict[str, Any]:
if side == "w":
return {
"date": clean(summary.get("ledger_date")),
"voucher_no": clean(summary.get("voucher_no")),
"draft_no": clean(summary.get("draft_no")),
"accounts": clean(summary.get("ledger_accounts")),
"vendors": clean(summary.get("ledger_vendors")),
"debit": amount(summary.get("ledger_debit")),
"credit": amount(summary.get("ledger_credit")),
"links": len(summary.get("erp_group_keys") or []),
"reason": clean(summary.get("review_reason")),
}
return {
"date": clean(summary.get("proof_date")) or clean(summary.get("ledger_date")),
"voucher_no": clean(summary.get("voucher_no")),
"draft_no": clean(summary.get("draft_no")),
"accounts": clean(summary.get("voucher_accounts")),
"vendors": clean(summary.get("voucher_vendors")),
"debit": amount(summary.get("voucher_debit")),
"credit": amount(summary.get("voucher_credit")),
"links": len(summary.get("wehago_group_keys") or []),
"reason": clean(summary.get("review_reason")),
}
def build_gap_report(year: int) -> Path:
rows = _get_cached_status_rows_by_range(engine, year, year)
sections = _get_cached_voucher_sections_by_range(engine, year, year, rows)
wehago_groups = sections["voucher_matched"]
voucher_groups = sections["erp_voucher_matched"]
wehago_by_key: dict[Any, dict[str, Any]] = {}
voucher_by_key: dict[Any, dict[str, Any]] = {}
adj: dict[tuple[str, Any], set[tuple[str, Any]]] = defaultdict(set)
for group in wehago_groups:
summary = group["summary"]
w_key = summary.get("wehago_group_key")
wehago_by_key[w_key] = group
for e_key in summary.get("erp_group_keys") or set():
adj[("w", w_key)].add(("e", e_key))
adj[("e", e_key)].add(("w", w_key))
for group in voucher_groups:
summary = group["summary"]
e_key = summary.get("erp_group_key")
voucher_by_key[e_key] = group
for w_key in summary.get("wehago_group_keys") or set():
adj[("e", e_key)].add(("w", w_key))
adj[("w", w_key)].add(("e", e_key))
components: list[dict[str, Any]] = []
visited: set[tuple[str, Any]] = set()
for node in list(adj):
if node in visited:
continue
q = deque([node])
visited.add(node)
w_keys: list[Any] = []
e_keys: list[Any] = []
while q:
side, key = q.popleft()
if side == "w":
w_keys.append(key)
else:
e_keys.append(key)
for nxt in adj[node if False else (side, key)]:
if nxt not in visited:
visited.add(nxt)
q.append(nxt)
components.append({"w_keys": w_keys, "e_keys": e_keys})
shape_counter = Counter((len(item["w_keys"]), len(item["e_keys"])) for item in components)
gap_components = [item for item in components if len(item["w_keys"]) != len(item["e_keys"])]
gap_components.sort(key=lambda item: (abs(len(item["w_keys"]) - len(item["e_keys"])) * -1, len(item["w_keys"]) * -1, len(item["e_keys"]) * -1))
EXPORT_DIR.mkdir(parents=True, exist_ok=True)
wb = Workbook(write_only=True)
ws_summary = wb.create_sheet("summary")
write_row(ws_summary, ["항목", "", "설명"], header=True)
write_row(ws_summary, ["연도", year, "분석 기준 연도"])
write_row(ws_summary, ["WEHAGO 매치 그룹 수", len(wehago_groups), "WEHAGO 카드에 표시되는 매치 전표 그룹 수"])
write_row(ws_summary, ["Voucher 매치 그룹 수", len(voucher_groups), "Voucher 카드에 표시되는 매치 전표 그룹 수"])
write_row(ws_summary, ["그룹 수 차이", len(wehago_groups) - len(voucher_groups), "WEHAGO 그룹 수 - Voucher 그룹 수"])
write_row(ws_summary, ["전체 연결 성분 수", len(components), "WEHAGO/ERP 매치 관계를 그래프로 묶은 연결 단위 수"])
write_row(ws_summary, ["1:1 성분 수", shape_counter.get((1, 1), 0), "양쪽 그룹 수가 같은 일반적인 경우"])
write_row(ws_summary, ["차이 발생 성분 수", len(gap_components), "WEHAGO 그룹 수와 Voucher 그룹 수가 다른 연결 성분 수"])
write_row(ws_summary, ["WEHAGO 쪽 초과 합계", sum(max(len(item['w_keys']) - len(item['e_keys']), 0) for item in gap_components), "WEHAGO 그룹 수가 더 많은 만큼의 합계"])
write_row(ws_summary, ["Voucher 쪽 초과 합계", sum(max(len(item['e_keys']) - len(item['w_keys']), 0) for item in gap_components), "Voucher 그룹 수가 더 많은 만큼의 합계"])
write_row(ws_summary, [])
write_row(ws_summary, ["형태", "건수", "설명"], header=True)
for (w_count, e_count), count in shape_counter.most_common(20):
desc = "정상 1:1" if (w_count, e_count) == (1, 1) else ("WEHAGO가 더 많이 쪼개짐" if w_count > e_count else "Voucher가 더 많이 쪼개짐")
write_row(ws_summary, [f"{w_count}:{e_count}", count, desc])
ws_gap = wb.create_sheet("gap_components")
write_row(
ws_gap,
[
"component_id",
"형태",
"WEHAGO 그룹 수",
"Voucher 그룹 수",
"차이(WEHAGO-Voucher)",
"WEHAGO 대표 전표",
"Voucher 대표 전표",
"WEHAGO 전표 목록",
"Voucher 전표 목록",
"WEHAGO 계정 요약",
"Voucher 계정 요약",
],
header=True,
)
for idx, item in enumerate(gap_components, start=1):
w_summaries = [summarize_group(wehago_by_key[key]["summary"], "w") for key in item["w_keys"] if key in wehago_by_key]
e_summaries = [summarize_group(voucher_by_key[key]["summary"], "e") for key in item["e_keys"] if key in voucher_by_key]
fill = POS_FILL if len(item["w_keys"]) > len(item["e_keys"]) else NEG_FILL
write_row(
ws_gap,
[
idx,
f"{len(item['w_keys'])}:{len(item['e_keys'])}",
len(item["w_keys"]),
len(item["e_keys"]),
len(item["w_keys"]) - len(item["e_keys"]),
" | ".join(f"{row['date']} {row['voucher_no']}" for row in w_summaries[:3]),
" | ".join(f"{row['date']} {row['voucher_no']}/{row['draft_no']}" for row in e_summaries[:3]),
"\n".join(f"{row['date']} / {row['voucher_no']} / {row['accounts']}" for row in w_summaries),
"\n".join(f"{row['date']} / {row['voucher_no']} / {row['draft_no']} / {row['accounts']}" for row in e_summaries),
"\n".join(row["accounts"] for row in w_summaries),
"\n".join(row["accounts"] for row in e_summaries),
],
fill=fill,
)
ws_wehago = wb.create_sheet("wehago_extra_groups")
write_row(
ws_wehago,
[
"component_id",
"형태",
"WEHAGO 일자",
"WEHAGO 전표번호",
"WEHAGO 계정",
"WEHAGO 거래처",
"WEHAGO 차변",
"WEHAGO 대변",
"연결 ERP 그룹 수",
"연결 ERP 전표 목록",
"검증 근거",
],
header=True,
)
for idx, item in enumerate(gap_components, start=1):
if len(item["w_keys"]) <= len(item["e_keys"]):
continue
linked_erp = [summarize_group(voucher_by_key[key]["summary"], "e") for key in item["e_keys"] if key in voucher_by_key]
linked_erp_text = "\n".join(f"{row['date']} / {row['voucher_no']} / {row['draft_no']}" for row in linked_erp)
for w_key in item["w_keys"]:
group = wehago_by_key.get(w_key)
if not group:
continue
s = summarize_group(group["summary"], "w")
write_row(
ws_wehago,
[
idx,
f"{len(item['w_keys'])}:{len(item['e_keys'])}",
s["date"],
s["voucher_no"],
s["accounts"],
s["vendors"],
s["debit"],
s["credit"],
s["links"],
linked_erp_text,
s["reason"],
],
fill=POS_FILL,
)
ws_voucher = wb.create_sheet("voucher_extra_groups")
write_row(
ws_voucher,
[
"component_id",
"형태",
"ERP 일자",
"ERP 전표번호",
"ERP 가전표번호",
"ERP 계정",
"ERP 거래처",
"ERP 차변",
"ERP 대변",
"연결 WEHAGO 그룹 수",
"연결 WEHAGO 전표 목록",
"검증 근거",
],
header=True,
)
for idx, item in enumerate(gap_components, start=1):
if len(item["e_keys"]) <= len(item["w_keys"]):
continue
linked_wehago = [summarize_group(wehago_by_key[key]["summary"], "w") for key in item["w_keys"] if key in wehago_by_key]
linked_wehago_text = "\n".join(f"{row['date']} / {row['voucher_no']}" for row in linked_wehago)
for e_key in item["e_keys"]:
group = voucher_by_key.get(e_key)
if not group:
continue
s = summarize_group(group["summary"], "e")
write_row(
ws_voucher,
[
idx,
f"{len(item['w_keys'])}:{len(item['e_keys'])}",
s["date"],
s["voucher_no"],
s["draft_no"],
s["accounts"],
s["vendors"],
s["debit"],
s["credit"],
s["links"],
linked_wehago_text,
s["reason"],
],
fill=NEG_FILL,
)
ws_examples = wb.create_sheet("top_patterns")
write_row(ws_examples, ["형태", "건수", "설명"], header=True)
for (w_count, e_count), count in shape_counter.most_common(50):
if (w_count, e_count) == (1, 1):
continue
write_row(
ws_examples,
[
f"{w_count}:{e_count}",
count,
"WEHAGO 전표 그룹 수가 더 많음" if w_count > e_count else "Voucher 전표 그룹 수가 더 많음",
],
fill=POS_FILL if w_count > e_count else NEG_FILL,
)
output = EXPORT_DIR / f"voucher_count_gap_{year}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
wb.save(output)
return output
def main() -> None:
parser = argparse.ArgumentParser(description="WEHAGO/Voucher 매치 그룹 수 차이 분석 엑셀 생성")
parser.add_argument("--year", type=int, default=2025)
args = parser.parse_args()
output = build_gap_report(args.year)
print(output)
if __name__ == "__main__":
main()
+281
View File
@@ -0,0 +1,281 @@
from __future__ import annotations
import json
import sys
from pathlib import Path
from typing import Any
from openpyxl import load_workbook
from sqlalchemy import bindparam, create_engine, text
BASE_DIR = Path(__file__).resolve().parent.parent
if str(BASE_DIR) not in sys.path:
sys.path.insert(0, str(BASE_DIR))
from wehago_compare import (
WEHAGO_SOURCE_ROOT,
_fallback_metric_counts_from_db,
compute_file_hash,
detect_file_kind,
import_ledger_rows,
import_voucher_rows,
infer_year_hint,
init_wehago_compare_db,
rebuild_comparison_results,
upsert_source_file,
)
DB_PATH = BASE_DIR / "data.db"
ENGINE = create_engine(f"sqlite:///{DB_PATH}", connect_args={"check_same_thread": False})
LEDGER_FILES = {
2022: WEHAGO_SOURCE_ROOT / "data_download/2022/ledger_2022_20220101_20221231_260507.xlsx",
2023: WEHAGO_SOURCE_ROOT / "data_download/wehago_account_ledger_20260506_2023_compare/ledger_2023_20230101_20231231_260506.xlsx",
2024: WEHAGO_SOURCE_ROOT / "data_download/wehago_account_ledger_20260506_2024_compare/ledger_2024_20240101_20241231_260504.xlsx",
2025: WEHAGO_SOURCE_ROOT / "data_download/wehago_account_ledger_20260423/ledger_2025_20250101_20251231_260423.xlsx",
}
VOUCHER_FILES = [
{
"path": WEHAGO_SOURCE_ROOT / "voucher_sort_20220101_20221231_260507.xlsx",
"expected_year": 2022,
"year_hint": 2022,
"allowed_years": {2022},
},
{
"path": WEHAGO_SOURCE_ROOT / "voucher_sort_20230101_20251231_260424.xlsx",
"expected_year": 2023,
"year_hint": None,
"allowed_years": {2023, 2024},
},
{
"path": WEHAGO_SOURCE_ROOT / "data_download/wehago_account_ledger_20260423/voucher_2025_20250101_20251231_260422.xlsx",
"expected_year": 2025,
"year_hint": 2025,
"allowed_years": {2025},
},
]
_DEFAULT_YEAR_HINT = object()
def _load_sheet(path: Path):
workbook = load_workbook(path, read_only=True, data_only=True)
return workbook, workbook.worksheets[0]
def _delete_year_data(conn, *, ledger_years: set[int], voucher_years: set[int]) -> dict[str, int]:
ledger_deleted = conn.execute(
text("DELETE FROM wehago_ledger_rows WHERE fiscal_year IN :years").bindparams(bindparam("years", expanding=True)),
{"years": sorted(ledger_years)},
).rowcount
voucher_deleted = conn.execute(
text("DELETE FROM wehago_voucher_rows WHERE fiscal_year IN :years").bindparams(bindparam("years", expanding=True)),
{"years": sorted(voucher_years)},
).rowcount
unused_source_ids = {
int(row[0])
for row in conn.execute(
text(
"""
SELECT sf.id
FROM wehago_source_files sf
LEFT JOIN wehago_ledger_rows l ON l.source_file_id = sf.id
LEFT JOIN wehago_voucher_rows v ON v.source_file_id = sf.id
GROUP BY sf.id
HAVING COUNT(l.id) = 0 AND COUNT(v.id) = 0
"""
)
)
}
if unused_source_ids:
conn.execute(
text("DELETE FROM wehago_source_files WHERE id IN :ids").bindparams(bindparam("ids", expanding=True)),
{"ids": sorted(unused_source_ids)},
)
conn.execute(
text("DELETE FROM wehago_comparison_results WHERE fiscal_year IN :years").bindparams(bindparam("years", expanding=True)),
{"years": sorted(ledger_years | voucher_years)},
)
conn.execute(text("DELETE FROM wehago_metric_count_cache"))
conn.execute(text("DELETE FROM wehago_result_row_cache"))
conn.execute(text("DELETE FROM wehago_pair_recommend_cache"))
conn.execute(text("DELETE FROM wehago_background_jobs WHERE job_type = 'pair_recommend_precompute'"))
return {"ledger_deleted": int(ledger_deleted or 0), "voucher_deleted": int(voucher_deleted or 0), "source_deleted": len(unused_source_ids)}
def _import_one(
conn,
path: Path,
expected_kind: str,
expected_year: int,
*,
allowed_years: set[int] | None = None,
year_hint_override: int | None | object = _DEFAULT_YEAR_HINT,
) -> dict[str, object]:
file_kind, header, sheet_name = detect_file_kind(path)
if file_kind != expected_kind:
raise ValueError(f"{path} 형식이 {expected_kind}가 아닙니다: {file_kind}")
workbook, sheet = _load_sheet(path)
try:
sample_rows = list(sheet.iter_rows(min_row=2, max_row=51, values_only=True))
if year_hint_override is _DEFAULT_YEAR_HINT:
year_hint = infer_year_hint(path, file_kind, sample_rows) or expected_year
else:
year_hint = year_hint_override
source_id, _changed = upsert_source_file(conn, path, file_kind, year_hint, sheet_name, header)
if file_kind == "ledger":
conn.execute(text("DELETE FROM wehago_ledger_rows WHERE source_file_id = :source_id"), {"source_id": source_id})
inserted = import_ledger_rows(conn, source_id, sheet_name, sheet.iter_rows(min_row=2, values_only=True), year_hint)
else:
conn.execute(text("DELETE FROM wehago_voucher_rows WHERE source_file_id = :source_id"), {"source_id": source_id})
inserted = import_voucher_rows(conn, source_id, sheet_name, sheet.iter_rows(min_row=2, values_only=True), year_hint)
allowed = sorted(allowed_years or {expected_year})
if allowed:
conn.execute(
text(
"""
DELETE FROM wehago_ledger_rows
WHERE source_file_id = :source_id
AND fiscal_year NOT IN :allowed_years
"""
).bindparams(bindparam("allowed_years", expanding=True)),
{"source_id": source_id, "allowed_years": allowed},
)
conn.execute(
text(
"""
DELETE FROM wehago_voucher_rows
WHERE source_file_id = :source_id
AND fiscal_year NOT IN :allowed_years
"""
).bindparams(bindparam("allowed_years", expanding=True)),
{"source_id": source_id, "allowed_years": allowed},
)
inserted = int(
conn.execute(
text(f"SELECT COUNT(*) FROM wehago_{file_kind}_rows WHERE source_file_id = :source_id"),
{"source_id": source_id},
).scalar_one()
or 0
)
conn.execute(
text("UPDATE wehago_source_files SET row_count = :row_count, imported_at = CURRENT_TIMESTAMP WHERE id = :source_id"),
{"row_count": inserted, "source_id": source_id},
)
return {
"path": str(path),
"kind": file_kind,
"year_hint": year_hint,
"allowed_years": allowed,
"source_id": source_id,
"inserted": inserted,
"file_hash": compute_file_hash(path),
}
finally:
workbook.close()
def _fetch_counts(conn) -> dict[str, object]:
counts: dict[str, object] = {}
for table in ("wehago_ledger_rows", "wehago_voucher_rows", "wehago_comparison_results"):
rows = conn.execute(
text(f"SELECT fiscal_year, COUNT(*) AS row_count FROM {table} GROUP BY fiscal_year ORDER BY fiscal_year")
).mappings()
counts[table] = {str(row["fiscal_year"]): int(row["row_count"]) for row in rows}
return counts
def _fetch_quality(conn) -> dict[str, object]:
total_rows = conn.execute(
text(
"""
SELECT COUNT(*)
FROM wehago_ledger_rows
WHERE REPLACE(COALESCE(description, ''), ' ', '') IN ('[월계]', '[누계]', '월계', '누계')
OR REPLACE(COALESCE(ledger_date, ''), ' ', '') IN ('[월계]', '[누계]', '월계', '누계')
"""
)
).scalar_one()
blank_account_rows = conn.execute(
text(
"""
SELECT COUNT(*)
FROM wehago_ledger_rows
WHERE fiscal_year IN (2022, 2023, 2024)
AND (COALESCE(account_code, '') = '' OR COALESCE(account_name, '') = '')
"""
)
).scalar_one()
account_names = conn.execute(
text(
"""
SELECT fiscal_year, account_code, COUNT(DISTINCT account_name) AS name_count,
GROUP_CONCAT(DISTINCT account_name) AS names
FROM wehago_ledger_rows
WHERE fiscal_year IN (2022, 2023, 2024)
AND COALESCE(account_code, '') <> ''
GROUP BY fiscal_year, account_code
HAVING COUNT(DISTINCT account_name) > 1
ORDER BY fiscal_year, account_code
LIMIT 20
"""
)
).mappings()
voucher_counts_by_source = conn.execute(
text(
"""
SELECT sf.file_name, v.fiscal_year, COUNT(*) AS row_count
FROM wehago_voucher_rows v
JOIN wehago_source_files sf ON sf.id = v.source_file_id
WHERE v.fiscal_year IN (2022, 2023, 2024, 2025)
GROUP BY sf.file_name, v.fiscal_year
ORDER BY v.fiscal_year, sf.file_name
"""
)
).mappings()
return {
"monthly_cumulative_rows": int(total_rows or 0),
"blank_account_rows_2022_2024": int(blank_account_rows or 0),
"multi_name_accounts": [dict(row) for row in account_names],
"voucher_counts_by_source": [dict(row) for row in voucher_counts_by_source],
}
def main() -> None:
voucher_paths = [item["path"] for item in VOUCHER_FILES]
missing = [path for path in [*LEDGER_FILES.values(), *voucher_paths] if not path.exists()]
if missing:
raise FileNotFoundError("\\n".join(str(path) for path in missing))
init_wehago_compare_db(ENGINE)
summary: dict[str, object] = {"deleted": {}, "imported": []}
with ENGINE.begin() as conn:
summary["deleted"] = _delete_year_data(conn, ledger_years={2022, 2023, 2024, 2025}, voucher_years={2022, 2023, 2024, 2025})
for year, path in LEDGER_FILES.items():
summary["imported"].append(_import_one(conn, path, "ledger", year, allowed_years={year}))
for item in VOUCHER_FILES:
summary["imported"].append(
_import_one(
conn,
item["path"],
"voucher",
int(item["expected_year"]),
allowed_years=set(item["allowed_years"]),
year_hint_override=item["year_hint"],
)
)
rebuild_comparison_results(conn)
summary["dashboard_counts"] = {
str(year): _fallback_metric_counts_from_db(conn, year, year)
for year in range(2022, 2026)
}
summary["counts"] = _fetch_counts(conn)
summary["quality"] = _fetch_quality(conn)
print(json.dumps(summary, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\WEHAGO_DB\find_realgrid_registry.py"
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\inspect_realgrid_views_9225.py"
+49
View File
@@ -0,0 +1,49 @@
import json
import os
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
driver.set_script_timeout(10)
script = r"""
const ids = Array.from(document.querySelectorAll('.rg-body')).map((el) => ({
view: String(el.$_view || ''),
text: (el.textContent || '').slice(0, 200),
rect: (() => { const r = el.getBoundingClientRect(); return {left:r.left, top:r.top, width:r.width, height:r.height}; })()
}));
const summarize = (name, value) => {
const out = {name, type: typeof value, text: String(value).slice(0, 300)};
if (value && typeof value === 'object') {
let keys = [];
try { keys = Object.getOwnPropertyNames(value).concat(Object.keys(value)); } catch(e) {}
out.keys = Array.from(new Set(keys)).slice(0, 250);
out.samples = out.keys.slice(0, 80).map((key) => {
try {
const v = value[key];
return {key, type: typeof v, text: String(v).slice(0, 160)};
} catch (e) {
return {key, error: String(e)};
}
});
}
return out;
};
const out = {ids, globals: []};
const names = ['Grids', 'RealGridJS', 'RealGrid', 'realgrid'];
for (const name of names) out.globals.push(summarize(name, window[name]));
for (const key of Object.keys(window)) {
if (/grid|real|view|provider/i.test(key)) {
try { out.globals.push(summarize('window.' + key, window[key])); } catch(e) {}
}
if (out.globals.length > 80) break;
}
return out;
"""
print(json.dumps(driver.execute_script(script), ensure_ascii=False, indent=2)[:100000])
driver.quit()
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\inspect_rg_body_props_9225.py"
+43
View File
@@ -0,0 +1,43 @@
import json
import os
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
driver.set_script_timeout(10)
script = r"""
const out = [];
for (const body of document.querySelectorAll('.rg-body')) {
const item = {view: String(body.$_view || ''), props: []};
const keys = [];
let node = body;
while (node) {
try { keys.push(...Object.getOwnPropertyNames(node)); } catch(e) {}
node = Object.getPrototypeOf(node);
if (!node || node === Object.prototype) break;
}
for (const key of Array.from(new Set(keys)).slice(0, 500)) {
if (!/^\$_|grid|view|data|provider|handler|container|model|_/.test(key)) continue;
try {
const v = body[key];
const rec = {key, type: typeof v, text: String(v).slice(0, 200)};
if (v && typeof v === 'object') {
let sub = [];
try { sub = Object.getOwnPropertyNames(v).concat(Object.keys(v)); } catch(e) {}
rec.keys = Array.from(new Set(sub)).slice(0, 80);
rec.funcs = rec.keys.filter(k => { try { return typeof v[k] === 'function'; } catch(e) { return false; } }).slice(0, 40);
}
item.props.push(rec);
} catch(e) {}
}
out.push(item);
}
return out;
"""
print(json.dumps(driver.execute_script(script), ensure_ascii=False, indent=2)[:80000])
driver.quit()
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\inspect_wehago_grid_9225.py"
+49
View File
@@ -0,0 +1,49 @@
import json
import os
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
driver.set_script_timeout(10)
script = r"""
const g = window.Grids && window.Grids.realgrid;
const safe = (fn) => {
try { return fn(); } catch (e) { return {error: String(e)}; }
};
const out = {};
out.current = safe(() => g && g.getCurrent && g.getCurrent());
out.fullItemCount = safe(() => g && g.fullItemCount && g.fullItemCount());
out.columns = safe(() => g && g.getColumns && g.getColumns().map(c => ({
name: c.name || c.fieldName || c.field,
fieldName: c.fieldName,
header: c.header && (c.header.text || c.header)
})).slice(0, 80));
out.rows = [];
if (g) {
const n = Math.min(30, Number(out.fullItemCount) || 30);
for (let i = 0; i < n; i += 1) {
out.rows.push({
item: i,
dataRow: safe(() => g.getDataRow(i)),
values: safe(() => g.getValues(i)),
display: safe(() => g.getDisplayValuesOfRow ? g.getDisplayValuesOfRow(i) : g.getDisplayValues(i))
});
}
}
out.visibleText = Array.from(document.querySelectorAll('.rg-data-cell, td[class*=rg-data-cell], [class*=rg-data-cell], td'))
.map(el => {
const r = el.getBoundingClientRect();
return {text: (el.textContent || '').trim(), left: Math.round(r.left), top: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height)};
})
.filter(x => x.text && x.width > 0 && x.height > 0 && x.top > 120)
.slice(0, 120);
return out;
"""
print(json.dumps(driver.execute_script(script), ensure_ascii=False, indent=2)[:60000])
driver.quit()
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\inspect_wehago_inputs_9225.py"
+35
View File
@@ -0,0 +1,35 @@
import json
import os
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
script = r"""
const items = [];
for (const el of document.querySelectorAll('input, textarea, select, button, [role=button], a')) {
const r = el.getBoundingClientRect();
const style = getComputedStyle(el);
if (r.width <= 0 || r.height <= 0 || style.display === 'none' || style.visibility === 'hidden') continue;
items.push({
tag: el.tagName,
type: el.getAttribute('type'),
text: (el.textContent || '').trim(),
value: el.value || '',
placeholder: el.getAttribute('placeholder') || '',
title: el.getAttribute('title') || '',
aria: el.getAttribute('aria-label') || '',
name: el.getAttribute('name') || '',
id: el.id || '',
cls: String(el.className || '').slice(0, 120),
left: Math.round(r.left), top: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height)
});
}
return items.sort((a,b)=>(a.top-b.top)||(a.left-b.left));
"""
print(json.dumps(driver.execute_script(script), ensure_ascii=False, indent=2)[:50000])
driver.quit()
+3
View File
@@ -0,0 +1,3 @@
@echo off
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\mark_wehago_2022_no_data.py"
+33
View File
@@ -0,0 +1,33 @@
from pathlib import Path
from openpyxl import load_workbook
base = Path(r"\\wsl.localhost\Ubuntu\home\b17301\WEHAGO_DB\data_download\2022")
target = max(base.glob("ledger_*.xlsx"), key=lambda item: item.stat().st_mtime)
no_data = {"377", "400", "636", "643", "805", "819", "820", "824", "843", "932", "936", "950", "951"}
workbook = load_workbook(target)
status_ws = workbook["계정별상태"]
error_ws = workbook["오류_누락"]
for row in status_ws.iter_rows(min_row=2):
code = str(row[0].value)
if code in no_data:
row[2].value = "데이터없음"
row[5].value = "2022년 WEHAGO 계정별원장 화면에서 거래 데이터가 없어 다운로드 파일을 생성하지 않았습니다."
for row_index in range(2, error_ws.max_row + 1):
for col_index in range(1, error_ws.max_column + 1):
error_ws.cell(row_index, col_index).value = None
write_row = 2
for row in status_ws.iter_rows(min_row=2, values_only=True):
code, name, status, file_name, _row_count, message = row[:6]
if status != "정상":
for col_index, value in enumerate([code, name, status, file_name, message], start=1):
error_ws.cell(write_row, col_index).value = value
write_row += 1
workbook.save(target)
print(target)
+3
View File
@@ -0,0 +1,3 @@
@echo off
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\mark_wehago_no_data.py" %*
+42
View File
@@ -0,0 +1,42 @@
import sys
from pathlib import Path
from openpyxl import load_workbook
YEAR_NO_DATA = {
"2022": {"377", "400", "636", "643", "805", "819", "820", "824", "843", "932", "936", "950", "951"},
"2023": {"263", "417", "636", "805", "819", "843", "932", "933", "936", "950", "951"},
"2024": {"116", "263", "417", "805", "950", "951"},
}
year = sys.argv[1]
base = Path(r"\\wsl.localhost\Ubuntu\home\b17301\WEHAGO_DB\data_download") / year
target = max(base.glob("ledger_*.xlsx"), key=lambda item: item.stat().st_mtime)
no_data = YEAR_NO_DATA[year]
workbook = load_workbook(target)
status_ws = workbook["계정별상태"]
error_ws = workbook["오류_누락"]
for row in status_ws.iter_rows(min_row=2):
code = str(row[0].value)
if code in no_data:
row[2].value = "데이터없음"
row[5].value = f"{year}년 WEHAGO 계정별원장 화면에서 거래 데이터가 없어 다운로드 파일을 생성하지 않았습니다."
for row_index in range(2, error_ws.max_row + 1):
for col_index in range(1, error_ws.max_column + 1):
error_ws.cell(row_index, col_index).value = None
write_row = 2
for row in status_ws.iter_rows(min_row=2, values_only=True):
code, name, status, file_name, _row_count, message = row[:6]
if status != "정상":
for col_index, value in enumerate([code, name, status, file_name, message], start=1):
error_ws.cell(write_row, col_index).value = value
write_row += 1
workbook.save(target)
print(target)
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_DOWNLOAD_DIR=\\wsl.localhost\Ubuntu\home\b17301\WEHAGO_DB\data_download\2022"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\wehago_data_download_2022_work.py" --merge-only
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_DOWNLOAD_DIR=\\wsl.localhost\Ubuntu\home\b17301\WEHAGO_DB\data_download\2023"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\wehago_data_download_2022_work.py" --merge-only
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_DOWNLOAD_DIR=\\wsl.localhost\Ubuntu\home\b17301\WEHAGO_DB\data_download\2024"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\wehago_data_download_2022_work.py" --merge-only
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\probe_realgrid_active_9225.py"
+71
View File
@@ -0,0 +1,71 @@
import json
import os
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
driver.set_script_timeout(10)
script = r"""
const body = Array.from(document.querySelectorAll('.rg-body')).find(el => String(el.$_view) === '75');
if (body) {
const r = body.getBoundingClientRect();
const x = r.left + 80, y = r.top + 85;
for (const type of ['mouseover','mousemove','mousedown','mouseup','click']) {
body.dispatchEvent(new MouseEvent(type,{bubbles:true,cancelable:true,view:window,clientX:x,clientY:y,button:0}));
}
}
const summarize = (obj) => {
if (!obj) return null;
let keys = [];
try { keys = Object.getOwnPropertyNames(obj).concat(Object.keys(obj)); } catch(e) {}
const funcs = Array.from(new Set(keys)).filter(k => {
try { return typeof obj[k] === 'function'; } catch(e) { return false; }
}).slice(0, 160);
const props = {};
for (const k of keys.slice(0, 100)) {
try {
const v = obj[k];
if (['string','number','boolean'].includes(typeof v)) props[k]=v;
} catch(e) {}
}
return {text:String(obj).slice(0,200), keys:Array.from(new Set(keys)).slice(0,200), funcs, props};
};
const active = Grids.getActiveGrid && Grids.getActiveGrid();
const handler = active && active.getHandler && active.getHandler();
const out = {
active: summarize(active),
handler: summarize(handler),
current: null,
itemCount: null,
columns: null,
rows: []
};
const targets = [active, handler].filter(Boolean);
for (const g of targets) {
if (!out.current) {
try { if (g.getCurrent) out.current = g.getCurrent(); } catch(e) { out.current = {error:String(e)}; }
}
if (!out.itemCount) {
try { if (g.getItemCount) out.itemCount = g.getItemCount(); } catch(e) {}
try { if (!out.itemCount && g.fullItemCount) out.itemCount = g.fullItemCount(); } catch(e) {}
}
if (!out.columns) {
try { if (g.getColumns) out.columns = g.getColumns().map(c => ({name:c.name, fieldName:c.fieldName, text:c.header && c.header.text})); } catch(e) {}
}
try {
if (g.getValues) {
for (let i=0; i<Math.min(10, out.itemCount || 10); i++) out.rows.push({i, values:g.getValues(i)});
break;
}
} catch(e) { out.rowsError = String(e); }
}
return out;
"""
print(json.dumps(driver.execute_script(script), ensure_ascii=False, indent=2)[:80000])
driver.quit()
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\probe_realgrid_view_methods_9225.py"
@@ -0,0 +1,37 @@
import json
import os
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
driver.set_script_timeout(10)
script = r"""
const rg = Grids && Grids.realgrid;
const ids = [75, '75', 327, '327'];
const methods = ['getCurrent','fullItemCount','getColumns','getValues','getDataRow','getDisplayValues','getDisplayValuesOfRow','setCurrent','setSelectionItem','getSelectionItem','getSelectionItems'];
const out = {};
for (const m of methods) {
out[m] = [];
for (const id of ids) {
try {
if (!rg || typeof rg[m] !== 'function') { out[m].push({id, missing:true}); continue; }
let value;
if (m === 'getValues' || m === 'getDataRow' || m === 'getDisplayValues' || m === 'getDisplayValuesOfRow') value = rg[m](id, 0);
else if (m === 'setCurrent') value = rg[m](id, {itemIndex: 5, column: 'Code'});
else if (m === 'setSelectionItem') value = rg[m](id, 5);
else value = rg[m](id);
out[m].push({id, ok:true, value});
} catch(e) {
out[m].push({id, error:String(e).slice(0,300)});
}
}
}
return out;
"""
print(json.dumps(driver.execute_script(script), ensure_ascii=False, indent=2)[:80000])
driver.quit()
+11 -2
View File
@@ -5,6 +5,7 @@ cd "$(dirname "$0")/.."
PORT="${1:-${INTRANET_PORT:-8010}}"
HEALTH_URL="http://127.0.0.1:${PORT}/health"
COMPARE_HEALTH_URL="http://127.0.0.1:${PORT}/health/wehago-compare"
if [ ! -x ".venv/bin/python" ]; then
echo "가상환경이 없습니다. 먼저 아래를 실행하세요."
@@ -15,8 +16,16 @@ if [ ! -x ".venv/bin/python" ]; then
fi
if curl -fsS --max-time 2 "$HEALTH_URL" >/dev/null 2>&1; then
echo "이미 실행 중입니다: $HEALTH_URL"
exit 0
if curl -fsS --max-time 4 "$COMPARE_HEALTH_URL" >/dev/null 2>&1; then
echo "이미 실행 중입니다: $HEALTH_URL"
exit 0
fi
echo "서버 포트는 열려 있지만 전표비교 readiness 가 실패했습니다. 재시작합니다."
EXISTING_PID="$(ss -ltnp "( sport = :${PORT} )" | awk 'NR>1 {print $NF}' | sed -n 's/.*pid=\([0-9][0-9]*\).*/\1/p' | head -n 1)"
if [ -n "${EXISTING_PID}" ]; then
kill "${EXISTING_PID}" >/dev/null 2>&1 || true
sleep 2
fi
fi
if ss -ltn "( sport = :${PORT} )" | tail -n +2 | grep -q .; then
+12
View File
@@ -0,0 +1,12 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
set "WEHAGO_OPEN_ACCOUNT_LEDGER_URL=1"
set "WEHAGO_REFRESH_BEFORE_RUN="
set "WEHAGO_ALLOW_UNCHANGED_DETAIL="
set "WEHAGO_SCAN_EXISTING_ACCOUNT_MODE="
set "WEHAGO_EXPECTED_PERIOD_START=2022.01.01"
set "WEHAGO_EXPECTED_PERIOD_END=2022.12.31"
set "WEHAGO_DOWNLOAD_DIR=\\wsl.localhost\Ubuntu\home\b17301\WEHAGO_DB\data_download\2022"
set "WEHAGO_ACCOUNT_LEDGER_URL=https://smarta.wehago.com/#/smarta/account/SABK0107?sao&cno=1173867&cd_com=biz202103030006368&gisu=27&yminsa=2026&searchData=2022010120221231&color=#1C90FB&companyName=(%%EC%%A3%%BC)%%ED%%95%%9C%%EB%%A7%%A5%%EA%%B8%%B0%%EC%%88%%A0&companyID=b21344"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\wehago_data_download_2022_work.py" --no-pause-on-failure --no-merge %*
@@ -0,0 +1,12 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
set "WEHAGO_OPEN_ACCOUNT_LEDGER_URL=1"
set "WEHAGO_REFRESH_BEFORE_RUN="
set "WEHAGO_ALLOW_UNCHANGED_DETAIL=1"
set "WEHAGO_SCAN_EXISTING_ACCOUNT_MODE="
set "WEHAGO_EXPECTED_PERIOD_START=2022.01.01"
set "WEHAGO_EXPECTED_PERIOD_END=2022.12.31"
set "WEHAGO_DOWNLOAD_DIR=\\wsl.localhost\Ubuntu\home\b17301\WEHAGO_DB\data_download\2022"
set "WEHAGO_ACCOUNT_LEDGER_URL=https://smarta.wehago.com/#/smarta/account/SABK0107?sao&cno=1173867&cd_com=biz202103030006368&gisu=27&yminsa=2026&searchData=2022010120221231&color=#1C90FB&companyName=(%%EC%%A3%%BC)%%ED%%95%%9C%%EB%%A7%%A5%%EA%%B8%%B0%%EC%%88%%A0&companyID=b21344"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\wehago_data_download_2022_work.py" --no-pause-on-failure --no-merge %*
@@ -0,0 +1,12 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
set "WEHAGO_OPEN_ACCOUNT_LEDGER_URL=1"
set "WEHAGO_REFRESH_BEFORE_RUN="
set "WEHAGO_ALLOW_UNCHANGED_DETAIL=1"
set "WEHAGO_SCAN_EXISTING_ACCOUNT_MODE="
set "WEHAGO_EXPECTED_PERIOD_START=2023.01.01"
set "WEHAGO_EXPECTED_PERIOD_END=2023.12.31"
set "WEHAGO_DOWNLOAD_DIR=\\wsl.localhost\Ubuntu\home\b17301\WEHAGO_DB\data_download\2023"
set "WEHAGO_ACCOUNT_LEDGER_URL=https://smarta.wehago.com/#/smarta/account/SABK0107?sao&cno=1173867&cd_com=biz202103030006368&gisu=28&yminsa=2026&searchData=2023010120231231&color=#1C90FB&companyName=(%%EC%%A3%%BC)%%ED%%95%%9C%%EB%%A7%%A5%%EA%%B8%%B0%%EC%%88%%A0&companyID=b21344"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\wehago_data_download_2022_work.py" --no-pause-on-failure --no-merge %*
@@ -0,0 +1,12 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
set "WEHAGO_OPEN_ACCOUNT_LEDGER_URL=1"
set "WEHAGO_REFRESH_BEFORE_RUN="
set "WEHAGO_ALLOW_UNCHANGED_DETAIL=1"
set "WEHAGO_SCAN_EXISTING_ACCOUNT_MODE="
set "WEHAGO_EXPECTED_PERIOD_START=2024.01.01"
set "WEHAGO_EXPECTED_PERIOD_END=2024.12.31"
set "WEHAGO_DOWNLOAD_DIR=\\wsl.localhost\Ubuntu\home\b17301\WEHAGO_DB\data_download\2024"
set "WEHAGO_ACCOUNT_LEDGER_URL=https://smarta.wehago.com/#/smarta/account/SABK0107?sao&cno=1173867&cd_com=biz202103030006368&gisu=29&yminsa=2026&searchData=2024010120241231&color=#1C90FB&companyName=(%%EC%%A3%%BC)%%ED%%95%%9C%%EB%%A7%%A5%%EA%%B8%%B0%%EC%%88%%A0&companyID=b21344"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\wehago_data_download_2022_work.py" --no-pause-on-failure --no-merge %*
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\test_left_click_modes_9225.py" %*
+55
View File
@@ -0,0 +1,55 @@
import json
import os
import sys
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
target = sys.argv[1] if len(sys.argv) > 1 else "110"
mode = sys.argv[2] if len(sys.argv) > 2 else "name-double"
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
result = driver.execute_script(
r"""
const code = arguments[0], mode = arguments[1];
const cells = Array.from(document.querySelectorAll('.rg-data-cell, td[class*=rg-data-cell], [class*=rg-data-cell], td'));
const codeCell = cells.find(cell => {
const r = cell.getBoundingClientRect();
return (cell.textContent || '').trim() === code && r.left < 220 && r.top > 145 && r.width > 0 && r.height > 0;
});
if (!codeCell) return {error:'code not visible'};
const cr = codeCell.getBoundingClientRect();
const nameCell = cells.find(cell => {
const r = cell.getBoundingClientRect();
return Math.abs(r.top - cr.top) < 3 && r.left > 180 && r.left < 360 && r.width > 0 && r.height > 0;
});
const targetEl = mode.includes('name') && nameCell ? nameCell : codeCell;
const r = targetEl.getBoundingClientRect();
const x = r.left + (mode.includes('left') ? 8 : r.width / 2);
const y = r.top + r.height / 2;
const send = (type, detail=1) => {
targetEl.dispatchEvent(new MouseEvent(type, {bubbles:true,cancelable:true,view:window,clientX:x,clientY:y,button:0,detail}));
};
const seq = ['mouseover','mousemove','mousedown','mouseup','click'];
for (const type of seq) send(type, 1);
if (mode.includes('double')) {
for (const type of ['mousedown','mouseup','click','dblclick']) send(type, 2);
}
return {clicked: code, mode, x, y, codeTop: cr.top, nameText: nameCell && nameCell.textContent};
""",
target,
mode,
)
time.sleep(1.0)
right = driver.execute_script(
r"""
const body = Array.from(document.querySelectorAll('.rg-body')).find(el => String(el.$_view) === '327');
return body ? (body.textContent || '').slice(0, 500) : '';
"""
)
print(json.dumps({"result": result, "right": right}, ensure_ascii=False, indent=2))
driver.quit()
+4
View File
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\test_left_list_search_9225.py" %*
+75
View File
@@ -0,0 +1,75 @@
import json
import os
import sys
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
code = sys.argv[1] if len(sys.argv) > 1 else "263"
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
def visible_controls():
return driver.execute_script(
r"""
const items=[];
for (const el of document.querySelectorAll('input,button,[role=button],a')) {
const r=el.getBoundingClientRect();
const st=getComputedStyle(el);
if (r.width<=0 || r.height<=0 || st.display==='none' || st.visibility==='hidden') continue;
items.push({tag:el.tagName,id:el.id||'',cls:String(el.className||'').slice(0,80),text:(el.textContent||'').trim(),value:el.value||'',placeholder:el.getAttribute('placeholder')||'',title:el.getAttribute('title')||'',left:Math.round(r.left),top:Math.round(r.top),width:Math.round(r.width),height:Math.round(r.height)});
}
return items.sort((a,b)=>(a.top-b.top)||(a.left-b.left));
"""
)
before = visible_controls()
button = None
for el in driver.find_elements(By.CSS_SELECTOR, "button, [role=button], a"):
if not el.is_displayed():
continue
r = driver.execute_script("const r=arguments[0].getBoundingClientRect(); return {left:r.left,top:r.top,width:r.width,height:r.height};", el)
text = (el.text or "").strip()
title = el.get_attribute("title") or ""
cls = el.get_attribute("class") or ""
if r["left"] < 180 and 150 <= r["top"] <= 210 and (text or title or "LSbutton" in cls):
button = el
break
if button:
button.click()
time.sleep(0.5)
after_click = visible_controls()
inputs = driver.find_elements(By.CSS_SELECTOR, "input")
typed = []
for inp in inputs:
if not inp.is_displayed() or not inp.is_enabled():
continue
r = driver.execute_script("const r=arguments[0].getBoundingClientRect(); return {left:r.left,top:r.top,width:r.width,height:r.height};", inp)
if 120 <= r["top"] <= 260 and r["left"] < 500:
try:
inp.click()
inp.send_keys(Keys.CONTROL, "a")
inp.send_keys(code)
inp.send_keys(Keys.ENTER)
typed.append(r)
time.sleep(0.8)
break
except Exception as exc:
typed.append({"error": str(exc), **r})
visible = driver.execute_script(
r"""
return Array.from(document.querySelectorAll('.rg-data-cell, td[class*=rg-data-cell], [class*=rg-data-cell], td')).map(cell=>{
const r=cell.getBoundingClientRect();
return {text:(cell.textContent||'').trim(),left:Math.round(r.left),top:Math.round(r.top),width:Math.round(r.width),height:Math.round(r.height)};
}).filter(x=>x.text && x.left<500 && x.top>140 && x.width>0 && x.height>0).slice(0,80);
"""
)
print(json.dumps({"clickedButton": bool(button), "typed": typed, "before": before[:40], "afterClick": after_click[:80], "visible": visible}, ensure_ascii=False, indent=2))
driver.quit()
@@ -0,0 +1,4 @@
@echo off
set "WEHAGO_CHROME_DEBUGGER_ADDRESS=127.0.0.1:9225"
cd /d C:\Windows
python -u "\\wsl.localhost\Ubuntu\home\b17301\my-intranet-app\scripts\test_selenium_left_click_9225.py" %*
+33
View File
@@ -0,0 +1,33 @@
import json
import os
import sys
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
target = sys.argv[1] if len(sys.argv) > 1 else "110"
options = Options()
options.add_experimental_option("debuggerAddress", os.environ.get("WEHAGO_CHROME_DEBUGGER_ADDRESS", "127.0.0.1:9225"))
driver = webdriver.Chrome(options=options)
clicked = None
for el in driver.find_elements(By.XPATH, f"//*[normalize-space(.)='{target}']"):
if not el.is_displayed():
continue
r = driver.execute_script("const r=arguments[0].getBoundingClientRect(); return {left:r.left, top:r.top, width:r.width, height:r.height};", el)
if r["left"] < 220 and r["top"] > 145 and r["width"] > 0 and r["height"] > 0:
clicked = r
try:
el.click()
except Exception:
ActionChains(driver).move_to_element(el).click(el).perform()
break
time.sleep(1.0)
right = driver.execute_script("const b=Array.from(document.querySelectorAll('.rg-body')).find(el=>String(el.$_view)==='327'); return b ? (b.textContent||'').slice(0,500) : '';")
print(json.dumps({"clicked": clicked, "right": right}, ensure_ascii=False, indent=2))
driver.quit()
File diff suppressed because it is too large Load Diff