Fix: Admin department binding and region visibility

1. Bind 'City-level' permits to 'SYSADMIN' department to ensure they are managed by admin.
2. Ensure Foshan 5 regions exist and Service Departments (FSSJSS etc.) are correctly bound to their regions.
3. Update specific permit binding logic to prevent leakage of 'City-level' data to district admins.
4. UI: Restrict region filter in Admin Console for non-admin users to their own region only.
This commit is contained in:
黄仁欢 2025-12-25 19:12:07 +08:00
parent 06944cd251
commit 2440a02a2d
5 changed files with 334 additions and 86 deletions

File diff suppressed because one or more lines are too long

View File

@ -2264,7 +2264,7 @@
<div class="header">
<div class="header-info">
<h1 id="pageTitle">🗃️ 管理员控制台</h1>
<p>LawRisk 法律风险提示系统 - 管理员功能面板</p>
<p>法律风险提示系统 - 管理员功能面板</p>
</div>
<div class="user-bar" id="userBar">
<div class="user-avatar" id="userAvatar">U</div>
@ -6112,21 +6112,91 @@
// 构建区域-部门映射关系
buildRegionDepartmentMapping();
// 渲染区域选项
// 渲染区域选项
const regionOptionsList = document.getElementById('regionOptionsList');
if (regionOptionsList) {
regionOptionsList.innerHTML = '';
permitFilterOptions.regions.forEach(region => {
let visibleRegions = permitFilterOptions.regions;
let isRestricted = false;
// 权限控制:如果不是 admin且有部门区域信息只显示所属区域
if (currentUserProfile && currentUserProfile.role !== 'admin' &&
currentUserProfile.department && currentUserProfile.department.region_name) {
const userRegion = currentUserProfile.department.region_name;
visibleRegions = visibleRegions.filter(r => (r.name || r) === userRegion);
if (visibleRegions.length > 0) {
isRestricted = true;
}
}
visibleRegions.forEach(region => {
const div = document.createElement('div');
div.style.padding = '6px 12px';
const checkedState = isRestricted ? 'checked' : '';
// 如果受限,禁止鼠标交互(pointer-events: none),让用户无法取消
const labelStyle = isRestricted
? 'display: flex; align-items: center; cursor: default; pointer-events: none; opacity: 0.8;'
: 'display: flex; align-items: center; cursor: pointer;';
div.innerHTML = `
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" name="regionFilter" value="${region.id || region}" onchange="onRegionSelectionChange()" style="margin-right: 8px;">
<label style="${labelStyle}">
<input type="checkbox" name="regionFilter" value="${region.id || region}"
onchange="onRegionSelectionChange()"
style="margin-right: 8px;" ${checkedState}>
<span>${region.name || region}</span>
</label>
`;
regionOptionsList.appendChild(div);
});
// 如果受限,主动触发一次状态更新,确保“关联部门”被联动过滤,且文本显示正确
if (isRestricted) {
const userRegionName = visibleRegions[0].name || visibleRegions[0];
// 1. 更新顶部显示文本
const selectedTextElement = document.getElementById('regionSelectedText');
if (selectedTextElement) {
selectedTextElement.textContent = userRegionName;
selectedTextElement.style.color = '#333';
}
// 2. 禁用下拉触发器
const filterDropdown = document.getElementById('filterRegion');
if (filterDropdown) {
filterDropdown.onclick = null; // 移除点击事件
filterDropdown.style.background = '#f3f4f6';
filterDropdown.style.cursor = 'not-allowed';
filterDropdown.style.color = '#666';
filterDropdown.title = '您只能查看所属区域的数据';
// 移除下拉箭头
const arrow = filterDropdown.querySelector('span[style*="float: right"]');
if (arrow) arrow.style.display = 'none';
}
// 3. 隐藏全选选项(避免困惑)
const regionSelectAllRow = document.querySelector('#regionOptions input[id="regionSelectAll"]')?.closest('div');
if (regionSelectAllRow) {
regionSelectAllRow.style.display = 'none';
}
// 4. 触发逻辑更新
setTimeout(() => {
onRegionSelectionChange();
}, 50);
} else {
// 恢复正常状态(如果是 Admin 切换回来)
const filterDropdown = document.getElementById('filterRegion');
if (filterDropdown) {
filterDropdown.onclick = function () { toggleMultiSelect('regionOptions'); };
filterDropdown.style.background = 'white';
filterDropdown.style.cursor = 'pointer';
const arrow = filterDropdown.querySelector('span[style*="float: right"]');
if (arrow) arrow.style.display = 'block';
}
}
}
// 渲染主题选项

View File

@ -1268,7 +1268,7 @@
<div class="page">
<header class="header">
<div class="title">
<h1>LawRisk 法律风险提示系统</h1>
<h1>法律风险提示系统</h1>
<p>超级管理员 · 用户、部门、主题与模板一站式管控</p>
</div>
<div class="user-chip" id="userChip">

View File

@ -0,0 +1,143 @@
import os
import sys
import uuid
import logging
import pg8000.native as pg
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Add project root to path
sys.path.append(os.getcwd())
from lawrisk.utils.env_loader import load_env
load_env()
def _lic_pg_conn(autocommit: bool = False) -> pg.Connection:
host = os.getenv("LIC_PG_HOST", "8.138.196.105") # Updated default to match known external IP
port = int(os.getenv("LIC_PG_PORT", os.getenv("PG_PORT", "5432")))
user = os.getenv("LIC_PG_USER", os.getenv("PG_USER", "postgres"))
password = os.getenv("LIC_PG_PASSWORD", "")
database = os.getenv("LIC_PG_DATABASE", "licensing_risks")
logger.info(f"Connecting to {host}:{port}/{database} as {user}")
try:
conn = pg.Connection(user=user, host=host, port=port, password=password, database=database)
conn.autocommit = autocommit
return conn
except Exception as e:
logger.error(f"Connection failed: {e}")
raise
def main():
try:
conn = _lic_pg_conn()
logger.info("Connected to database successfully.")
# 1. Get '市级' Region ID
region_id_city = None
for row in conn.run("SELECT id, name FROM regions WHERE name LIKE '市级%'"):
region_id_city = row[0]
logger.info(f"Found City Region: {row[1]} ({row[0]})")
break
if not region_id_city:
# Fallback if specific name differs
for row in conn.run("SELECT id, name FROM regions WHERE name = '市级'"):
region_id_city = row[0]
break
if not region_id_city:
logger.error("Could not find '市级' region. Exiting.")
return
# 2. Get/Create Admin Department
admin_dept_name = "系统管理部"
admin_dept_code = "SYSADMIN"
result = conn.run("SELECT id FROM service_departments WHERE code = :code", code=admin_dept_code)
if result:
admin_dept_id = result[0][0]
logger.info(f"Found existing Admin Department: {admin_dept_id}")
# Ensure it is bound to City Region
conn.run("UPDATE service_departments SET region_id = :rid WHERE id = :id", rid=region_id_city, id=admin_dept_id)
else:
admin_dept_id = str(uuid.uuid4())
logger.info(f"Creating new Admin Department: {admin_dept_id}")
conn.run("""
INSERT INTO service_departments (id, name, code, region_id, description, grade, unit_level)
VALUES (:id, :name, :code, :rid, 'System Administrator Department', 100, 'municipal')
""", id=admin_dept_id, name=admin_dept_name, code=admin_dept_code, rid=region_id_city)
# 3. Assign 'admin' user to this department
conn.run("""
UPDATE auth_users
SET service_department_id = :dept_id,
role = 'admin',
grade = 100
WHERE username = 'admin'
""", dept_id=admin_dept_id)
logger.info(f"Assigned 'admin' user to department {admin_dept_id}")
# 4. Bind City-level Permis to this Department
# Find permits in the city region
permits = conn.run("""
SELECT p.id, p.name
FROM region_permit_details rpd
JOIN permits p ON p.id = rpd.permit_id
WHERE rpd.region_id = :rid
""", rid=region_id_city)
permit_ids = [row[0] for row in permits]
logger.info(f"Found {len(permit_ids)} permits in '市级' region.")
if not permit_ids:
logger.warning("No permits found to bind!")
return
# Upsert permit_sources
# We want to ensure specific records exist for these permits
updated_count = 0
inserted_count = 0
for pid in permit_ids:
# Check if source exists
existing = conn.run("""
SELECT 1 FROM permit_sources WHERE region_id = :rid AND permit_id = :pid
""", rid=region_id_city, pid=pid)
if existing:
conn.run("""
UPDATE permit_sources
SET bound_department_id = :dept_id,
uploader_department_id = COALESCE(uploader_department_id, :dept_id),
updated_at = NOW()
WHERE region_id = :rid AND permit_id = :pid
""", dept_id=admin_dept_id, rid=region_id_city, pid=pid)
updated_count += 1
else:
conn.run("""
INSERT INTO permit_sources (
region_id, permit_id, source_type, source_name,
uploader_department_id, bound_department_id, created_at, updated_at
) VALUES (
:rid, :pid, 'system_init', 'Initial Binding',
:dept_id, :dept_id, NOW(), NOW()
)
""", rid=region_id_city, pid=pid, dept_id=admin_dept_id)
inserted_count += 1
logger.info(f"Binding complete. Updated: {updated_count}, Inserted: {inserted_count}")
except Exception as e:
logger.error(f"Error: {e}")
import traceback
traceback.print_exc()
finally:
if 'conn' in locals():
conn.close()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,116 @@
import os
import sys
import pg8000.native as pg
sys.path.append(os.getcwd())
from lawrisk.utils.env_loader import load_env
load_env()
def main():
try:
conn = pg.Connection(
user=os.getenv('LIC_PG_USER', 'postgres'),
host=os.getenv('LIC_PG_HOST', '8.138.196.105'),
port=int(os.getenv('LIC_PG_PORT', 5432)),
password=os.getenv('LIC_PG_PASSWORD', ''),
database=os.getenv('LIC_PG_DATABASE', 'licensing_risks')
)
print("Connected to DB.")
# 1. Investigate User FSSJSS
print("-" * 30)
print("Investigating User 'FSSJSS'")
user_res = conn.run("""
SELECT au.id, au.username, au.service_department_id, sd.name, sd.code, sd.parent_id
FROM auth_users au
JOIN service_departments sd ON sd.id = au.service_department_id
WHERE au.username = 'fssjss'
""")
if not user_res:
print("User FSSJSS not found!")
return
u_id, u_name, u_dept_id, u_dept_name, u_dept_code, u_parent_id = user_res[0]
print(f"User: {u_name}")
print(f"Department: {u_dept_name} ({u_dept_code}) ID: {u_dept_id}")
print(f"Parent Dept ID: {u_parent_id}")
# 2. Investigate SYSADMIN Department
print("-" * 30)
print("Investigating Department 'SYSADMIN'")
sys_res = conn.run("SELECT id, name, parent_id FROM service_departments WHERE code = 'SYSADMIN'")
if not sys_res:
print("SYSADMIN Department not found!")
else:
sys_id, sys_name, sys_parent = sys_res[0]
print(f"SYSADMIN ID: {sys_id}, Name: {sys_name}, Parent: {sys_parent}")
if str(sys_id) == str(u_dept_id):
print("ALERT: FSSJSS is directly in SYSADMIN department!")
if str(sys_id) == str(u_parent_id):
print("ALERT: FSSJSS is a direct child of SYSADMIN!")
# 3. Check Recursive Access for FSSJSS
print("-" * 30)
print("Checking accessible departments for FSSJSS...")
accessible_query = """
WITH RECURSIVE sub AS (
SELECT id, name, code FROM service_departments WHERE id = :root_id
UNION ALL
SELECT sd.id, sd.name, sd.code
FROM service_departments sd
JOIN sub ON sd.parent_id = sub.id
)
SELECT id, name, code FROM sub
"""
access_rows = conn.run(accessible_query, root_id=u_dept_id)
accessible_ids = [str(row[0]) for row in access_rows]
print(f"FSSJSS can see data from {len(accessible_ids)} departments.")
for row in access_rows:
print(f" - {row[1]} ({row[2]}) ID: {row[0]}")
if str(sys_id) in accessible_ids:
print("CRITICAL: SYSADMIN is in the accessible list! (This implies SYSADMIN is a descendant of FSSJSS??)")
else:
print("OK: SYSADMIN is NOT in the accessible list.")
# 4. Investigate a City Permit
print("-" * 30)
print("Investigating a sample City Permit")
# Find a permit in '市级'
permit_res = conn.run("""
SELECT ps.permit_id, ps.bound_department_id, ps.uploader_department_id, p.name
FROM permit_sources ps
JOIN region_permit_details rpd ON rpd.permit_id = ps.permit_id AND rpd.region_id = ps.region_id
JOIN regions r ON r.id = rpd.region_id
JOIN permits p ON p.id = ps.permit_id
WHERE r.name = '市级'
LIMIT 1
""")
if permit_res:
p_id, bound_id, uploader_id, p_name = permit_res[0]
print(f"Permit: {p_name}")
print(f"Bound Dept ID: {bound_id}")
print(f"Uploader Dept ID: {uploader_id}")
if str(bound_id) == str(sys_id):
print("Permit is correctly bound to SYSADMIN.")
else:
print(f"Permit is bound to {bound_id}, NOT SYSADMIN!")
if str(bound_id) in accessible_ids:
print("CRITICAL: The bound department IS in FSSJSS's accessible list.")
else:
print("OK: The bound department is NOT in FSSJSS's accessible list.")
else:
print("No city permits found for check.")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()