feat: add organization policy center

This commit is contained in:
xz
2026-09-01 13:22:04 +08:00
parent a9c8eea271
commit 1d4154dd20
12 changed files with 1565 additions and 12 deletions
+37 -6
View File
@@ -140,6 +140,8 @@ db.exec("CREATE INDEX IF NOT EXISTS idx_generation_jobs_model_approval ON genera
db.exec("CREATE INDEX IF NOT EXISTS idx_commercial_approvals_org_status ON commercial_approval_requests(organization_id, status, updated_at DESC)");
db.exec("CREATE INDEX IF NOT EXISTS idx_commercial_approvals_requester ON commercial_approval_requests(requester_user_id, status, created_at DESC)");
db.exec("CREATE INDEX IF NOT EXISTS idx_commercial_approvals_target ON commercial_approval_requests(organization_id, request_type, target_key, status)");
db.exec("CREATE INDEX IF NOT EXISTS idx_organization_policies_org_category ON organization_policies(organization_id, category, policy_key)");
db.exec("CREATE INDEX IF NOT EXISTS idx_organization_policy_evaluations_scope ON organization_policy_evaluations(organization_id, workspace_id, project_id, result, created_at DESC)");
db.exec("CREATE INDEX IF NOT EXISTS idx_asset_versions_governance ON asset_versions(asset_id, rights_status, expires_at)");
db.exec("CREATE INDEX IF NOT EXISTS idx_asset_governance_reviews_asset ON asset_governance_reviews(asset_id, created_at DESC)");
db.exec("CREATE INDEX IF NOT EXISTS idx_asset_governance_reviews_scope ON asset_governance_reviews(organization_id, workspace_id, project_id, risk_status, created_at DESC)");
@@ -237,6 +239,8 @@ function seedRoles() {
["organization:manage", "修改组织设置"],
["organization:members:invite", "邀请组织成员"],
["organization:roles:manage", "管理组织角色权限策略"],
["policy:read", "查看组织生产、安全和合规策略"],
["policy:manage", "管理组织生产、安全和合规策略"],
["workspace:create", "创建工作区"],
["workspace:manage", "管理工作区设置"],
["workspace:members:manage", "管理工作区成员"],
@@ -285,14 +289,14 @@ function seedRoles() {
org_admin: [
"organization:manage", "organization:members:invite", "workspace:create", "workspace:manage",
"workspace:members:manage", "project:create", "project:manage", "project:members:manage",
"workflow:manage", "task:view", "task:manage", "task:complete", "style:read", "style:manage", "model:manage", "model:approve", "usage:view", "billing:manage", "quota:manage", "qa:review", "delivery:approve", "delivery:view", "compliance:manage", "audit:view", "queue:manage", "voice:approve",
"workflow:manage", "task:view", "task:manage", "task:complete", "policy:read", "policy:manage", "style:read", "style:manage", "model:manage", "model:approve", "usage:view", "billing:manage", "quota:manage", "qa:review", "delivery:approve", "delivery:view", "compliance:manage", "audit:view", "queue:manage", "voice:approve",
"system:settings:view", "service:health:view", "organization:roles:manage"
],
producer: ["project:create", "project:manage", "project:members:manage", "workflow:manage", "task:view", "task:manage", "task:complete", "style:read", "style:manage", "script:read", "job:create", "job:prioritize", "usage:view", "delivery:approve", "delivery:view", "voice:approve"],
writer: ["script:read", "script:edit", "task:view", "task:complete", "style:read", "job:create"],
art_director: ["asset:edit", "prompt:edit", "task:view", "task:complete", "style:read", "style:manage", "job:create"],
voice_editor: ["voice:edit", "voice:approve", "task:view", "task:complete", "style:read", "job:create"],
reviewer: ["script:read", "task:view", "task:complete", "style:read", "qa:review", "voice:approve", "delivery:view"],
producer: ["project:create", "project:manage", "project:members:manage", "workflow:manage", "task:view", "task:manage", "task:complete", "policy:read", "style:read", "style:manage", "script:read", "job:create", "job:prioritize", "usage:view", "delivery:approve", "delivery:view", "voice:approve"],
writer: ["script:read", "script:edit", "task:view", "task:complete", "policy:read", "style:read", "job:create"],
art_director: ["asset:edit", "prompt:edit", "task:view", "task:complete", "policy:read", "style:read", "style:manage", "job:create"],
voice_editor: ["voice:edit", "voice:approve", "task:view", "task:complete", "policy:read", "style:read", "job:create"],
reviewer: ["script:read", "task:view", "task:complete", "policy:read", "style:read", "qa:review", "voice:approve", "delivery:view"],
project_guest: ["script:read", "task:view", "style:read", "delivery:view"],
project_editor: ["script:read", "script:edit", "asset:edit", "prompt:edit", "voice:edit", "task:view", "task:manage", "task:complete", "style:read", "job:create", "delivery:view"],
project_viewer: ["script:read", "task:view", "style:read", "delivery:view"]
@@ -355,6 +359,32 @@ function seedOrganization({ id, name, slug, ownerUserId, description, workspaceI
insertIgnore("INSERT OR IGNORE INTO quota_allocations(id, organization_id, workspace_id, metric, limit_value, used_value, unit, period_start, period_end, created_at, updated_at) VALUES (?, ?, ?, 'storage', 1024, 128, 'GB', ?, ?, ?, ?)", [`quota-${workspaceId}-storage`, id, workspaceId, monthStart(), monthEnd(), timestamp, timestamp]);
}
function rollQuotaPeriods() {
const start = monthStart();
const end = monthEnd();
const timestamp = now();
const rows = dbAll("SELECT * FROM quota_allocations");
for (const row of rows) {
const current = dbGet(
"SELECT julianday(?) <= julianday('now') AND julianday(?) >= julianday('now') AS active",
[row.period_start, row.period_end]
);
if (current?.active) continue;
const usedValue = row.metric === "clip"
? Number(dbGet(
`SELECT COALESCE(SUM(units), 0) AS units
FROM usage_events
WHERE organization_id = ?
AND (workspace_id = ? OR (? IS NULL AND workspace_id IS NULL))
AND unit_name IN ('job', 'clip', 'clips')
AND created_at >= datetime('now', 'start of month')`,
[row.organization_id, row.workspace_id, row.workspace_id]
)?.units || 0)
: Number(row.used_value || 0);
dbRun("UPDATE quota_allocations SET used_value = ?, period_start = ?, period_end = ?, updated_at = ? WHERE id = ?", [usedValue, start, end, timestamp, row.id]);
}
}
function commercialEntitlementDefaults(billing = {}) {
const clipLimit = Number(billing.monthly_clip_quota || 2400);
const storageGb = Number(billing.storage_gb || 1024);
@@ -1239,6 +1269,7 @@ withTransaction(() => {
insertIgnore("INSERT OR IGNORE INTO workspaces(id, organization_id, name, slug, description, status, created_at, updated_at) VALUES ('ws-pilot', 'org-studio-lab', '素材实验室', 'asset-lab', '角色、场景和模型试验空间', 'active', ?, ?)", [now(), now()]);
seedCommercialPlans();
seedOrganizationEntitlements();
rollQuotaPeriods();
seedCommercialApprovals();
seedMembersAndProjects();
seedModels();