Skip to content

Project Decision Records

This document captures key project-level decisions including roadmap priorities, feature decisions, and implementation strategies.

What is a PDR?

A Project Decision Record (PDR) documents significant project decisions that impact scope, timeline, priorities, or business direction. Unlike ADRs which focus on technical architecture, PDRs capture strategic and tactical project decisions.


PDR-001: FastAPI Backend Roadmap

Status

Accepted

Date

2025-12-24

Context

Need to define the implementation roadmap for migrating from direct Odoo access to FastAPI control plane architecture (see ADR-012, ADR-013, ADR-014).

Decision

Implement in 9-week phases with the following priority order:

Phase 1: FastAPI Core (Week 1-2)

  • Project structure with FastAPI
  • Database schema + Alembic migrations
  • Core infrastructure:
  • CORS, rate limiting, correlation IDs
  • Structured logging (structlog)
  • Health endpoints (/health, /health/ready, /health/live)
  • Odoo client with:
  • Connection pooling
  • Circuit breaker
  • Retry with exponential backoff
  • Basic tests

Phase 2: Auth Migration (Week 3)

  • JWT authentication endpoints
  • User model + password hashing
  • User migration script (Odoo → FastAPI)
  • Password reset flow with email
  • PWA login updates

Phase 3: FSM Job Endpoints (Week 4)

  • /jobs endpoints (tenant-scoped)
  • FSM order CRUD operations
  • Job completion workflow
  • PWA job module updates

Phase 4: CRM & Sales (Week 5)

  • /crm endpoints (leads, opportunities)
  • /sales endpoints (quotes, orders)
  • Landing page form integration

Phase 5: Portal & Tokens (Week 6)

  • Customer portal tokens
  • Scoped, expiring access
  • Self-service job status
  • Quote approval workflow

Phase 6: Email & SMS (Week 7)

  • Message outbox pattern
  • AWS SES integration
  • JustCall SMS integration
  • Background worker for delivery

Phase 7: Audit & Multi-Tenant (Week 8)

  • Complete audit logging
  • Tenant model + membership
  • OdooRouting table
  • TenantMiddleware
  • OdooProvisioner (auto DB creation)

Phase 8: Monitoring & Production (Week 9)

  • Prometheus metrics
  • Grafana dashboards
  • Documentation + runbooks
  • Production deployment
  • Load testing (optional)

Rationale

  • Auth first: Foundation for all other features
  • FSM next: Highest-value PWA feature
  • Portal later: Depends on auth + business endpoints
  • Multi-tenant last: Can retrofit after single-tenant works

Consequences

  • PWA changes spread across multiple phases
  • Need parallel development tracks
  • Some temporary direct Odoo access during migration

PDR-002: Multi-Tenant Pricing Strategy

Status

Proposed

Date

2025-12-24

Context

With multi-tenant SaaS architecture decided (ADR-013), need to define subscription plans and pricing.

Decision

Four-tier subscription model:

Plan Monthly Yearly Target
Free $0 $0 Trials, small ops
Starter $49 $470 Growing businesses
Professional $99 $950 Established ops
Enterprise $299 $2,870 Large operations

Feature Matrix

Feature Free Starter Pro Enterprise
Users 2 5 15 Unlimited
Technicians 1 3 10 Unlimited
Jobs/month 20 100 500 Unlimited
PDF Reports Yes Yes Yes Yes
Custom Branding No Yes Yes Yes
API Access No No Yes Yes
Advanced Reports No No Yes Yes
Multi-Location No No Yes Yes
Recurring Jobs No Yes Yes Yes
Inventory No No Yes Yes

Rationale

  • Free tier for acquisition and trials
  • Starter priced for small operations ($49 accessible)
  • Professional unlocks full features
  • Enterprise for high-volume customers

Consequences

  • Need Stripe integration
  • Feature flag system required
  • Limit enforcement in FastAPI
  • Upgrade prompts in PWA/Portal

PDR-003: Template Database Strategy

Status

Accepted

Date

2025-12-24

Context

