B-44: 返修——余额目录视觉、八列事件表、抽屉键盘与审核三项决定
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const assert = require("assert");
|
||||
const ui = require(path.join(__dirname, "..", "web", "app.js"));
|
||||
|
||||
assert.strictEqual(ui.fmtAbsMoney(-1280), "1,280.00");
|
||||
assert.strictEqual(ui.fmtAbsMoney(1280), "1,280.00");
|
||||
assert.strictEqual(ui.fmtAbsMoney("60.5"), "60.50");
|
||||
|
||||
assert.strictEqual(ui.eventIsNegative({ posting_kind: "reversal" }), true);
|
||||
assert.strictEqual(ui.eventIsNegative({ posting_kind: "normal", is_repayment: true }), true);
|
||||
assert.strictEqual(ui.eventIsNegative({ posting_kind: "normal", is_repayment: false }), false);
|
||||
|
||||
assert.strictEqual(ui.cycleTab(0, 4, false), 1);
|
||||
assert.strictEqual(ui.cycleTab(3, 4, false), 0);
|
||||
assert.strictEqual(ui.cycleTab(0, 4, true), 3);
|
||||
assert.strictEqual(ui.cycleTab(2, 5, true), 1);
|
||||
assert.strictEqual(ui.cycleTab(0, 0, false), 0);
|
||||
|
||||
assert.strictEqual(ui.drawerEscAction(1), "close");
|
||||
assert.strictEqual(ui.drawerEscAction(0), "close");
|
||||
assert.strictEqual(ui.drawerEscAction(2), "back");
|
||||
assert.strictEqual(ui.drawerEscAction(3), "back");
|
||||
|
||||
const abs = ui.amountWithCurrency(1280, "CNY");
|
||||
assert.ok(abs.includes("CNY"));
|
||||
assert.ok(abs.includes("1,280.00"));
|
||||
assert.ok(!abs.includes("+"));
|
||||
assert.ok(!abs.includes("−"));
|
||||
|
||||
const repay = ui.amountWithCurrency(3200000, "CNY", { signed: true, negative: true });
|
||||
assert.ok(repay.includes("−"));
|
||||
assert.ok(!repay.includes("+"));
|
||||
assert.ok(repay.includes("3,200,000.00"));
|
||||
|
||||
assert.strictEqual(ui.resultDirection(10).label, "应收");
|
||||
assert.strictEqual(ui.resultDirection(-10).label, "应付");
|
||||
assert.strictEqual(ui.resultDirection(0).label, "持平");
|
||||
|
||||
assert.ok(ui.accountCell({ visibility: "visible", label: "中信 5316" }).includes("中信 5316"));
|
||||
assert.ok(ui.accountCell({ visibility: "masked" }).includes("按对方授权不可见"));
|
||||
assert.ok(ui.accountCell({ visibility: "missing" }).includes("源行缺失"));
|
||||
|
||||
console.log("b44_ui_check ok");
|
||||
@@ -0,0 +1,94 @@
|
||||
"""Static + Node checks for the B-44 visual rework.
|
||||
|
||||
Covers unique ids, nav copy, drawer/directory breakpoints, eight-column
|
||||
event table, and the exported keyboard/amount helpers.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
WEB = ROOT / "web"
|
||||
|
||||
|
||||
class B44FrontendContractTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.admin = (WEB / "admin.html").read_text(encoding="utf-8")
|
||||
cls.company = (WEB / "company.html").read_text(encoding="utf-8")
|
||||
cls.css = (WEB / "styles.css").read_text(encoding="utf-8")
|
||||
cls.js = (WEB / "app.js").read_text(encoding="utf-8")
|
||||
|
||||
def test_admin_ids_are_unique(self) -> None:
|
||||
self.assertEqual(1, self.admin.count('id="companyLedgers"'))
|
||||
self.assertEqual(1, self.admin.count('id="balanceLedgers"'))
|
||||
self.assertIn('data-view="pair"', self.admin)
|
||||
self.assertRegex(self.admin, r'data-view="pair"[^>]*>[\s\S]*?<span>往来查询</span>')
|
||||
self.assertIn("<h1>往来查询</h1>", self.admin)
|
||||
self.assertIn("转为异常后,该记录暂不纳入余额计算", self.admin)
|
||||
self.assertIn('id="auditExceptionNote"', self.admin)
|
||||
|
||||
def test_company_balance_groups_and_nav(self) -> None:
|
||||
self.assertIn('id="companyBalanceGroups"', self.company)
|
||||
self.assertNotIn('id="companyBalanceLine"', self.company)
|
||||
self.assertRegex(self.company, r'data-view="balances"[^>]*>[\s\S]*?<span>往来余额</span>')
|
||||
self.assertIn("<h1>往来余额</h1>", self.company)
|
||||
|
||||
def test_css_directory_and_drawer_breakpoints(self) -> None:
|
||||
self.assertIn("@media (max-width: 768px)", self.css)
|
||||
self.assertIn("@media (max-width: 375px)", self.css)
|
||||
self.assertIn("@media (max-width: 1179px)", self.css)
|
||||
self.assertIn("@media (max-width: 767px)", self.css)
|
||||
self.assertIn(".drawer {", self.css)
|
||||
self.assertIn("width: 640px", self.css)
|
||||
self.assertIn(".drawer { width: 480px; }", self.css)
|
||||
self.assertIn(".drawer { width: 100%; }", self.css)
|
||||
self.assertIn(".drawer .pair-balance-line.is-six", self.css)
|
||||
self.assertIn("repeat(3, 1fr)", self.css)
|
||||
self.assertIn(".drawer .event-table { min-width: 860px; }", self.css)
|
||||
self.assertIn(".company-row.is-balance-counterparty", self.css)
|
||||
self.assertIn(".ledger-hide-md", self.css)
|
||||
|
||||
def test_js_selectors_and_event_table(self) -> None:
|
||||
self.assertIn('loadAdminBalances($("#balanceLedgers"))', self.js)
|
||||
self.assertNotIn('loadAdminBalances($("#companyLedgers"))', self.js)
|
||||
self.assertIn("交易日期", self.js)
|
||||
self.assertIn("本方账户", self.js)
|
||||
self.assertIn("对方账户", self.js)
|
||||
self.assertIn("摘要", self.js)
|
||||
self.assertIn("匹配状态", self.js)
|
||||
self.assertIn("function eventTableHead()", self.js)
|
||||
self.assertIn("colspan=\"8\"", self.js)
|
||||
self.assertIn("function cycleTab(", self.js)
|
||||
self.assertIn("function drawerEscAction(", self.js)
|
||||
self.assertIn("function eventIsNegative(", self.js)
|
||||
self.assertIn("fmtAbsMoney", self.js)
|
||||
self.assertIn("is-balance-counterparty", self.js)
|
||||
heads = re.search(
|
||||
r"function eventTableHead\(\) \{\s*return `([^`]+)`",
|
||||
self.js,
|
||||
)
|
||||
self.assertIsNotNone(heads)
|
||||
markup = heads.group(1)
|
||||
self.assertNotIn("来源", markup)
|
||||
for label in ("交易日期", "方向", "科目", "本方账户", "对方账户", "摘要", "匹配状态", "金额"):
|
||||
self.assertIn(label, markup)
|
||||
self.assertEqual(8, len(re.findall(r"<th\b", markup)))
|
||||
|
||||
def test_node_keyboard_and_amount_helpers(self) -> None:
|
||||
result = subprocess.run(
|
||||
["node", str(ROOT / "tests" / "b44_ui_check.js")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(ROOT),
|
||||
)
|
||||
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
|
||||
self.assertIn("b44_ui_check ok", result.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -359,6 +359,72 @@ class PaginationTests(LedgerBase):
|
||||
{(self.company_a, "CNY"), (self.company_b, "CNY")}, seen
|
||||
)
|
||||
|
||||
def test_event_payload_account_chip_and_repayment_flag(self) -> None:
|
||||
self.pair(
|
||||
self.company_a, self.company_b, "40.00",
|
||||
summary="归还往来款", purpose="还款",
|
||||
)
|
||||
ledger_events.reconcile_bank_events(self.connection, actor=self.admin)
|
||||
event_id = int(self.ledger_events()[0]["id"])
|
||||
detail = positions.event_detail(self.connection, event_id)
|
||||
event = detail["event"]
|
||||
self.assertEqual("visible", event["payer_account"]["visibility"])
|
||||
self.assertEqual("测试 0001", event["payer_account"]["label"])
|
||||
self.assertEqual("visible", event["payee_account"]["visibility"])
|
||||
self.assertEqual("测试 0002", event["payee_account"]["label"])
|
||||
self.assertEqual("归还往来款", event["summary"])
|
||||
self.assertTrue(event["is_repayment"])
|
||||
|
||||
company_view = positions.event_payload(
|
||||
self.connection,
|
||||
self.connection.execute(
|
||||
positions._DETAIL_SELECT + " WHERE p.ledger_event_id = ?",
|
||||
(event_id,),
|
||||
).fetchone(),
|
||||
viewer_company_id=self.company_a,
|
||||
)
|
||||
self.assertEqual("visible", company_view["payer_account"]["visibility"])
|
||||
self.assertEqual("masked", company_view["payee_account"]["visibility"])
|
||||
self.assertEqual("按对方授权不可见", company_view["payee_account"]["label"])
|
||||
|
||||
def test_park_subject_exception_hides_from_review_queue(self) -> None:
|
||||
self.pair(self.company_a, self.company_b, "100.00")
|
||||
ledger_events.reconcile_bank_events(self.connection, actor=self.admin)
|
||||
event_id = int(self.ledger_events()[0]["id"])
|
||||
current = self.current(event_id)
|
||||
subjects.park_subject(
|
||||
self.connection, event_id,
|
||||
disposition="exception", reason="转异常核查",
|
||||
expected_revision=current["id"], request_key="park-exc-1",
|
||||
actor=self.admin,
|
||||
)
|
||||
queue = positions.subject_review_queue(
|
||||
self.connection, from_="2026-01-01", cutoff="2026-07-31",
|
||||
)
|
||||
self.assertFalse(
|
||||
any(item["ledger_event_id"] == event_id for item in queue["items"])
|
||||
)
|
||||
revision = self.current(event_id)
|
||||
self.assertEqual("pending_subject", revision["state"])
|
||||
|
||||
def test_park_subject_return_stays_in_review_queue(self) -> None:
|
||||
self.pair(self.company_a, self.company_b, "80.00")
|
||||
ledger_events.reconcile_bank_events(self.connection, actor=self.admin)
|
||||
event_id = int(self.ledger_events()[0]["id"])
|
||||
current = self.current(event_id)
|
||||
subjects.park_subject(
|
||||
self.connection, event_id,
|
||||
disposition="return", reason="退回补充摘要",
|
||||
expected_revision=current["id"], request_key="park-ret-1",
|
||||
actor=self.admin,
|
||||
)
|
||||
queue = positions.subject_review_queue(
|
||||
self.connection, from_="2026-01-01", cutoff="2026-07-31",
|
||||
)
|
||||
self.assertTrue(
|
||||
any(item["ledger_event_id"] == event_id for item in queue["items"])
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -550,6 +550,45 @@ class IntercompanyApiTests(unittest.TestCase):
|
||||
self.assertEqual("2026-06-15", reversals[0]["effective_at"][:10])
|
||||
self.assertEqual(event_id, reversals[0]["reverses_ledger_event_id"])
|
||||
|
||||
def test_admin_events_include_account_chips(self) -> None:
|
||||
pending = self._fresh_pair("100.00")
|
||||
revision = self._confirm(pending["ledger_event_id"])
|
||||
status, _, data = self.admin.get(
|
||||
"/api/admin/intercompany/events?from=2026-01-01&cutoff=2026-12-31"
|
||||
)
|
||||
self.assertEqual(200, status, data)
|
||||
event = next(
|
||||
item for item in as_json(data)["items"]
|
||||
if item["ledger_event_id"] == revision["ledger_event_id"]
|
||||
)
|
||||
self.assertIn("payer_account", event)
|
||||
self.assertIn("payee_account", event)
|
||||
self.assertEqual("visible", event["payer_account"]["visibility"])
|
||||
self.assertTrue(event["payer_account"]["label"])
|
||||
self.assertIn("summary", event)
|
||||
self.assertIn("is_repayment", event)
|
||||
|
||||
def test_subject_exception_drops_from_review_queue(self) -> None:
|
||||
pending = self._fresh_pair("77.00")
|
||||
status, _, data = self.admin.post_json(
|
||||
f"/api/admin/intercompany/events/{pending['ledger_event_id']}/subject-decisions",
|
||||
{
|
||||
"perspective_company_id": pending["payer_company_id"],
|
||||
"action": "exception",
|
||||
"reason": "转异常待核查",
|
||||
"expected_revision": pending["revision_id"],
|
||||
"request_key": "park-exc-api",
|
||||
},
|
||||
)
|
||||
self.assertEqual(200, status, data)
|
||||
self.assertEqual("pending_subject", as_json(data)["revision"]["state"])
|
||||
status, _, data = self.admin.get(
|
||||
"/api/admin/subject-reviews?from=2026-01-01&cutoff=2026-12-31"
|
||||
)
|
||||
self.assertEqual(200, status, data)
|
||||
ids = [item["ledger_event_id"] for item in as_json(data)["items"]]
|
||||
self.assertNotIn(pending["ledger_event_id"], ids)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user