Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)

含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 10:04:03 +00:00
commit 76f266645d
507 changed files with 99891 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
import { ForbiddenException } from '@nestjs/common';
import type { Prisma } from '@prisma/client';
import type { AuthUser } from './decorators/current-user.decorator';
import { PrismaService } from '../prisma/prisma.service';
export type DataScope = 'ALL' | 'DEPT' | 'SELF';
const RANK: Record<DataScope, number> = { SELF: 0, DEPT: 1, ALL: 2 };
export function mergeDataScope(scopes: string[]): DataScope {
let best: DataScope = 'SELF';
for (const s of scopes) {
const v = s as DataScope;
if (RANK[v] > RANK[best]) best = v;
}
return best;
}
export function effectiveScope(user: AuthUser): DataScope {
if (user.roles.includes('admin') || user.roles.includes('owner')) return 'ALL';
return user.dataScope || 'SELF';
}
export async function descendantDeptIds(prisma: PrismaService, rootId?: string | null) {
if (!rootId) return [] as string[];
const all = await prisma.department.findMany({
where: { tenantId: '1' },
select: { id: true, parentId: true },
});
const ids = new Set<string>([rootId]);
let grew = true;
while (grew) {
grew = false;
for (const d of all) {
if (d.parentId && ids.has(d.parentId) && !ids.has(d.id)) {
ids.add(d.id);
grew = true;
}
}
}
return [...ids];
}
function empInScope(user: AuthUser, deptIds: string[]): Prisma.EmployeeWhereInput {
return {
OR: [
{ departmentId: { in: deptIds } },
...(user.employeeId
? [{ managerId: user.employeeId }, { managerIds: { contains: user.employeeId } }]
: []),
],
};
}
/** 本人 / 本部门及下级 / 全公司 */
export async function ownerScope(
prisma: PrismaService,
user: AuthUser,
owner: 'createdById' | 'applicantId',
): Promise<Prisma.BidCaseWhereInput | Prisma.ExpenseClaimWhereInput | Record<string, never>> {
const scope = effectiveScope(user);
if (scope === 'ALL') return {};
if (scope === 'SELF' || !user.departmentId) return { [owner]: user.id };
const deptIds = await descendantDeptIds(prisma, user.departmentId);
const rel = owner === 'createdById' ? 'createdBy' : 'applicant';
return {
OR: [{ [owner]: user.id }, { [rel]: { employee: empInScope(user, deptIds) } }],
};
}
export function withAnd<T extends { AND?: unknown }>(where: T, extra: Record<string, unknown>): T {
if (!extra || !Object.keys(extra).length) return where;
const prev = where.AND;
const list = Array.isArray(prev) ? [...prev] : prev ? [prev] : [];
list.push(extra);
where.AND = list;
return where;
}
export async function canSeeOwner(prisma: PrismaService, user: AuthUser, ownerId?: string | null) {
if (!ownerId) return true;
const scope = effectiveScope(user);
if (scope === 'ALL') return true;
if (ownerId === user.id) return true;
if (scope !== 'DEPT' || !user.departmentId) return false;
const owner = await prisma.user.findUnique({
where: { id: ownerId },
include: { employee: true },
});
if (!owner?.employee) return false;
const deptIds = await descendantDeptIds(prisma, user.departmentId);
if (owner.employee.departmentId && deptIds.includes(owner.employee.departmentId)) return true;
const extra = String(owner.employee.managerIds || '')
.split(/[,,、\s]+/)
.filter(Boolean);
return Boolean(
user.employeeId &&
(owner.employee.managerId === user.employeeId || extra.includes(user.employeeId)),
);
}
export async function assertCanSeeOwner(prisma: PrismaService, user: AuthUser, ownerId?: string | null) {
if (await canSeeOwner(prisma, user, ownerId)) return;
throw new ForbiddenException('超出数据范围,无权查看');
}