For multi-tenant provisioning (ADR-014), need a strategy for creating new Odoo databases for tenants.

Decision

Use template database duplication approach:

  1. Maintain template_jdx database with:
  2. All required modules installed
  3. Default configuration
  4. Sample data removed
  5. Admin user for API access

  6. Provisioning flow:

    New tenant signup
    Duplicate template_jdx → tenant_<slug>
    Create service account (api-<slug>)
    Configure company name
    Store encrypted credentials
    

  7. Template updates:

  8. Test changes on template database
  9. Version template database in backups
  10. Document module list in template

Alternatives Considered

  • Fresh install per tenant: Too slow (5-10 min vs 30 sec)
  • Shared multi-company: Insufficient isolation
  • Pre-created pool: Complexity, resource waste

Rationale

  • Duplication is fast (~30 seconds)
  • Consistent configuration across tenants
  • Easy to update (change template, new tenants get updates)
  • Odoo-native operation

Consequences

  • Must maintain template database
  • Template updates don't affect existing tenants
  • Need process for applying updates to existing tenants
  • Storage for template backups

PDR-004: Frontend Migration to Next.js

Status

Accepted (Updated)

Date

2025-12-24

Context

The Flask-based PWA and Landing Page are being replaced with a unified Next.js frontend (see ADR-015). This PDR outlines the migration strategy from Flask to Next.js while integrating with the FastAPI control plane.

Decision

Complete frontend rewrite with Next.js, migrating in phases:

Phase 1: Next.js Foundation (Week 1)

  • Create nextjs-frontend/ project structure
  • Configure TypeScript, Tailwind CSS 4, ESLint
  • Setup Docker container for development and production
  • Configure NextAuth.js for JWT auth with FastAPI
  • Create shared API client for FastAPI communication
  • Basic layout components and routing structure

Phase 2: Landing Page Migration (Week 2)

  • Port marketing pages from Flask Jinja templates to React components
  • Implement contact/lead form with FastAPI integration
  • Setup SEO: metadata, sitemap.xml, robots.txt
  • Configure SSG for static pages, ISR for dynamic content
  • Add analytics and reCAPTCHA integration

Phase 3: Field PWA Core (Week 3-4)

  • Port job list and job detail views
  • Implement job completion flow with signature capture
  • Camera integration for photo uploads
  • Setup service worker with Serwist for offline support
  • Push notification configuration
  • Install prompt and PWA manifest

Phase 4: Customer Portal (Week 5)

  • Token-based quote viewing
  • Digital signature for quote approval
  • Job status tracking for customers
  • Email notification integration

Phase 5: Decommission Flask Apps (Week 6)

  • Redirect all routes to Next.js
  • Remove Flask PWA container
  • Remove Landing Page container
  • Update nginx configuration
  • Archive Flask codebases

Technical Implementation

// API Client pattern for FastAPI
import { createApiClient } from '@/lib/api';

const api = createApiClient({
  baseUrl: process.env.NEXT_PUBLIC_API_URL,
  getToken: () => getSession()?.accessToken,
});

// React Query for data fetching
const { data: jobs } = useQuery({
  queryKey: ['jobs'],
  queryFn: () => api.get('/jobs'),
});

Routing Strategy

Old Flask Route New Next.js Route Notes
Landing / / SSG
Landing /contact /contact With form
PWA /jobs /field/jobs Protected, JWT
PWA /jobs/<id> /field/jobs/[id] Protected
Portal /quote/<token> /portal/quote/[token] Token auth

Rollback Plan

  • Keep Flask containers available but inactive
  • Nginx can switch routing instantly
  • Feature flags in Next.js for partial rollback:
    if (flags.useOldJobsFlow) {
      redirect('/legacy/jobs');
    }
    

Rationale

  • Clean break: New codebase avoids legacy complexity
  • Modern stack: React 19, TypeScript, better DX
  • Unified frontend: One codebase instead of two Flask apps
  • Better offline: Next.js + Serwist for robust PWA
  • SEO optimized: SSG/SSR for marketing pages

