文件
hyb-oa/.trellis/spec/hyb-oa/backend-fastify-prisma.md
T

4.3 KiB

Backend Fastify / Prisma

Scope

Server work lives under server/. Prefer existing route, service, utility and QA script patterns over new abstractions.

Rules

  • Validate Prisma changes with cd server && npm run prisma:validate.
  • For route/service edits, run node --check on the touched files and the focused regression listed in the current Trellis task.
  • Prisma migrations require rollback notes in the Trellis task design or PRD.
  • Do not resolve migration drift, reset databases, or rewrite migration history without explicit approval.
  • Workflow, approval and permission behavior must be verified at process level, not only syntax level.

Common Checks

  • cd server && node --check src/routes/reimbursements.js
  • cd server && node --check src/routes/workflow.js
  • cd server && ./scripts/regression-workflow-actions.sh
  • cd server && ./scripts/regression-all.sh

Scenario: Portal Announcement Acknowledgement

1. Scope / Trigger

  • Trigger: extending announcements_v1 from read-only announcements into formal employee acknowledgement.
  • Applies to server/prisma/schema.prisma, server/src/routes/announcements.js, client/src/api/announcements.ts, employee /announcements, and admin /system/announcements.
  • Use this only for lightweight policy acknowledgement; do not treat it as electronic signature, CA signing, contract signing, or full policy CMS.

2. Signatures

  • DB: PortalAnnouncement may carry category, requires_acknowledgement, acknowledgement_statement, acknowledgement_due_at.
  • DB: PortalAnnouncementAcknowledgement stores one receipt per (announcement_id, employee_id) with statement/title/summary snapshots and actor_id.
  • API: GET /api/announcements?manage=0 returns visible announcements and the current employee's acknowledgement status.
  • API: GET /api/announcements?manage=1 requires system.settings and returns acknowledgement summary plus minimal pending employees.
  • API: POST /api/announcements/:id/acknowledge accepts { "confirmed": true }.

3. Contracts

  • Required acknowledgement status values: not_required, pending, acknowledged, overdue.
  • Employee response must not include other employees' acknowledgement rows or pending lists.
  • Admin pending list may include only id, employeeNo, name, and status; do not include phone, email, WeCom userid, account identifiers, or sensitive profile fields.
  • Acknowledgement is explicit only: opening an announcement or clicking its link must never create a receipt.
  • Policy content changes after any acknowledgement require a new announcement version; do not mutate title, summary, category, required flag, or statement in place.

4. Validation & Error Matrix

  • announcements_v1 disabled -> 409 VALID_001.
  • policy acknowledgement without statement -> 400 VALID_001.
  • manage=1 without system.settings -> 403 PERM_001.
  • acknowledge invisible/missing announcement -> 404 ANN_404.
  • acknowledge normal announcement -> 409 VALID_001.
  • acknowledge without { confirmed: true } -> 400 VALID_001.
  • edit protected policy fields after acknowledgement exists -> 409 VALID_001.

5. Good/Base/Bad Cases

  • Good: admin creates a policy announcement, employee checks the confirmation box, server creates one receipt and one audit log.
  • Base: normal announcement remains compatible and returns not_required.
  • Bad: frontend displays another employee's receipt status on /announcements.
  • Bad: admin updates an acknowledged policy title instead of creating a new version.

6. Tests Required

  • Prisma validate after schema/migration changes.
  • Route focused tests for required statement, permission, idempotency, normal/invisible announcement rejection, admin summary, and protected edit rejection.
  • Frontend typecheck and build.
  • Playwright flow for admin create -> employee acknowledge -> admin summary update when UI changes.

7. Wrong vs Correct

Wrong

await prisma.portalAnnouncement.update({
  where: { id },
  data: { title: "制度 V2" },
});

Correct

const acknowledgementCount = await prisma.portalAnnouncementAcknowledgement.count({
  where: { announcement_id: id },
});
if (acknowledgementCount > 0) {
  throw new AppError({ code: "VALID_001", message: "参数校验失败", statusCode: 409 });
}