diff --git a/.trellis/spec/backend/drizzle-postgres-current.md b/.trellis/spec/backend/drizzle-postgres-current.md index be1a421..d93eea9 100644 --- a/.trellis/spec/backend/drizzle-postgres-current.md +++ b/.trellis/spec/backend/drizzle-postgres-current.md @@ -110,3 +110,73 @@ await database.transaction(async (transaction) => { await transaction.update(elders).set({ status: "active" }).where(eq(elders.id, elderId)); }); ``` + +## Scenario: Facility Room Creation Without Fabricated Hierarchy + +### 1. Scope / Trigger + +- Trigger: changing facility room creation APIs or any UI that creates rooms. +- Applies because `rooms.floorId` is required by schema, but the system must not create + fake campus/building/floor records to satisfy that foreign key. + +### 2. Signatures + +- `POST /api/facilities/rooms` +- Request: `{ name: string; code: string; floorId: string; capacity?: number }` +- Response: `ApiResult<{ room: typeof rooms.$inferSelect }>` + +### 3. Contracts + +- `floorId` must refer to an existing `floors.id` in the authenticated active organization. +- `capacity` is optional; omitted means schema/product default `1`. +- If `capacity` is supplied, it must be a positive integer. +- The handler may insert only the `rooms` row. It must not insert `campuses`, + `buildings`, or `floors` as placeholder hierarchy. + +### 4. Validation & Error Matrix + +- Missing active organization -> `400` / `请选择机构后维护房间`. +- Missing `name`, `code`, or `floorId` -> `400` / `房间名称、编号和楼层不能为空`. +- Invalid supplied `capacity` -> `400` / `房间容量需为正整数`. +- `floorId` not found in active organization -> `404` / `楼层不存在`. +- Insert returning no row -> `500` / `房间创建失败`. + +### 5. Good/Base/Bad Cases + +- Good: create UI first exposes real campus/building/floor selection, then submits the selected `floorId`. +- Base: if facility hierarchy management is not exposed yet, keep room creation UI hidden or disabled. +- Bad: auto-create `"默认院区"`, `"默认楼栋"`, or `"默认楼层"` inside the room API. + +### 6. Tests Required + +- `pnpm lint` +- `pnpm type-check` +- `pnpm build` +- Integration assertions: + - missing `floorId` returns `400` + - unknown or cross-organization `floorId` returns `404` + - valid `floorId` creates a room without inserting campus/building/floor rows + +### 7. Wrong vs Correct + +#### Wrong + +```ts +const floor = existingFloor ?? await transaction.insert(floors).values({ name: "默认楼层" }).returning(); +await transaction.insert(rooms).values({ floorId: floor.id, name, code }); +``` + +#### Correct + +```ts +const floorRows = await database + .select({ id: floors.id }) + .from(floors) + .where(and(eq(floors.id, floorId), eq(floors.organizationId, organizationId))) + .limit(1); +const floor = floorRows[0]; +if (!floor) { + return jsonFailure("楼层不存在", 404); +} +await database.insert(rooms).values({ organizationId, floorId: floor.id, name, code }); +```