From f0e3deb6c86476bd92a475f8ee112aa1cf3323ab Mon Sep 17 00:00:00 2001 From: liuyun Date: Thu, 15 Oct 2026 08:36:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(c5):=20=E8=A7=92=E8=89=B2=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=9F=A9=E9=98=B5=20+=20=E6=B5=8B=E8=AF=95=E6=9C=BA?= =?UTF-8?q?=E6=9E=84=20seed-demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BUSINESS_ROLES 补全业务权限(director 只读/qc 主角色/leader 派单/nurse 留痕) - seed-demo.ts 初始化「投云养老服务机构」+ 5 账号 + 完整样例数据 Co-Authored-By: Claude Opus 4.8 --- package.json | 1 + prisma/seed-demo.ts | 557 ++++++++++++++++++++++++++++++++++ src/common/tenant-template.ts | 77 ++++- 3 files changed, 628 insertions(+), 7 deletions(-) create mode 100644 prisma/seed-demo.ts diff --git a/package.json b/package.json index 587a6a8..178ca05 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "test:debug": "vitest --inspect-brk --no-file-parallelism", "test:e2e": "vitest run --config ./vitest.config.e2e.ts", "seed": "prisma db seed", + "seed:demo": "tsx prisma/seed-demo.ts", "sync:resources": "tsx prisma/sync-resources.ts" }, "dependencies": { diff --git a/prisma/seed-demo.ts b/prisma/seed-demo.ts new file mode 100644 index 0000000..ba53199 --- /dev/null +++ b/prisma/seed-demo.ts @@ -0,0 +1,557 @@ +import { PrismaClient } from '@prisma/client'; +import bcrypt from 'bcryptjs'; +import { TENANT_ADMIN_ROLE_CODE } from '../src/common/constants.js'; +import { initTenant } from '../src/common/tenant-template.js'; + +/** + * 测试机构(TY20261015 投云养老服务机构)演示数据 seed。 + * 幂等:可重复执行(tenant/user/userRole/staff/elder/svc_org 按业务键 upsert,plan/order/record/abnormal/qc 用固定 id)。 + * + * 运行:pnpm exec tsx prisma/seed-demo.ts + */ +const prisma = new PrismaClient(); + +const TENANT_CODE = 'TY20261015'; +const TENANT_NAME = '投云养老服务机构'; +const ADMIN_USERNAME = 'admin01'; +const PASSWORD = 'Touyun@123'; +const BIZ_LINE = 'ltci'; +const LOC_CODE = '530100'; + +// 固定业务 id(幂等锚点) +const IDS = { + staffLeader: 'demo-staff-leader-0001', + staffNurseZhang: 'demo-staff-nurse-0001', + staffNurseLi: 'demo-staff-nurse-0002', + staffNurseWang: 'demo-staff-nurse-0003', + planZhang: 'demo-plan-zhang-0001', + orderZhang: 'demo-order-zhang-0001', + recordZhang: 'demo-record-zhang-0001', + abnormalZhang: 'demo-abnormal-zhang-0001', + qcZhang: 'demo-qc-zhang-0001', +} as const; + +// L1 目录样例(10 项)+ L4 拍照分档/配价(photoTier:沐浴/压疮/鼻饲/导管=must,擦浴=optional,其余 free) +const SERVICES: { natCode: string; natName: string; natCategory: string; isMedical: boolean; photoTier: string; price: string }[] = [ + { natCode: 'L0000106010020001', natName: '协助翻身', natCategory: 'life', isMedical: false, photoTier: 'free', price: '25.00' }, + { natCode: 'L0000106010020002', natName: '协助进食', natCategory: 'life', isMedical: false, photoTier: 'free', price: '25.00' }, + { natCode: 'L0000106010020003', natName: '口腔清洁', natCategory: 'life', isMedical: false, photoTier: 'free', price: '20.00' }, + { natCode: 'L0000106010020004', natName: '沐浴', natCategory: 'life', isMedical: false, photoTier: 'must', price: '50.00' }, + { natCode: 'L0000106010020005', natName: '压疮护理', natCategory: 'medical', isMedical: true, photoTier: 'must', price: '60.00' }, + { natCode: 'L0000106010020006', natName: '擦浴', natCategory: 'life', isMedical: false, photoTier: 'optional', price: '40.00' }, + { natCode: 'L0000106010020007', natName: '鼻饲', natCategory: 'medical', isMedical: true, photoTier: 'must', price: '45.00' }, + { natCode: 'L0000106010020008', natName: '导管护理', natCategory: 'medical', isMedical: true, photoTier: 'must', price: '55.00' }, + { natCode: 'L0000106010020009', natName: '血糖监测', natCategory: 'medical', isMedical: true, photoTier: 'free', price: '15.00' }, + { natCode: 'L0000106010020010', natName: '关节活动', natCategory: 'life', isMedical: false, photoTier: 'free', price: '30.00' }, +]; + +// 业务角色账号(admin01 由 initTenant 创建并绑 tenant_super_admin) +const ACCOUNTS = [ + { username: 'director01', roleCode: 'director', nickname: '院长' }, + { username: 'qc01', roleCode: 'qc', nickname: '质控员' }, + { username: 'leader01', roleCode: 'leader', nickname: '组长' }, + { username: 'nurse01', roleCode: 'nurse', nickname: '护理员' }, +]; + +// ---- 日期工具(@db.Date 用 UTC 零点,datetime 用本地墙上时间) ---- + +function pad(n: number): string { + return String(n).padStart(2, '0'); +} + +/** YYYY-MM-DD → @db.Date(UTC 零点)。 */ +function dateOnly(s: string): Date { + return new Date(`${s}T00:00:00Z`); +} + +/** 今天(本地)YYYY-MM-DD。 */ +function todayStr(): string { + const d = new Date(); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; +} + +/** 距今天 N 天的 YYYY-MM-DD(本地)。 */ +function daysFromToday(days: number): string { + const d = new Date(); + d.setDate(d.getDate() + days); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; +} + +/** 本月最后一天 YYYY-MM-DD。 */ +function lastDayOfMonth(): string { + const d = new Date(); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate())}`; +} + +/** 今天本地时间 HH:mm 的 Date。 */ +function todayAt(hour: number, minute: number): Date { + const d = new Date(); + d.setHours(hour, minute, 0, 0); + return d; +} + +// ---- GB11643 校验位(生成合法 18 位身份证) ---- + +const ID_WEIGHTS = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; +const ID_CHECK_CODES = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']; + +function idCard(prefix17: string): string { + const sum = ID_WEIGHTS.reduce((acc, w, i) => acc + w * Number(prefix17[i]), 0); + return prefix17 + ID_CHECK_CODES[sum % 11]; +} + +// 关键老人身份证(张三用任务给定值,李四与 staff 用校验位生成) +const ID_ZHANG = '530102199001011231'; +const ID_LI = idCard('53010219900303342'); + +// ---- 机构项目快照结构 ---- + +interface SvcRecord { + svcId: string; + svcName: string; + svcCode: string; + photoTier: string; + isMedical: boolean; + needCert: boolean; +} + +function itemSnapshot(s: SvcRecord): Record { + return { + svcId: s.svcId, + svcName: s.svcName, + svcCode: s.svcCode, + freqType: null, + freqValue: null, + durationMin: null, + photoTier: s.photoTier, + needCert: s.needCert, + isMedical: s.isMedical, + }; +} + +async function main(): Promise { + // 1. 机构(租户)+ 平台化初始化(admin01 + 资源 + 内置字典 + business roles 授权) + await prisma.tenant.upsert({ + where: { code: TENANT_CODE }, + update: { name: TENANT_NAME, status: 'enabled' }, + create: { code: TENANT_CODE, name: TENANT_NAME, status: 'enabled' }, + }); + await initTenant({ + prisma, + tenantCode: TENANT_CODE, + tenantName: TENANT_NAME, + adminRoleCode: TENANT_ADMIN_ROLE_CODE, + adminRoleName: '机构管理员', + adminUsername: ADMIN_USERNAME, + adminPassword: PASSWORD, + includePlatformPerms: false, + forcePasswordChange: false, + }); + + // 2. 角色账号 + 绑角色 + const passwordHash = await bcrypt.hash(PASSWORD, 10); + const userIds: Record = {}; + for (const acc of ACCOUNTS) { + const user = await prisma.user.upsert({ + where: { tenantCode_username: { tenantCode: TENANT_CODE, username: acc.username } }, + update: { nickname: acc.nickname, status: 'enabled' }, + create: { + tenantCode: TENANT_CODE, + username: acc.username, + passwordHash, + nickname: acc.nickname, + status: 'enabled', + orgCode: TENANT_CODE, + }, + }); + userIds[acc.username] = user.id; + await prisma.userRole.upsert({ + where: { tenantCode_userId_roleCode: { tenantCode: TENANT_CODE, userId: user.id, roleCode: acc.roleCode } }, + update: {}, + create: { tenantCode: TENANT_CODE, userId: user.id, roleCode: acc.roleCode }, + }); + } + + // 3. staff:1 组长(刘组长,绑 leader01)+ 3 护理员(张/李/王,张护理绑 nurse01) + const leaderId = IDS.staffLeader; + await prisma.staff.upsert({ + where: { id: leaderId }, + update: { + userId: userIds.leader01, + name: '刘组长', + idCardNo: idCard('53010219850101004'), + jobType: 'leader', + workType: 'fulltime', + employStatus: 'on_job', + groupLeaderId: null, + }, + create: { + id: leaderId, + tenantCode: TENANT_CODE, + userId: userIds.leader01, + name: '刘组长', + idCardNo: idCard('53010219850101004'), + phone: '13800000004', + jobType: 'leader', + workType: 'fulltime', + entryDate: dateOnly('2024-01-01'), + employStatus: 'on_job', + groupLeaderId: null, + }, + }); + + const nurseDefs = [ + { id: IDS.staffNurseZhang, userId: userIds.nurse01, name: '张护理', idPrefix: '53010219880515001', phone: '13800000001' }, + { id: IDS.staffNurseLi, userId: null, name: '李护理', idPrefix: '53010219920310002', phone: '13800000002' }, + { id: IDS.staffNurseWang, userId: null, name: '王护理', idPrefix: '53010219941025003', phone: '13800000003' }, + ]; + const nurseStaffIds: Record = {}; + for (const nd of nurseDefs) { + await prisma.staff.upsert({ + where: { id: nd.id }, + update: { + userId: nd.userId, + name: nd.name, + idCardNo: idCard(nd.idPrefix), + jobType: 'nurse', + workType: 'fulltime', + employStatus: 'on_job', + groupLeaderId: leaderId, + }, + create: { + id: nd.id, + tenantCode: TENANT_CODE, + userId: nd.userId, + name: nd.name, + idCardNo: idCard(nd.idPrefix), + phone: nd.phone, + jobType: 'nurse', + workType: 'fulltime', + entryDate: dateOnly('2024-06-01'), + employStatus: 'on_job', + groupLeaderId: leaderId, + }, + }); + nurseStaffIds[nd.name] = nd.id; + } + const zhangStaffId = nurseStaffIds['张护理']; + + // 4. L1 国家目录 + L4 机构项目库(srcId = svc_national.id,与 plan/record 解析逻辑一致) + const svcs: SvcRecord[] = []; + for (const svc of SERVICES) { + const national = await prisma.svcNational.upsert({ + where: { natCode: svc.natCode }, + update: { natName: svc.natName, natCategory: svc.natCategory, isMedical: svc.isMedical }, + create: { natCode: svc.natCode, natName: svc.natName, natCategory: svc.natCategory, isMedical: svc.isMedical }, + }); + const existing = await prisma.svcOrg.findFirst({ where: { tenantCode: TENANT_CODE, srcId: national.id } }); + let svcOrgId: string; + if (existing) { + await prisma.svcOrg.update({ + where: { id: existing.id }, + data: { srcType: 'national', isEnabled: true, price: svc.price, photoTier: svc.photoTier }, + }); + svcOrgId = existing.id; + } else { + const created = await prisma.svcOrg.create({ + data: { + tenantCode: TENANT_CODE, + srcType: 'national', + srcId: national.id, + isEnabled: true, + price: svc.price, + photoTier: svc.photoTier, + }, + }); + svcOrgId = created.id; + } + svcs.push({ + svcId: svcOrgId, + svcName: svc.natName, + svcCode: svc.natCode, + photoTier: svc.photoTier, + isMedical: svc.isMedical, + needCert: svc.isMedical, + }); + } + + // 5. 老人:张三(active)、李四(waiting_period,垫付演示) + const zhang = await prisma.elder.upsert({ + where: { tenantCode_idCardNo: { tenantCode: TENANT_CODE, idCardNo: ID_ZHANG } }, + update: {}, + create: { + tenantCode: TENANT_CODE, + locCode: LOC_CODE, + name: '张三', + gender: 'M', + birthDate: dateOnly('1990-01-01'), + idCardNo: ID_ZHANG, + phone: '13900000001', + address: '昆明市五华区春城路 1 号', + guardianName: '张小明', + guardianPhone: '13900000002', + insurNo: 'YN5301000001', + insurType: 'employee', + careLevel: 'care_1', + treatStatus: 'active', + benefitStart: dateOnly(daysFromToday(-365)), + benefitEnd: dateOnly(daysFromToday(365)), + bizLine: BIZ_LINE, + }, + }); + + await prisma.elder.upsert({ + where: { tenantCode_idCardNo: { tenantCode: TENANT_CODE, idCardNo: ID_LI } }, + update: {}, + create: { + tenantCode: TENANT_CODE, + locCode: LOC_CODE, + name: '李四', + gender: 'F', + birthDate: dateOnly('1990-03-03'), + idCardNo: ID_LI, + phone: '13900000003', + address: '昆明市盘龙区东风路 2 号', + insurNo: 'YN5301000002', + insurType: 'resident', + careLevel: 'care_2', + treatStatus: 'waiting_period', + benefitStart: dateOnly(daysFromToday(30)), + benefitEnd: dateOnly(daysFromToday(400)), + bizLine: BIZ_LINE, + }, + }); + + // 6. 计划(张三,active,覆盖本月)+ 计划项(勾选全部 10 项,photoTier 快照自 svc_org) + const planId = IDS.planZhang; + const planPeriodStart = `${new Date().getFullYear()}-${pad(new Date().getMonth() + 1)}-01`; + await prisma.plan.upsert({ + where: { id: planId }, + update: { + elderId: zhang.id, + planPeriodStart: dateOnly(planPeriodStart), + planPeriodEnd: dateOnly(lastDayOfMonth()), + careLevel: 'care_1', + careMode: 'home_individual', + itemCount: svcs.length, + planStatus: 'active', + }, + create: { + id: planId, + tenantCode: TENANT_CODE, + elderId: zhang.id, + planPeriodStart: dateOnly(planPeriodStart), + planPeriodEnd: dateOnly(lastDayOfMonth()), + careLevel: 'care_1', + careMode: 'home_individual', + itemCount: svcs.length, + planStatus: 'active', + bizLine: BIZ_LINE, + }, + }); + await prisma.planItem.deleteMany({ where: { planId } }); + for (const s of svcs) { + await prisma.planItem.create({ + data: { + planId, + tenantCode: TENANT_CODE, + svcId: s.svcId, + svcNameSnapshot: s.svcName, + svcCodeSnapshot: s.svcCode, + photoTier: s.photoTier, + isMedical: s.isMedical, + needCert: s.needCert, + }, + }); + } + + // 7. 工单(张三,今天 09:00-10:00,派给张护理,itemList 快照自 plan_item) + const orderId = IDS.orderZhang; + const itemList = svcs.map(itemSnapshot); + const needPhoto = svcs.some((s) => s.photoTier === 'must'); + await prisma.order.upsert({ + where: { id: orderId }, + update: { + elderId: zhang.id, + planId, + serviceDate: dateOnly(todayStr()), + timeSlotStart: todayAt(9, 0), + timeSlotEnd: todayAt(10, 0), + assigneeId: zhangStaffId, + orderStatus: 'assigned', + itemList, + needPhoto, + }, + create: { + id: orderId, + tenantCode: TENANT_CODE, + elderId: zhang.id, + planId, + serviceDate: dateOnly(todayStr()), + timeSlotStart: todayAt(9, 0), + timeSlotEnd: todayAt(10, 0), + assigneeId: zhangStaffId, + assignTime: new Date(), + assignMode: 'manual', + orderStatus: 'assigned', + itemList, + needPhoto, + billable: true, + bizLine: BIZ_LINE, + }, + }); + + // 8. 留痕(张三,submitted)+ 明细 + 照片 + const recordId = IDS.recordZhang; + await prisma.record.upsert({ + where: { id: recordId }, + update: { + orderId, + elderId: zhang.id, + assigneeId: zhangStaffId, + checkinTime: todayAt(9, 0), + checkoutTime: todayAt(10, 0), + durationMin: 60, + recordStatus: 'submitted', + }, + create: { + id: recordId, + tenantCode: TENANT_CODE, + orderId, + elderId: zhang.id, + assigneeId: zhangStaffId, + checkinTime: todayAt(9, 0), + checkoutTime: todayAt(10, 0), + durationMin: 60, + recordStatus: 'submitted', + submitTime: todayAt(10, 0), + isPrepaid: false, + billable: true, + bizLine: BIZ_LINE, + }, + }); + await prisma.recordItem.deleteMany({ where: { recordId } }); + for (const s of svcs) { + await prisma.recordItem.create({ + data: { + tenantCode: TENANT_CODE, + recordId, + svcId: s.svcId, + svcNameSnapshot: s.svcName, + execResult: 'done', + photoRequired: s.photoTier === 'must', + }, + }); + } + await prisma.recordPhoto.deleteMany({ where: { recordId } }); + const mustSvc = svcs.find((s) => s.photoTier === 'must')!; + await prisma.recordPhoto.create({ + data: { + tenantCode: TENANT_CODE, + recordId, + svcId: mustSvc.svcId, + filePath: '/demo/TY20261015/record/zhang/photo1.jpg', + fileSize: 128000, + visibleToFamily: false, + }, + }); + + // 9. 异常上报(压疮) + await prisma.recordAbnormal.upsert({ + where: { id: IDS.abnormalZhang }, + update: { + recordId, + elderId: zhang.id, + reporterId: zhangStaffId, + abnormalType: 'pressure_sore', + notifyStatus: 'pending', + handleStatus: 'open', + }, + create: { + id: IDS.abnormalZhang, + tenantCode: TENANT_CODE, + recordId, + orderId, + elderId: zhang.id, + reporterId: zhangStaffId, + abnormalType: 'pressure_sore', + abnormalDesc: '发现背部皮肤压红,需关注', + notifyStatus: 'pending', + handleStatus: 'open', + }, + }); + + // 10. 质控(pending) + await prisma.qc.upsert({ + where: { id: IDS.qcZhang }, + update: { recordId, qcType: 'normal', qcResult: 'pending' }, + create: { + id: IDS.qcZhang, + tenantCode: TENANT_CODE, + recordId, + qcType: 'normal', + qcResult: 'pending', + }, + }); + + console.log(`seed-demo 完成:机构 ${TENANT_CODE} ${TENANT_NAME}`); +} + +async function verify(): Promise { + const tenant = await prisma.tenant.findUnique({ where: { code: TENANT_CODE } }); + const users = await prisma.user.findMany({ where: { tenantCode: TENANT_CODE }, orderBy: { username: 'asc' } }); + const roles = await prisma.role.findMany({ where: { tenantCode: TENANT_CODE }, orderBy: { code: 'asc' } }); + const userRoles = await prisma.userRole.findMany({ where: { tenantCode: TENANT_CODE } }); + const staff = await prisma.staff.findMany({ where: { tenantCode: TENANT_CODE }, orderBy: { name: 'asc' } }); + const elders = await prisma.elder.findMany({ where: { tenantCode: TENANT_CODE } }); + const plans = await prisma.plan.findMany({ where: { tenantCode: TENANT_CODE } }); + const planItems = await prisma.planItem.findMany({ where: { tenantCode: TENANT_CODE } }); + const orders = await prisma.order.findMany({ where: { tenantCode: TENANT_CODE } }); + const records = await prisma.record.findMany({ where: { tenantCode: TENANT_CODE } }); + const recordItems = await prisma.recordItem.findMany({ where: { tenantCode: TENANT_CODE } }); + const recordPhotos = await prisma.recordPhoto.findMany({ where: { tenantCode: TENANT_CODE } }); + const abnormals = await prisma.recordAbnormal.findMany({ where: { tenantCode: TENANT_CODE } }); + const qcs = await prisma.qc.findMany({ where: { tenantCode: TENANT_CODE } }); + const svcOrgs = await prisma.svcOrg.findMany({ where: { tenantCode: TENANT_CODE } }); + + const roleByUser: Record = {}; + for (const ur of userRoles) { + (roleByUser[ur.userId] ??= []).push(ur.roleCode); + } + const nameByUser = new Map(users.map((u) => [u.id, u.username])); + + console.log('\n===== 验证结果 ====='); + console.log(`tenant: ${tenant ? '存在 ' + tenant.name : '缺失'} (code=${TENANT_CODE})`); + console.log(`账号数: ${users.length}(期望 5)`); + for (const u of users) { + console.log(` - ${u.username} → [${(roleByUser[u.id] ?? []).join(',')}]`); + } + console.log(`角色数: ${roles.length}(business roles: ${roles.map((r) => r.code).join('/')})`); + console.log(`staff: ${staff.length}(期望 4)→ ${staff.map((s) => `${s.name}(${s.jobType},${s.employStatus})`).join(' / ')}`); + console.log(`elder: ${elders.length}(期望 2)→ ${elders.map((e) => `${e.name}(${e.treatStatus},${e.careLevel})`).join(' / ')}`); + console.log(`svc_org(L4): ${svcOrgs.length}(期望 10)`); + console.log(`plan: ${plans.length}(期望 1); plan_item: ${planItems.length}(期望 10)`); + console.log(`order: ${orders.length}(期望 1); record: ${records.length}(期望 1); record_item: ${recordItems.length}(期望 10); record_photo: ${recordPhotos.length}(期望 1)`); + console.log(`record_abnormal: ${abnormals.length}(期望 1); qc: ${qcs.length}(期望 1)`); + console.log('\n===== 测试账号 ====='); + const expected = ['admin01', 'director01', 'qc01', 'leader01', 'nurse01']; + for (const uname of expected) { + const u = users.find((x) => x.username === uname); + console.log(` ${uname} / ${PASSWORD} → [${(u ? roleByUser[u.id] ?? [] : []).join(',')}]`); + } + // 未绑任何角色的多余账号告警 + const orphans = users.filter((u) => !expected.includes(u.username)); + if (orphans.length > 0) { + console.log(`\n⚠️ 存在非预期账号: ${orphans.map((u) => `${u.username}[${nameByUser.get(u.id)}]`).join(', ')}`); + } +} + +main() + .then(() => verify()) + .catch((e) => { + console.error(e); + process.exitCode = 1; + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/src/common/tenant-template.ts b/src/common/tenant-template.ts index 21858ea..8c579d0 100644 --- a/src/common/tenant-template.ts +++ b/src/common/tenant-template.ts @@ -145,7 +145,24 @@ export const BUTTONS = [ /** 业务租户预置角色(C0:nurse/leader/qc/admin/director)及初始授权。 */ export const BUSINESS_ROLES: { code: string; name: string; permissions: string[] }[] = [ - { code: 'director', name: '院长', permissions: [Permissions.agency.view] }, + { + code: 'director', + name: '院长', + permissions: [ + Permissions.agency.view, + Permissions.elder.list, + Permissions.elder.detail, + Permissions.plan.detail, + Permissions.order.detail, + Permissions.order.today, + Permissions.record.list, + Permissions.record.detail, + Permissions.abnormal.list, + Permissions.qc.pendingList, + Permissions.qc.detail, + Permissions.export.export, + ], + }, { code: 'admin', name: '机构管理员', @@ -169,21 +186,67 @@ export const BUSINESS_ROLES: { code: string; name: string; permissions: string[] Permissions.agency.view, Permissions.staff.list, Permissions.svcOrg.list, - Permissions.svcOrg.config, - Permissions.svcOrg.newNational, + Permissions.elder.list, + Permissions.elder.create, + Permissions.elder.detail, + Permissions.elder.statusChange, + Permissions.elder.svcSave, + Permissions.plan.create, + Permissions.plan.change, + Permissions.plan.detail, + Permissions.plan.confirm, + Permissions.order.detail, + Permissions.order.today, + Permissions.record.list, + Permissions.record.detail, + Permissions.abnormal.list, + Permissions.abnormal.report, + Permissions.abnormal.handle, Permissions.qc.pendingList, Permissions.qc.detail, Permissions.qc.pass, Permissions.qc.return, Permissions.qc.exceptionLink, Permissions.qc.batchPass, - Permissions.abnormal.list, - Permissions.abnormal.handle, Permissions.export.export, ], }, - { code: 'leader', name: '组长', permissions: [Permissions.staff.list, Permissions.svcOrg.list] }, - { code: 'nurse', name: '护理员', permissions: [] }, + { + code: 'leader', + name: '组长', + permissions: [ + Permissions.staff.list, + Permissions.svcOrg.list, + Permissions.elder.list, + Permissions.elder.detail, + Permissions.plan.detail, + Permissions.order.detail, + Permissions.order.today, + Permissions.order.assign, + Permissions.order.reassign, + Permissions.record.list, + Permissions.record.detail, + Permissions.abnormal.list, + Permissions.qc.pendingList, + Permissions.qc.detail, + ], + }, + { + code: 'nurse', + name: '护理员', + permissions: [ + Permissions.record.checkin, + Permissions.record.itemSave, + Permissions.record.photoUpload, + Permissions.record.checkout, + Permissions.record.offlineSync, + Permissions.record.detail, + Permissions.record.list, + Permissions.qc.correct, + Permissions.order.detail, + Permissions.order.today, + ], + }, ]; /** 内置枚举字典(值对齐 enums.ts,仅展示层 label/颜色/排序可改)。 */