Consequences

  • Parallel development during migration (Flask + Next.js)
  • Need to maintain Flask apps until migration complete
  • Learning curve for team new to React/Next.js
  • Better long-term maintainability
  • Single codebase reduces operational overhead

PDR-005: Documentation as Code

Status

Accepted

Date

2025-12-24

Context

FastAPI architecture introduces significant documentation needs. Need to ensure documentation stays current.

Decision

Documentation as Code approach:

  1. All docs in Git (docs/ folder)
  2. MkDocs Material for rendering
  3. PR reviews include docs
  4. Auto-generated API docs from FastAPI OpenAPI

Documentation Structure

docs/content/
├── development/
│   ├── adr.md              # Architecture decisions
│   ├── pdr.md              # Project decisions (this file)
│   └── fastapi/            # NEW: FastAPI-specific docs
│       ├── index.md
│       ├── authentication.md
│       ├── multi-tenancy.md
│       └── deployment.md
├── api/
│   ├── index.md            # Existing Odoo API
│   └── fastapi/            # NEW: FastAPI API docs
│       ├── index.md
│       ├── auth.md
│       ├── jobs.md
│       └── ...
└── operations/
    └── fastapi/            # NEW: FastAPI operations
        ├── deployment.md
        ├── monitoring.md
        └── troubleshooting.md

Rationale

  • Version control for docs
  • PR process ensures reviews
  • Same deployment as code
  • OpenAPI auto-generation reduces manual work

Consequences

  • Docs PRs alongside code PRs
  • Build docs in CI/CD
  • Need doc contribution guidelines
  • Review process may slow PRs

PDR-006: Multi-Tenant Frontend Architecture (v2)

Status

Proposed

Date

2025-12-24

Context

With multi-tenant SaaS (ADR-013) and Next.js frontend (ADR-015), each tenant company needs their own customer-facing website for: - Landing page with company branding - Contact/lead forms - Helpdesk ticket submission - Customer portal (quote viewing, job status)

Different tenant domains (e.g., jdx-austin.com, blinds-dallas.com) should all be served by a single Next.js application with tenant-specific branding and data isolation.

Decision

Implement v2: Multi-Tenant Frontend after v1 (single-tenant Next.js) is complete.

Architecture Overview

┌─────────────────────────────────────────────────────────────────────┐
│                         Tenant Domains                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌─────────────────┐   ┌─────────────────┐   ┌─────────────────┐  │
│   │ jdx-austin      │   │ blinds-dallas   │   │ shutters-houston│  │
│   │ .platform.com   │   │ .platform.com   │   │ .platform.com   │  │
│   └────────┬────────┘   └────────┬────────┘   └────────┬────────┘  │
│            │   (서브도메인)        │                     │           │
│            └──────────────────────┼─────────────────────┘           │
│                                   │                                 │
│   ┌─────────────────┐            │                                 │
│   │ www.jdx.com     │ ───────────┤  (커스텀 도메인 - Premium)       │
│   └─────────────────┘            │                                 │
│                                   ▼                                 │
│                    ┌──────────────────────────┐                    │
│                    │     Next.js Frontend     │                    │
│                    │  ┌────────────────────┐  │                    │
│                    │  │    Middleware      │  │                    │
│                    │  │ (도메인→테넌트 해석) │  │                    │
│                    │  └────────────────────┘  │                    │
│                    └────────────┬─────────────┘                    │
│                                 │                                   │
│                                 ▼                                   │
│                    ┌──────────────────────────┐                    │
│                    │   FastAPI Control Plane  │                    │
│                    │   - Tenant validation    │                    │
│                    │   - API routing          │                    │
│                    └────────────┬─────────────┘                    │
│                                 │                                   │
│           ┌─────────────────────┼─────────────────────┐            │
│           ▼                     ▼                     ▼            │
│   ┌──────────────┐     ┌──────────────┐     ┌──────────────┐      │
│   │ odoo_jdx     │     │ odoo_dallas  │     │ odoo_houston │      │
│   │ (Database)   │     │ (Database)   │     │ (Database)   │      │
│   └──────────────┘     └──────────────┘     └──────────────┘      │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Phase v2.1: Tenant Domain System (Week 1-2)

