Feature Design Doc: Implementation Portal v1
Overview
A native Professional Services Automation (PSA) module for FlowPOS that enables any service business to manage client onboarding and project delivery via structured, phase-based implementation boards. Includes a public client-facing portal, billable step tracking, manual time entry, and invoice draft generation — all connected to the existing Stripe and SendGrid infrastructure.
Problem Statement
FlowPOS merchants who deliver services (IT consultancies, law firms, marketing agencies, architects, and FlowPOS's own onboarding team) lack a structured tool to:
- Define repeatable delivery templates by industry/vertical
- Share a client-facing view of project progress without requiring a FlowPOS login
- Track billable work at the task level and generate invoices from completed steps
- Create accountability through assignee roles, due dates, and notifications
The closest commercial tools (Rocketlane, Dock, Vitally) are standalone SaaS products. Building this natively inside FlowPOS makes it available to all tenants and positions FlowPOS as a platform beyond POS.
Target Users
- Internal implementers (FlowPOS team or merchant's operations staff): create boards, manage progress, log hours, issue invoices
- External clients (the merchant's own customers or the business being onboarded): view progress, complete assigned steps, upload documents — without a FlowPOS account
Data Model
GuideTemplate
Reusable template scoped to a business. Ships with starter templates per vertical.
GuideTemplate
id uuid PK
businessId uuid FK — tenant scope
name string — e.g. "Restaurant Onboarding", "Corporate Registration"
vertical enum: retail | restaurant | legal | accounting | agency | it | generic
description string?
isDefault boolean — starter templates shipped by FlowPOS
createdAt timestamp
updatedAt timestamp
Phase
Ordered phases within a template.
Phase
id uuid PK
templateId uuid FK → GuideTemplate
name string
order integer
description string?
Step
Individual work items within a phase.
Step
id uuid PK
phaseId uuid FK → Phase
name string
description string?
order integer
type enum: task | document | form | approval
assigneeRole enum: implementer | client
billableType enum: fixed | hourly | milestone | non_billable
fixedAmount decimal? — used when billableType = fixed
hourlyRate decimal? — used when billableType = hourly
isVisibleToClient boolean — controls whether amount shows on client portal
dueOffsetDays integer? — relative due date offset from board start date
ImplementationBoard
One board per client engagement, derived from a template.
ImplementationBoard
id uuid PK
businessId uuid FK — tenant scope
templateId uuid FK → GuideTemplate (snapshot reference)
clientName string
clientEmail string?
clientLogoUrl string?
startDate date?
shareToken string UNIQUE — public read-only access token (UUID v4)
status enum: draft | active | completed | archived
internalNotes string?
createdBy uuid FK → User
createdAt timestamp
updatedAt timestamp
BoardPhase
Phase instance on a specific board (copied from template at board creation).
BoardPhase
id uuid PK
boardId uuid FK → ImplementationBoard
name string
order integer
targetDate date?
status enum: pending | in_progress | completed
completedAt timestamp?
BoardStep
Step instance on a specific board.
BoardStep
id uuid PK
boardPhaseId uuid FK → BoardPhase
name string
description string?
order integer
type enum: task | document | form | approval
assigneeRole enum: implementer | client
billableType enum: fixed | hourly | milestone | non_billable
fixedAmount decimal?
hourlyRate decimal?
isVisibleToClient boolean
dueDate date?
status enum: pending | in_progress | completed | approved | rejected
completedAt timestamp?
completedBy uuid FK → User?
TimeEntry
Manual hour log against hourly-type steps.
TimeEntry
id uuid PK
boardStepId uuid FK → BoardStep
userId uuid FK → User
hours decimal — e.g. 1.5
description string?
loggedAt date
createdAt timestamp
StepAttachment
File uploads on document-type steps, stored in GCS.
StepAttachment
id uuid PK
boardStepId uuid FK → BoardStep
uploadedBy uuid FK → User? — null if uploaded by client via shareToken
fileName string
gcsKey string
mimeType string
sizeBytes integer
createdAt timestamp
StepComment
Per-step communication with internal/client visibility control.
StepComment
id uuid PK
boardStepId uuid FK → BoardStep
authorId uuid FK → User? — null if posted by client via shareToken
authorName string — denormalized for client comments
body string
visibility enum: internal | client
createdAt timestamp
InvoiceDraft
Auto-generated invoice from completed billable steps.
InvoiceDraft
id uuid PK
boardId uuid FK → ImplementationBoard
businessId uuid FK
status enum: draft | sent | paid | void
lineItems jsonb — snapshot of billable steps at generation time
totalAmount decimal
currency string — default: businessId currency (GTQ, USD, etc.)
generatedAt timestamp
stripeInvoiceId string? — populated when pushed to Stripe
Feature Scope
1. GuideTemplate Management (internal)
- CRUD for templates scoped to
businessId - CRUD for phases and steps within a template
- Seed 4 starter templates at tenant creation:
- FlowPOS Retail Onboarding (8 phases)
- FlowPOS Restaurant Onboarding (8 phases)
- Legal Matter (generic law firm template, 4 phases)
- IT Project Delivery (agency/consultancy, 5 phases)
- Templates can be cloned and customized per tenant
2. ImplementationBoard Management (internal)
- Create board from a template: copies all phases + steps into
BoardPhase/BoardStepinstances - Set
clientName,clientEmail,startDate,internalNotes - Due dates auto-calculated from
startDate + step.dueOffsetDaysat creation time - Board status transitions:
draft → active → completed → archived - Implementer dashboard: list all active boards with health indicators
- Green: no overdue steps
- Yellow: 1–2 overdue steps
- Red: 3+ overdue steps or no activity in 7 days
3. Step Execution (internal)
- Mark steps complete, in-progress, or rejected
- For
approvalsteps: separate approve/reject action with optional comment - Manual time entry on
hourlysteps:hours+description+loggedAt - File upload on
documentsteps: signed GCS URL flow - Per-step comments: visibility toggle (internal vs. client-visible)
- Inline due date editing per step
4. Public Client Portal (shareToken)
Accessible via /portal/:shareToken — no FlowPOS login required.
Displays:
- Business name + logo (from board config)
- Overall progress bar (% of steps completed)
- Phase-by-phase breakdown with status
- "What's next" card: next open step assigned to client
- Per-step detail: description, due date, status, client-visible comments
- Client can:
- Mark their assigned
tasksteps complete - Upload files on
documentsteps - Post comments (stored with
visibility: client,authorNamefrom a prompt)
- Mark their assigned
- Billable steps show amount only if
isVisibleToClient = true
Portal is read-only for implementer-assigned steps from the client's perspective.
5. Invoice Draft Generation
- "Generate Invoice" action on any active or completed board
- Collects all
BoardStepwhere:billableType != non_billablestatus = completed OR approved- Not already included in a previous
InvoiceDraft
- Line item calculation:
fixed: usesfixedAmounthourly: sums allTimeEntry.hours×hourlyRatemilestone: fixed amount, triggered only when a designated approval step is approved
- Produces
InvoiceDraftwithlineItemsjsonb snapshot +totalAmount - Draft can be reviewed, edited (line items only), then pushed to Stripe as a Stripe Invoice
- Status tracked:
draft → sent → paid → void
6. Email Notifications (SendGrid)
Events that trigger email:
| Event | Recipient |
|---|---|
| Board activated | Client (welcome email with portal link) |
| Step assigned to client is completed by implementer | Client (next action available) |
| Step assigned to implementer is completed by client | Implementer |
| Step is overdue by 1 day | Assignee |
| Phase completed | Both parties |
| Board completed | Both parties |
| Invoice draft sent | Client |
All emails include the portal link. Templated in SendGrid, i18n-ready (es/en).
API Endpoints (NestJS)
Templates
GET /businesses/:businessId/guide-templates
POST /businesses/:businessId/guide-templates
GET /businesses/:businessId/guide-templates/:id
PUT /businesses/:businessId/guide-templates/:id
DELETE /businesses/:businessId/guide-templates/:id
POST /guide-templates/:id/phases
PUT /guide-templates/:id/phases/:phaseId
DELETE /guide-templates/:id/phases/:phaseId
POST /guide-templates/:id/phases/:phaseId/steps
PUT /guide-templates/:id/phases/:phaseId/steps/:stepId
DELETE /guide-templates/:id/phases/:phaseId/steps/:stepId
Boards
GET /businesses/:businessId/implementation-boards
POST /businesses/:businessId/implementation-boards
GET /businesses/:businessId/implementation-boards/:id
PUT /businesses/:businessId/implementation-boards/:id
DELETE /businesses/:businessId/implementation-boards/:id
PATCH /implementation-boards/:id/status
Steps
PATCH /board-steps/:stepId/status
POST /board-steps/:stepId/time-entries
GET /board-steps/:stepId/time-entries
POST /board-steps/:stepId/attachments
GET /board-steps/:stepId/attachments
POST /board-steps/:stepId/comments
GET /board-steps/:stepId/comments
Client Portal (public, no auth)
GET /portal/:shareToken
PATCH /portal/:shareToken/steps/:stepId/complete
POST /portal/:shareToken/steps/:stepId/attachments
POST /portal/:shareToken/steps/:stepId/comments
Invoices
POST /implementation-boards/:boardId/invoice-drafts
GET /implementation-boards/:boardId/invoice-drafts
PUT /invoice-drafts/:id
POST /invoice-drafts/:id/send — push to Stripe + email client
Hexagonal Architecture Layout
modules/
implementation-portal/
domain/
entities/
guide-template.entity.ts
phase.entity.ts
step.entity.ts
implementation-board.entity.ts
board-phase.entity.ts
board-step.entity.ts
time-entry.entity.ts
step-attachment.entity.ts
step-comment.entity.ts
invoice-draft.entity.ts
value-objects/
step-type.vo.ts
billable-type.vo.ts
assignee-role.vo.ts
ports/
guide-template.repository.port.ts
implementation-board.repository.port.ts
board-step.repository.port.ts
invoice-draft.repository.port.ts
storage.port.ts — GCS upload/signed URL
notification.port.ts — SendGrid events
payment.port.ts — Stripe Invoice push
services/
board-factory.service.ts — creates BoardPhase/BoardStep from template
invoice-calculator.service.ts — computes line items from completed steps
overdue-checker.service.ts — domain logic for health status
application/
use-cases/
create-guide-template.use-case.ts
create-implementation-board.use-case.ts
complete-board-step.use-case.ts
log-time-entry.use-case.ts
upload-step-attachment.use-case.ts
post-step-comment.use-case.ts
generate-invoice-draft.use-case.ts
send-invoice-draft.use-case.ts
get-client-portal.use-case.ts
complete-client-step.use-case.ts
infrastructure/
persistence/
kysely/
guide-template.repository.ts
implementation-board.repository.ts
board-step.repository.ts
invoice-draft.repository.ts
storage/
gcs-storage.adapter.ts
notifications/
sendgrid-notification.adapter.ts
payment/
stripe-invoice.adapter.ts
interfaces/
http/
guide-template.controller.ts
implementation-board.controller.ts
board-step.controller.ts
client-portal.controller.ts
invoice-draft.controller.ts
dto/
(request/response DTOs per endpoint)
Multi-Tenancy
- All templates and boards are scoped by
businessId - Starter templates are seeded as
isDefault: trueand copied per tenant at provisioning shareTokenURLs are tenant-agnostic — the token itself identifies the board- Client portal does NOT expose
businessIdin the URL
i18n
- All UI strings in
es(primary) anden - Step type labels, phase status labels, and notification emails localized
- Locale determined by board's
businessIdlocale setting - Client portal defaults to
es, respects browserAccept-Languageas fallback
Storage (GCS)
- Step attachments stored under:
gs://{bucket}/portal/{boardId}/steps/{stepId}/{filename} - Signed URLs generated server-side (15-minute expiry for client portal uploads)
- Max file size: 10MB per attachment
- Allowed MIME types: pdf, png, jpg, docx, xlsx
Implementation Ordering (for AI coding assistants)
Follow this sequence to avoid dependency issues:
- Database migrations — all tables in order:
guide_template,phase,step,implementation_board,board_phase,board_step,time_entry,step_attachment,step_comment,invoice_draft - Domain entities + value objects — pure TypeScript, no framework
- Repository ports — interfaces only
- Kysely repository adapters — implement ports
- Domain services —
BoardFactory,InvoiceCalculator,OverdueChecker - Use cases (application layer) — one at a time, in order listed above
- Storage adapter (GCS signed URL)
- Notification adapter (SendGrid templates)
- NestJS module wiring — providers, exports, imports
- Controllers + DTOs — internal endpoints first
- Client portal controller — public routes,
shareTokenguard - Invoice draft → Stripe adapter
- React PWA — Template management UI
- React PWA — Board management + implementer dashboard
- React PWA — Step execution UI (complete, log hours, upload, comment)
- React PWA — Invoice draft review + send UI
- Public client portal page (Next.js or standalone React route under
/portal/:shareToken) - Overdue notification cron job (Cloud Scheduler → Cloud Run job)
- Seed starter templates (migration seed or admin script)
- Import Center adapters —
GuideTemplateImportAdapter,BoardImportAdapter,TimeEntryImportAdapter - Import Center UI — register new import types with upload → validate → preview → confirm flow
- Unit tests — domain layer — entities, value objects,
BoardFactory,InvoiceCalculator,OverdueChecker - Unit tests — application layer — all use cases with mocked ports
- Unit tests — infrastructure adapters — repositories, GCS, SendGrid, Stripe, import adapters
- Integration/e2e tests — controllers — all endpoints via supertest
- Unit tests — React PWA components and hooks
- Unit tests — client portal components
- Bruno API collection for all endpoints
- Swagger documentation on all controllers
Data Migration (Import Center)
The Implementation Portal integrates with the existing FlowPOS Import Center so merchants can migrate existing project data, client lists, or template structures into the module rather than starting from scratch.
Supported Import Types
GuideTemplate import
- Source format: CSV or JSON
- Columns/fields:
templateName,vertical,phaseName,phaseOrder,stepName,stepOrder,stepType,assigneeRole,billableType,fixedAmount,hourlyRate,dueOffsetDays - Behavior: creates a new
GuideTemplatewith nestedPhaseandSteprecords scoped to the tenant'sbusinessId - Use case: teams who already have their delivery process documented in a spreadsheet
ImplementationBoard import
- Source format: CSV
- Columns:
clientName,clientEmail,templateName,startDate,status - Behavior: creates boards in
draftstatus from the matching template; steps are instantiated but not completed - Use case: migrating active client projects from a previous tool (spreadsheet, Trello, Asana, etc.)
TimeEntry import
- Source format: CSV
- Columns:
boardName,stepName,hours,description,loggedAt,userEmail - Behavior: appends historical time entries to matched
BoardSteprecords - Use case: teams moving from a separate time tracking tool who want billing history intact
Import Center Integration Points
- New import types registered in the Import Center module under category:
implementation-portal - Each import type follows the existing Import Center pattern: upload file → validate → preview → confirm → process
- Validation errors reported per row (e.g. unknown
templateName, invalidstepTypevalue, missing required fields) - Import runs async via existing job queue; status tracked in Import Center UI
- Partial imports allowed: valid rows are processed, invalid rows reported in the error summary
Import Adapter (Hexagonal)
infrastructure/
import/
guide-template-import.adapter.ts — implements ImportPort<GuideTemplateRow>
board-import.adapter.ts — implements ImportPort<BoardRow>
time-entry-import.adapter.ts — implements ImportPort<TimeEntryRow>
Each adapter maps raw CSV/JSON rows to domain commands and delegates to the corresponding use case.
Testing Requirements
Unit tests are required for all new code in this module. Tests must be written alongside implementation, not after.
Backend (NestJS — Jest)
Domain layer — 100% coverage target
- All domain entities: construction, state transitions, invariant enforcement
- All value objects: valid/invalid input, equality
BoardFactory: verify correctBoardPhase/BoardStepinstances are created from a template, due dates calculated correctly fromstartDate + dueOffsetDaysInvoiceCalculator: fixed amount, hourly (hours × rate), milestone gating, non-billable exclusion, multi-step aggregation, already-invoiced step exclusionOverdueChecker: health status logic (green/yellow/red thresholds)
Application layer — use case tests
- Each use case tested in isolation with mocked ports (repository, storage, notification, payment)
- Key scenarios per use case:
- Happy path
- Entity not found (throws expected domain error)
- Authorization failure (wrong
businessId) - Invalid state transition (e.g. completing an already-completed step)
GetClientPortal: validshareToken, expired/invalid token, board indraftstatus (should not be publicly accessible)GenerateInvoiceDraft: no billable steps, partial billable steps, all steps billable, steps already included in prior draft excluded
Infrastructure layer — adapter tests
- Repository adapters: tested against an in-memory or test database (not mocked) to verify query correctness
GcsStorageAdapter: mock GCS client, verify signed URL generation and key constructionSendgridNotificationAdapter: mock SendGrid client, verify correct template IDs and recipient resolution per event typeStripeInvoiceAdapter: mock Stripe client, verify line item mapping and invoice creation call- Import adapters: valid CSV rows, malformed rows, missing required fields, unknown reference values
Controller layer — e2e/integration tests
- All endpoints tested via NestJS
supertestwith seeded test data - Auth guard behavior (protected vs. public
shareTokenroutes) - Request validation (invalid DTOs return 400)
- Correct HTTP status codes per scenario
Frontend PWA (React — Vitest + React Testing Library)
Unit tests per component/hook
useImplementationBoard: loading, error, and success states; step completion mutationuseTimeEntry: hour logging, validation (negative hours, zero hours rejected)useInvoiceDraft: generation, line item display, send actionGuideTemplateForm: field validation, phase/step add-remove interactionsBoardStepCard: renders correct UI per step type and status; billable amount visibility toggleStepCommentThread: internal vs. client-visible rendering; post comment actionImplementerDashboard: health status badge rendering (green/yellow/red); board list filtering
Client portal components (public route)
ClientPortalView: renders progress bar, phases, and steps correctly from mock board dataClientStepAction: correct action per step type (checkbox for task, upload for document, approve/reject for approval)ClientCommentBox: posts comment with author name prompt; internal comments not rendered
Import Center UI
GuideTemplateImportFlow: file upload step, validation error display, confirm stepBoardImportFlow: same pattern
Testing Conventions
- Test files colocated:
*.spec.ts(backend),*.test.tsx(frontend) - No test depends on external network calls — all adapters mocked at the port boundary
- Factories/fixtures provided for each domain entity to keep test setup concise
- CI enforces: all tests pass before merge, coverage thresholds enforced per layer (domain: 100%, application: 90%, infrastructure: 80%, UI components: 80%)
Out of Scope for v1
- Start/stop timer (manual hour entry only — timer in v2)
- Customer accounts / client login (shareToken is sufficient for v1)
- Recurring boards or board templates with variable pricing tiers
- Multi-currency per board (uses business default currency)
- Delivery tracking or GPS
- Native mobile app for client portal
- Automated Stripe subscription billing (invoice-based only in v1)
Speckit Command
/speckit.specify A Professional Services Automation (PSA) module for FlowPOS called Implementation Portal. Enables any service-based FlowPOS tenant (or FlowPOS itself) to manage client project delivery via structured GuideTemplates (phases + steps), ImplementationBoards (one per client engagement), per-step billable tracking (fixed | hourly | milestone | non-billable), manual time entry on hourly steps, auto-generated InvoiceDrafts pushed to Stripe, per-step file attachments (GCS), per-step comments with internal/client visibility, email notifications via SendGrid for key events, and a public client-facing portal accessible via shareToken (no login required). Integrates with the existing FlowPOS Import Center to support data migration of templates, boards, and time entries from CSV/JSON. Follows hexagonal architecture with businessId multi-tenant scoping. Ships with 4 starter GuideTemplates. Unit tests required across all layers: domain entities and services (100% coverage), application use cases (90%), infrastructure adapters (80%), and React PWA components and hooks (80%). React PWA covers template management, board management, step execution, time logging, and invoice drafting. A public Next.js or React route serves the client portal at /portal/:shareToken.