HEL-144 返工A:四件事(改名/登录端标识/结账设置真保存/提醒回改)+ 迁移升为 7 + 移除废弃 B-44 前端样式测试
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -12,7 +12,7 @@ from urllib.parse import parse_qs, urlparse
|
||||
|
||||
from bank_importer import (
|
||||
auth, importing, ledger_events, manual_records, master_data, matching,
|
||||
multipart, personal_transit, positions, subjects,
|
||||
multipart, personal_transit, positions, settings, subjects,
|
||||
)
|
||||
from bank_importer.db import connect, migrate, utc_now
|
||||
|
||||
@@ -80,6 +80,15 @@ class AppHandler(SimpleHTTPRequestHandler):
|
||||
if path == "/api/admin/audit-log":
|
||||
self._handle_admin_audit_log(query)
|
||||
return
|
||||
if path == "/api/admin/settings":
|
||||
self._handle_admin_settings()
|
||||
return
|
||||
if path == "/api/admin/reminders":
|
||||
self._handle_admin_reminders(query)
|
||||
return
|
||||
if path == "/api/admin/reminders/pending":
|
||||
self._handle_admin_reminder_pending(query)
|
||||
return
|
||||
if path == "/api/admin/transfer-events":
|
||||
self._handle_admin_transfer_events(query)
|
||||
return
|
||||
@@ -232,6 +241,12 @@ class AppHandler(SimpleHTTPRequestHandler):
|
||||
if mapping_review:
|
||||
self._handle_admin_review_personal_mapping(int(mapping_review.group(1)))
|
||||
return
|
||||
if path == "/api/admin/settings":
|
||||
self._handle_admin_update_settings()
|
||||
return
|
||||
if path == "/api/admin/reminders/send":
|
||||
self._handle_admin_send_reminders()
|
||||
return
|
||||
|
||||
# B-44 intercompany positions (admin writes)
|
||||
subject_decision = re.fullmatch(
|
||||
@@ -297,7 +312,7 @@ class AppHandler(SimpleHTTPRequestHandler):
|
||||
if user is None:
|
||||
return None
|
||||
if user["role"] != "admin":
|
||||
self._send_json(403, {"status": "error", "message": "该操作仅限总账管理员。"})
|
||||
self._send_json(403, {"status": "error", "message": "该操作仅限管理员。"})
|
||||
return None
|
||||
return user
|
||||
|
||||
@@ -354,7 +369,7 @@ class AppHandler(SimpleHTTPRequestHandler):
|
||||
return
|
||||
if reason == "disabled":
|
||||
self._send_json(
|
||||
403, {"status": "error", "message": "账号已停用,请联系总账管理员。"}
|
||||
403, {"status": "error", "message": "账号已停用,请联系管理员。"}
|
||||
)
|
||||
return
|
||||
if user is None:
|
||||
@@ -1404,6 +1419,204 @@ class AppHandler(SimpleHTTPRequestHandler):
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# System settings (admin read/write, persisted + audited)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _handle_admin_settings(self) -> None:
|
||||
connection = connect(DB_PATH)
|
||||
try:
|
||||
user = self._require_admin(connection)
|
||||
if user is None:
|
||||
return
|
||||
values = settings.get_settings(connection)
|
||||
self._send_json(200, {"status": "ok", "settings": values})
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
def _handle_admin_update_settings(self) -> None:
|
||||
connection = connect(DB_PATH)
|
||||
try:
|
||||
user = self._require_admin(connection)
|
||||
if user is None:
|
||||
return
|
||||
data = self._read_json_body()
|
||||
if data is None:
|
||||
return
|
||||
before = settings.get_settings(connection)
|
||||
try:
|
||||
updated = settings.update_settings(connection, data, user)
|
||||
except ValueError as exc:
|
||||
self._send_json(400, {"status": "error", "message": str(exc)})
|
||||
return
|
||||
changed = {
|
||||
key: updated[key]
|
||||
for key in updated
|
||||
if before.get(key) != updated[key]
|
||||
}
|
||||
auth.audit(
|
||||
connection,
|
||||
"settings_update",
|
||||
actor=user,
|
||||
target="system_settings",
|
||||
detail=";".join(
|
||||
f"{key}:{before.get(key)}->{updated[key]}" for key in changed
|
||||
),
|
||||
ip=self._client_ip,
|
||||
)
|
||||
self._send_json(200, {"status": "ok", "settings": updated})
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Reminder management (admin)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _reminder_payload(row) -> dict[str, object]:
|
||||
payload = {
|
||||
"id": row["id"],
|
||||
"company_id": row["company_id"],
|
||||
"company_name": row["company_name"],
|
||||
"kind": row["kind"],
|
||||
"content": row["content"],
|
||||
"deadline": row["deadline"],
|
||||
"source": row["source"],
|
||||
"status": row["status"],
|
||||
"actor_username": row["actor_username"],
|
||||
"created_at": row["created_at"],
|
||||
}
|
||||
return payload
|
||||
|
||||
def _handle_admin_reminders(self, query: dict[str, list[str]]) -> None:
|
||||
connection = connect(DB_PATH)
|
||||
try:
|
||||
user = self._require_admin(connection)
|
||||
if user is None:
|
||||
return
|
||||
conditions: list[str] = []
|
||||
params: list[object] = []
|
||||
raw_company = (query.get("company_id") or [None])[0]
|
||||
if raw_company:
|
||||
try:
|
||||
params.append(int(raw_company))
|
||||
except ValueError:
|
||||
self._send_json(400, {"status": "error", "message": "company_id 参数无效。"})
|
||||
return
|
||||
conditions.append("r.company_id = ?")
|
||||
raw_limit = (query.get("limit") or ["200"])[0]
|
||||
try:
|
||||
limit = max(1, min(int(raw_limit), 500))
|
||||
except ValueError:
|
||||
limit = 200
|
||||
where = f"WHERE {' AND '.join(conditions)}" if conditions else ""
|
||||
rows = connection.execute(
|
||||
f"""
|
||||
SELECT r.id, r.company_id, c.name AS company_name, r.kind,
|
||||
r.content, r.deadline, r.source, r.status,
|
||||
r.actor_username, r.created_at
|
||||
FROM reminders r
|
||||
JOIN companies c ON c.id = r.company_id
|
||||
{where}
|
||||
ORDER BY r.id DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
(*params, limit),
|
||||
).fetchall()
|
||||
self._send_json(
|
||||
200,
|
||||
{"status": "ok",
|
||||
"reminders": [self._reminder_payload(row) for row in rows]},
|
||||
)
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
def _handle_admin_reminder_pending(self, query: dict[str, list[str]]) -> None:
|
||||
connection = connect(DB_PATH)
|
||||
try:
|
||||
user = self._require_admin(connection)
|
||||
if user is None:
|
||||
return
|
||||
raw_company = (query.get("company_id") or [None])[0]
|
||||
try:
|
||||
company_id = int(raw_company)
|
||||
except (TypeError, ValueError):
|
||||
self._send_json(400, {"status": "error", "message": "company_id 参数无效。"})
|
||||
return
|
||||
company = connection.execute(
|
||||
"SELECT id, name FROM companies WHERE id = ?", (company_id,)
|
||||
).fetchone()
|
||||
if company is None:
|
||||
self._send_json(404, {"status": "error", "message": "公司不存在。"})
|
||||
return
|
||||
items = settings.pending_items(connection, company_id)
|
||||
self._send_json(
|
||||
200,
|
||||
{
|
||||
"status": "ok",
|
||||
"company_id": company_id,
|
||||
"company_name": company["name"],
|
||||
"items": items,
|
||||
},
|
||||
)
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
def _handle_admin_send_reminders(self) -> None:
|
||||
connection = connect(DB_PATH)
|
||||
try:
|
||||
user = self._require_admin(connection)
|
||||
if user is None:
|
||||
return
|
||||
data = self._read_json_body()
|
||||
if data is None:
|
||||
return
|
||||
try:
|
||||
company_id = int(str(data.get("company_id")))
|
||||
except (TypeError, ValueError):
|
||||
self._send_json(400, {"status": "error", "message": "company_id 参数无效。"})
|
||||
return
|
||||
company = connection.execute(
|
||||
"SELECT id, name FROM companies WHERE id = ?", (company_id,)
|
||||
).fetchone()
|
||||
if company is None:
|
||||
self._send_json(404, {"status": "error", "message": "公司不存在。"})
|
||||
return
|
||||
try:
|
||||
created, deadline = settings.send_reminders(
|
||||
connection, company_id, user
|
||||
)
|
||||
except ValueError as exc:
|
||||
self._send_json(400, {"status": "error", "message": str(exc)})
|
||||
return
|
||||
if not created:
|
||||
self._send_json(
|
||||
400,
|
||||
{"status": "error",
|
||||
"message": "该公司当前没有待提醒事项,无需发送。"},
|
||||
)
|
||||
return
|
||||
auth.audit(
|
||||
connection,
|
||||
"reminder_send",
|
||||
actor=user,
|
||||
target=f"company:{company_id}",
|
||||
detail=f"items:{len(created)}",
|
||||
ip=self._client_ip,
|
||||
)
|
||||
self._send_json(
|
||||
200,
|
||||
{
|
||||
"status": "ok",
|
||||
"company_id": company_id,
|
||||
"company_name": company["name"],
|
||||
"deadline": deadline,
|
||||
"reminders": created,
|
||||
},
|
||||
)
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Canonical transfer events (admin)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user