FastAPI Changes:

# New table: tenant_domains
class TenantDomain(Base):
    __tablename__ = "tenant_domains"

    id = Column(UUID, primary_key=True)
    tenant_id = Column(UUID, ForeignKey("tenants.id"))
    domain_type = Column(Enum("subdomain", "custom"))
    domain = Column(String(255), unique=True)  # "jdx-austin" or "www.jdx.com"
    is_primary = Column(Boolean, default=False)
    ssl_status = Column(Enum("pending", "active", "failed"))
    created_at = Column(DateTime)

# API endpoints
GET  /api/tenants/by-domain?domain=xxx
POST /api/tenants/{id}/domains
DELETE /api/tenants/{id}/domains/{domain_id}

Next.js Middleware:

// middleware.ts
export async function middleware(request: NextRequest) {
  const host = request.headers.get('host');

  // 1. Subdomain detection: xxx.platform.com
  // 2. Custom domain lookup: www.company.com → tenant
  // 3. Inject tenant context into request

  const tenant = await resolveTenant(host);
  if (!tenant) return NextResponse.redirect('/not-found');

  // Pass tenant to server components via headers
  const response = NextResponse.next();
  response.headers.set('x-tenant-id', tenant.id);
  response.headers.set('x-tenant-slug', tenant.slug);
  return response;
}

Phase v2.2: Tenant Branding System (Week 3)

Tenant Settings Table:

class TenantSettings(Base):
    __tablename__ = "tenant_settings"

    tenant_id = Column(UUID, ForeignKey("tenants.id"), primary_key=True)

    # Branding
    company_name = Column(String(100))
    logo_url = Column(String(255))
    favicon_url = Column(String(255))
    primary_color = Column(String(7))    # "#1e40af"
    secondary_color = Column(String(7))

    # Contact Info
    phone = Column(String(20))
    email = Column(String(100))
    address = Column(Text)

    # Social Links
    social_facebook = Column(String(255))
    social_instagram = Column(String(255))

    # SEO
    meta_title = Column(String(100))
    meta_description = Column(String(255))

    # Features
    helpdesk_enabled = Column(Boolean, default=True)
    portal_enabled = Column(Boolean, default=True)

Next.js Theme Provider:

// app/providers/TenantProvider.tsx
export function TenantProvider({ children, tenant }: Props) {
  return (
    <TenantContext.Provider value={tenant}>
      <style>{`
        :root {
          --color-primary: ${tenant.primaryColor};
          --color-secondary: ${tenant.secondaryColor};
        }
      `}</style>
      {children}
    </TenantContext.Provider>
  );
}

Phase v2.3: Custom Domain SSL (Week 4)

Cloudflare for SaaS Approach:

1. 고객이 커스텀 도메인 등록 요청
2. FastAPI가 Cloudflare API로 도메인 추가
3. 고객에게 CNAME 설정 안내:
   www.customer.com → tenants.platform.com
4. Cloudflare가 자동 SSL 발급
5. 도메인 상태 → "active"

Alternative: Let's Encrypt:

# Certbot integration for custom domains
async def provision_ssl(domain: str):
    # 1. Verify domain ownership (DNS challenge)
    # 2. Request certificate from Let's Encrypt
    # 3. Store certificate
    # 4. Update nginx config
    pass

Phase v2.4: Tenant-Scoped Forms (Week 5)

All forms automatically include tenant context:

// Contact form - automatically scoped to tenant
async function submitContactForm(data: ContactFormData) {
  const tenant = useTenant();

  await api.post('/leads', {
    ...data,
    tenant_id: tenant.id,  // 자동 주입
    source: 'website',
  });
}

// Helpdesk ticket - automatically routed to tenant's Odoo
async function submitTicket(data: TicketData) {
  const tenant = useTenant();

  await api.post('/helpdesk/tickets', {
    ...data,
    tenant_id: tenant.id,
  });
  // FastAPI routes to tenant's Odoo database
}

Phase v2.5: Admin Dashboard for Tenants (Week 6)

