Skip to main content

Decision log

Key decisions in depth

Staff-only portal

Decision: Employees have no login accounts. All ordering is done on their behalf by staff. Rationale: Matches the operational model; eliminates employee self-service and simplifies authorization. Impact: No employee-facing UI or authentication. A legacy Role.EMPLOYEE code path remains as documented technical debt pending removal.

Entitlement logic as pure functions

Decision: Criteria matching and phase calculation live in pure functions with no Prisma or NestJS dependencies. Rationale: The most complex business logic becomes independently testable and reusable by the reporting service. Impact: The engine can be tested without a database and shared between order flow and reports.

Scope guards for multi-tenancy

Decision: Data isolation is enforced by application-layer guards and service-level filtering, not database RLS. Rationale: Faster to build, easier to reason about, sufficient for the current trust model. Impact: A service-layer bug could theoretically leak cross-client data. RLS is a planned defense-in-depth measure.

Linear order lifecycle

Decision: Orders follow a forward-only pipeline with server-enforced transitions. Rationale: Matches the physical fulfilment process and is simple to reason about. Impact: No partial approvals, split shipments, or parallel stages.

In-app plus email notifications

Decision: Notifications are stored in-app (bell + notifications page) and sent by email, using Brevo (production) or SMTP (development). Rationale: In-app records give every user a visible activity feed; email covers offline attention. Impact: No push or SMS channels. A WhatsApp channel value is reserved in the schema but has no gateway yet.

Anti-patterns to avoid