完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)
这个提交包含在:
@@ -50,9 +50,10 @@ function parseEmployeeDirectoryFilters(query, options = {}) {
|
||||
return filters;
|
||||
}
|
||||
|
||||
function buildEmployeeDirectoryWhere(filters = {}) {
|
||||
function buildEmployeeDirectoryWhere(filters = {}, options = {}) {
|
||||
const q = filters.q?.trim() ?? "";
|
||||
const title = filters.title?.trim() ?? "";
|
||||
const includeContactSearch = options.includeContactSearch !== false;
|
||||
const and = [];
|
||||
|
||||
if (q) {
|
||||
@@ -60,8 +61,12 @@ function buildEmployeeDirectoryWhere(filters = {}) {
|
||||
OR: [
|
||||
{ employee_no: { contains: q, mode: "insensitive" } },
|
||||
{ name: { contains: q, mode: "insensitive" } },
|
||||
{ phone: { contains: q, mode: "insensitive" } },
|
||||
{ email: { contains: q, mode: "insensitive" } },
|
||||
...(includeContactSearch
|
||||
? [
|
||||
{ phone: { contains: q, mode: "insensitive" } },
|
||||
{ email: { contains: q, mode: "insensitive" } },
|
||||
]
|
||||
: []),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ const fs = require("node:fs/promises");
|
||||
const fsSync = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const { S3Client, PutObjectCommand, GetObjectCommand } = require("@aws-sdk/client-s3");
|
||||
const { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } = require("@aws-sdk/client-s3");
|
||||
|
||||
class StorageNotFoundError extends Error {
|
||||
constructor(message) {
|
||||
@@ -119,6 +119,34 @@ async function writeBuffer({ config, key, buffer, contentType }) {
|
||||
return key;
|
||||
}
|
||||
|
||||
async function deleteObject({ config, storagePath }) {
|
||||
const parsed = parseStoragePath(storagePath);
|
||||
if (!parsed.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.driver === "s3") {
|
||||
const s3 = resolveS3Config(config);
|
||||
const client = getS3Client(config);
|
||||
await client.send(
|
||||
new DeleteObjectCommand({
|
||||
Bucket: s3.bucket,
|
||||
Key: parsed.key,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const absolutePath = ensureSafeLocalPath(config.storageDir, parsed.key);
|
||||
try {
|
||||
await fs.unlink(absolutePath);
|
||||
} catch (error) {
|
||||
if (error?.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function readFileBuffer({ config, storagePath }) {
|
||||
const parsed = parseStoragePath(storagePath);
|
||||
if (!parsed.key) {
|
||||
@@ -206,6 +234,7 @@ async function openReadStream({ config, storagePath }) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
deleteObject,
|
||||
StorageConfigError,
|
||||
StorageNotFoundError,
|
||||
openReadStream,
|
||||
@@ -213,4 +242,3 @@ module.exports = {
|
||||
readFileBuffer,
|
||||
writeBuffer,
|
||||
};
|
||||
|
||||
|
||||
@@ -14,9 +14,11 @@ const BUSINESS_APPROVE_PERMISSIONS = Object.freeze([
|
||||
]);
|
||||
const BUSINESS_APPROVER_ROLE_CODES = new Set(["manager", "ceo", "finance", "finance_mgr", "hr"]);
|
||||
const FINANCE_ROLE_CODES = new Set(["finance", "finance_mgr"]);
|
||||
const PAYMENT_REVIEWER_ROLE_CODES = new Set(["manager", "ceo"]);
|
||||
const TEAM_NAVIGATION_ROLE_CODES = new Set(["manager", "ceo", "finance_mgr", "hr", "admin"]);
|
||||
const FINANCE_PERMISSION_CODES = Object.freeze(["payment.batch.manage", "risk.review.manage", "expense.finance.pay"]);
|
||||
const FINANCE_NAVIGATION_MODULES = new Set(["paymentBatches", "apOperations", "invoicePool"]);
|
||||
const PAYMENT_NAVIGATION_MODULES = new Set(["paymentBatches", "apOperations"]);
|
||||
const DAY_TO_DAY_BUSINESS_MODULES = new Set(["assets", "contractsRegistry", "purchaseRequests", "projects"]);
|
||||
const MANAGEMENT_CHILD_MODULES = new Set([
|
||||
"workspace",
|
||||
@@ -80,7 +82,7 @@ const NAVIGATION_REGISTRY = Object.freeze({
|
||||
projects: { key: "projects", label: "项目", icon: "workspaces", href: "/projects", anyOf: ["project.view_own", "project.view_all", "project.manage"], featureFlagKey: FEATURE_FLAG_KEYS.projectDomain },
|
||||
|
||||
workspace: { key: "workspace", label: "工作区设置", icon: "tune", href: "/system/workspace", anyOf: ["system.settings"] },
|
||||
people: { key: "people", label: "人员管理", icon: "group", href: "/people", anyOf: ["system.users", "report.dashboard.view"], featureFlagKey: FEATURE_FLAG_KEYS.employeeLifecycle },
|
||||
people: { key: "people", label: "人员管理", icon: "group", href: "/people", anyOf: ["system.users"], featureFlagKey: FEATURE_FLAG_KEYS.employeeLifecycle },
|
||||
organization: { key: "organization", label: "组织架构", icon: "account_tree", href: "/organization", anyOf: ["system.users", "report.dashboard.view"], featureFlagKey: FEATURE_FLAG_KEYS.employeeLifecycle },
|
||||
contractsRegistry: { key: "contractsRegistry", label: "合同", icon: "description", href: "/contracts-registry", anyOf: ["project.manage", "project.view_all", "system.settings"], featureFlagKey: FEATURE_FLAG_KEYS.projectLinking },
|
||||
workflowDefinitions: { key: "workflowDefinitions", label: "流程定义", icon: "schema", href: "/system/workflow-definitions", anyOf: ["system.settings"], featureFlagKey: FEATURE_FLAG_KEYS.workflowDefinitionAdmin },
|
||||
@@ -89,7 +91,7 @@ const NAVIGATION_REGISTRY = Object.freeze({
|
||||
auditLogs: { key: "auditLogs", label: "审计日志", icon: "manage_search", href: "/audit-logs", anyOf: ["system.audit.view"] },
|
||||
|
||||
approvalRules: { key: "approvalRules", label: "审批规则", icon: "rule", href: "/system/approval-rules", anyOf: ["system.settings"] },
|
||||
userManagement: { key: "userManagement", label: "用户管理", icon: "badge", href: "/system/users", anyOf: ["system.users"] },
|
||||
userManagement: { key: "userManagement", label: "用户管理", icon: "badge", href: "/system/users", anyOf: ["system.accounts.manage", "system.roles.manage"] },
|
||||
departments: { key: "departments", label: "部门管理", icon: "account_tree", href: "/system/departments", anyOf: ["system.users"] },
|
||||
masterData: { key: "masterData", label: "主数据", icon: "dataset", href: "/system/master-data", anyOf: ["masterdata.view", "masterdata.manage"] },
|
||||
notificationChannels: { key: "notificationChannels", label: "通知渠道", icon: "notifications_active", href: "/system/notification-channels", anyOf: ["notification.channel.manage"] },
|
||||
@@ -98,8 +100,8 @@ const NAVIGATION_REGISTRY = Object.freeze({
|
||||
purchaseRequests: { key: "purchaseRequests", label: "采购", icon: "shopping_cart", href: "/purchase-requests", anyOf: ["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"] },
|
||||
travelRequests: { key: "travelRequests", label: "差旅申请", icon: "flight", href: "/travel-requests", anyOf: ["travel.view_all", "travel.manage", "travel.payment.manage"] },
|
||||
budgets: { key: "budgets", label: "预算管理", icon: "account_balance", href: "/budgets", anyOf: ["budget.view", "budget.manage"] },
|
||||
paymentBatches: { key: "paymentBatches", label: "付款", icon: "payments", href: "/payment-batches", anyOf: ["payment.batch.manage", "risk.review.manage"], featureFlagKey: FEATURE_FLAG_KEYS.paymentBatch },
|
||||
apOperations: { key: "apOperations", label: "应付账款", icon: "account_balance_wallet", href: "/ap-operations", anyOf: ["payment.batch.manage", "risk.review.manage"], featureFlagKey: FEATURE_FLAG_KEYS.paymentBatch },
|
||||
paymentBatches: { key: "paymentBatches", label: "付款", icon: "payments", href: "/payment-batches", anyOf: ["payment.batch.manage", "risk.review.manage", "payment.batch.review"], featureFlagKey: FEATURE_FLAG_KEYS.paymentBatch },
|
||||
apOperations: { key: "apOperations", label: "应付账款", icon: "account_balance_wallet", href: "/ap-operations", anyOf: ["payment.batch.manage", "risk.review.manage", "payment.batch.review"], featureFlagKey: FEATURE_FLAG_KEYS.paymentBatch },
|
||||
invoicePool: { key: "invoicePool", label: "票据", icon: "receipt_long", href: "/invoice-pool", anyOf: ["invoice.manage"] },
|
||||
inventoryChecks: { key: "inventoryChecks", label: "资产盘点", icon: "inventory", href: "/inventory-checks", anyOf: ["asset.inventory"] },
|
||||
cutover: { key: "cutover", label: "切换控制台", icon: "swap_calls", href: "/system/cutover", anyOf: ["system.settings"], featureFlagKey: FEATURE_FLAG_KEYS.cutoverConsole },
|
||||
@@ -251,6 +253,14 @@ function hasFinanceRole(hasPermission, employee) {
|
||||
);
|
||||
}
|
||||
|
||||
function hasPaymentReviewerRole(hasPermission, employee) {
|
||||
const roleCodes = employee?.roleCodes ?? [];
|
||||
return (
|
||||
roleCodes.some((roleCode) => PAYMENT_REVIEWER_ROLE_CODES.has(roleCode)) &&
|
||||
hasPermission(employee, "payment.batch.review")
|
||||
);
|
||||
}
|
||||
|
||||
function hasTeamNavigationRole(employee) {
|
||||
const roleCodes = employee?.roleCodes ?? [];
|
||||
return roleCodes.some((roleCode) => TEAM_NAVIGATION_ROLE_CODES.has(roleCode));
|
||||
@@ -333,7 +343,12 @@ async function getSystemNavigation({ prisma, config, employee, hasPermission })
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FINANCE_NAVIGATION_MODULES.has(moduleKey) && !hasFinanceRole(hasPermission, employee)) {
|
||||
const financeNavigation = hasFinanceRole(hasPermission, employee);
|
||||
const paymentNavigation = financeNavigation || hasPaymentReviewerRole(hasPermission, employee);
|
||||
if (
|
||||
FINANCE_NAVIGATION_MODULES.has(moduleKey) &&
|
||||
!(PAYMENT_NAVIGATION_MODULES.has(moduleKey) ? paymentNavigation : financeNavigation)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -355,7 +370,7 @@ async function getSystemNavigation({ prisma, config, employee, hasPermission })
|
||||
const section = resolveNavigationSection({
|
||||
moduleKey,
|
||||
placement,
|
||||
financeNavigation: hasFinanceRole(hasPermission, employee),
|
||||
financeNavigation: PAYMENT_NAVIGATION_MODULES.has(moduleKey) ? paymentNavigation : financeNavigation,
|
||||
});
|
||||
|
||||
if (!section) {
|
||||
@@ -373,10 +388,11 @@ async function getSystemNavigation({ prisma, config, employee, hasPermission })
|
||||
backup: orderNavigationItems(sections.backup, NAVIGATION_SECTION_ORDER.backup),
|
||||
},
|
||||
recommendedRolePresets: [
|
||||
{ key: "owner", label: "老板", roleCodes: ["ceo"], description: "查看团队状态、审批汇总与关键管理入口。" },
|
||||
{ key: "admin", label: "管理员", roleCodes: ["admin"], description: "管理工作区、人员、流程、开关与审计。" },
|
||||
{ key: "employee", label: "员工", roleCodes: ["employee"], description: "日常使用工作台、请假记录、报销和进度查询。" },
|
||||
{ key: "manager", label: "主管", roleCodes: ["manager"], description: "处理审批、查看团队周报与在岗状态。" },
|
||||
{ key: "manager", label: "直属经理", roleCodes: ["employee", "manager"], description: "处理分配给自己的普通业务审批与团队工作。" },
|
||||
{ key: "owner", label: "公司负责人", roleCodes: ["employee", "ceo"], description: "处理公司级审批、付款复核与关键摘要。" },
|
||||
{ key: "finance", label: "财务", roleCodes: ["employee", "finance"], description: "处理工资、银行资料只读和付款业务。" },
|
||||
{ key: "systemAdmin", label: "OA 系统管理员", roleCodes: ["employee", "system_admin"], description: "管理账号、角色、系统配置、集成和审计,不处理业务审批。" },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户