Tenant admin can configure their site:

/admin/settings
  ├── Branding (logo, colors, fonts)
  ├── Contact Info
  ├── Social Links
  ├── SEO Settings
  ├── Domain Management
  │   ├── Primary domain
  │   ├── Add custom domain
  │   └── SSL status
  └── Feature Toggles

Domain Strategy

Tier Domain Type SSL Example
Free Subdomain only Wildcard (*.platform.com) demo.jdxplatform.com
Starter Subdomain Wildcard company.jdxplatform.com
Pro Subdomain + 1 Custom Auto (Cloudflare/LE) www.mycompany.com
Enterprise Unlimited Custom Auto Multiple domains

API Design for Multi-Tenant

All API endpoints are tenant-scoped:

# Every endpoint gets tenant from middleware
@router.post("/leads")
async def create_lead(
    lead: LeadCreate,
    tenant: Tenant = Depends(get_current_tenant)
):
    # Automatically uses tenant's Odoo database
    odoo = await get_odoo_client(tenant)
    lead_id = await odoo.create("crm.lead", lead.dict())
    return {"id": lead_id}

@router.post("/helpdesk/tickets")
async def create_ticket(
    ticket: TicketCreate,
    tenant: Tenant = Depends(get_current_tenant)
):
    odoo = await get_odoo_client(tenant)
    ticket_id = await odoo.create("helpdesk.ticket", {
        **ticket.dict(),
        "team_id": tenant.settings.helpdesk_team_id,
    })
    return {"id": ticket_id}

Development Phases Summary

Phase Scope Depends On
v1 Single-tenant Next.js PDR-004 (Frontend Migration)
v2.1 Tenant domain system v1 complete, PDR-001 Phase 7
v2.2 Tenant branding v2.1
v2.3 Custom domain SSL v2.1
v2.4 Tenant-scoped forms v2.2
v2.5 Tenant admin dashboard v2.1-v2.4

Prerequisites (v1 Must Complete First)

Before v2 development: - [ ] PDR-004: Next.js frontend migration complete - [ ] PDR-001 Phase 7: Multi-tenant infrastructure in FastAPI - [ ] Tenant database and settings models - [ ] OdooRouting table functional

Alternatives Considered

Option Pros Cons
Separate app per tenant Full isolation Doesn't scale, maintenance nightmare
Single domain with path routing Simple Poor branding (company.com/jdx)
Subdomain only Easy SSL Customers want own domain
Subdomain + Custom (chosen) Flexible, scalable SSL complexity

Rationale

  • Subdomain as default: Easy, immediate, no SSL hassle
  • Custom domain as premium: Revenue opportunity, customer demand
  • Single codebase: Maintainability, consistent features
  • Cloudflare for SaaS: Industry standard for custom domain SSL

Consequences

  • Need Cloudflare for SaaS or Let's Encrypt automation
  • Middleware adds latency (tenant lookup per request)
  • Need tenant context in all components
  • Cache invalidation per tenant
  • More complex local development (multiple domains)

PDR Template

Use this template for new PDRs:

## PDR-XXX: Title

### Status
Proposed | Accepted | Deprecated | Superseded

### Date
YYYY-MM-DD

### Context
What is the business/project need driving this decision?

### Decision
What is the decision being made?

### Alternatives Considered
What other options were evaluated?

### Rationale
Why was this decision made over alternatives?

### Consequences
What are the implications of this decision?

PDR-007: Email Service Provider Strategy

Status

Accepted

Date

2025-12-28

Context

With the FastAPI control plane architecture (ADR-012) and multi-tenant SaaS model (ADR-013), need a professional email service strategy for: - Transactional emails (order confirmations, invoices, portal magic links) - Customer portal token-based access (no signup required) - Per-tenant email domains (each tenant sends from their own domain) - High deliverability for critical emails

Decision

Adopt Hybrid Email Architecture with:

Provider Role Use Cases
Postmark Pro Primary Magic links, order confirmations, invoices, password resets
AWS SES Secondary/Fallback Bulk emails, internal notifications, failover

Postmark Pro Plan Details

  • Cost: $16.50/month base (10K emails included)
  • Extra emails: $1.30 per 1,000
  • Signature domains: Up to 10 (one per tenant)
  • Features: Fastest delivery, 45-day logs, excellent deliverability

Per-Tenant Domain Configuration

Each tenant has their own verified sending domain in Postmark:

Tenant A: orders@blinds-pro.com.au
Tenant B: orders@sydney-shutters.com.au
Tenant C: orders@outdoor-blinds.com.au

All managed via single Postmark account with: - Automated domain provisioning via Postmark Domains API - Per-tenant DNS instructions (SPF, DKIM, DMARC) - Tenant-tagged emails for metrics isolation

Architecture

┌─────────────────────────────────────────────────────────────┐
│              HYBRID EMAIL ARCHITECTURE                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  POSTMARK PRO (Primary)                                     │
│  ├── Customer-facing emails                                 │
│  ├── Portal magic links (speed critical)                   │
│  ├── Order confirmations & invoices                        │
│  ├── Quote notifications                                    │
│  └── Per-tenant sending domains                            │
│                                                             │
│  AWS SES (Secondary)                                        │
│  ├── Internal team notifications                            │
│  ├── Bulk status updates                                    │
│  ├── System alerts                                          │
│  └── Automatic failover from Postmark                      │
│                                                             │
│  SHARED INFRASTRUCTURE                                      │
│  ├── Unified suppression list                              │
│  ├── Bounce/complaint webhook handlers                      │
│  └── Email queue with provider routing                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Alternatives Considered

Option Pros Cons
Postmark Only Simple, excellent deliverability No fallback, higher cost at scale
AWS SES Only Lowest cost, already integrated Complex setup, slower delivery, DIY deliverability
SendGrid Good feature set Deliverability issues on shared IPs
Hybrid (chosen) Best of both, failover capability Two providers to manage

Rationale

  1. Speed: Postmark has industry-leading delivery times (critical for magic links)
  2. Deliverability: Postmark's sole focus on transactional email = best inbox rates
  3. Cost efficiency: SES for bulk/internal keeps costs down
  4. Resilience: Automatic failover if primary fails
  5. Multi-tenant: Postmark Pro supports 10 signature domains (tenants)
  6. Existing infrastructure: SES already configured from Phase 6 (ADR-012)

Implementation

Phase 1: Postmark Setup

  • Create Postmark account
  • Configure primary sending domain (jdx.com)
  • Set up webhook endpoints for bounces/complaints
  • Add Postmark client to FastAPI

Phase 2: Per-Tenant Domains

  • Implement Postmark Domains API integration
  • Add tenant email settings to tenant.settings JSONB
  • Create admin endpoints for domain management
  • Automate DNS verification flow

Phase 3: Unified Email Service

  • Create UnifiedEmailService with provider routing
  • Implement suppression list (shared across providers)
  • Add priority-based routing (critical → Postmark, bulk → SES)
  • Automatic failover logic

Consequences

  • Additional monthly cost ($16.50+ for Postmark Pro)
  • Two providers to monitor and maintain
  • DNS configuration required per tenant
  • Better deliverability for customer-facing emails
  • Reduced risk with failover capability
  • Foundation for customer portal magic links
  • ADR-012: FastAPI Control Plane (email via FastAPI, not Odoo)
  • ADR-016: Hybrid Email Architecture (technical details)
  • PDR-001 Phase 6: Email & SMS integration

Decision Log

ID Title Status Date
PDR-001 FastAPI Backend Roadmap Accepted 2025-12-24
PDR-002 Multi-Tenant Pricing Strategy Proposed 2025-12-24
PDR-003 Template Database Strategy Accepted 2025-12-24
PDR-004 Frontend Migration to Next.js (v1) Accepted 2025-12-24
PDR-005 Documentation as Code Accepted 2025-12-24
PDR-006 Multi-Tenant Frontend Architecture (v2) Proposed 2025-12-24
PDR-007 Email Service Provider Strategy Accepted 2025-12-28