# Proposal → Invoice Direct

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 107  
**Tier:** All tiers  
**Depends on:** `marketing-catalogs-campaigns`, `invoices-core`, `proposal-to-contract`, `invoice-settings-page`, `foundation-auth-rbac`  
**Referenced by:** `marketing-catalogs-campaigns`, `proposal-to-contract`, `invoices-core`

---

## Overview

Spec 47 (`proposal-to-contract`) notes that accepted proposals can optionally skip contract creation and go directly to an invoice. This spec defines the UI flow and API for converting an accepted proposal directly into an invoice — pre-populating invoice lines from proposal line items, preserving currency and customer context.

---

## When This Flow Applies

Available when:
- `proposals.status = 'ACCEPTED'`
- No invoice already exists for this proposal (no `invoices.proposal_id` match)

Shown as **[Create Invoice]** button on:
1. The proposal detail page (staff view)
2. The public proposal acceptance confirmation page (spec 51) — redirects to `/proposals/:id?accepted=1` which surfaces the button

---

## Proposal Detail: Invoice Action

`/proposals/:id` (staff view) — accepted state:

```
┌──────────────────────────────────────────────────────────────┐
│  ← Proposals                                                 │
│                                                              │
│  Website Redesign — Acme Corp        ✓ ACCEPTED              │
│                                                              │
│  Value: ₪18,000   Accepted: 2026-05-30                       │
│                                                              │
│  ─────────────────────────────────────────────────────────── │
│  Next step:                                                   │
│  [Create Contract]          [Create Invoice directly]         │
│                                                              │
│  "Create Invoice directly" skips contract — for short         │
│   engagements or trusted clients.                            │
└──────────────────────────────────────────────────────────────┘
```

**[Create Invoice directly]** → opens invoice preview modal pre-populated from proposal data.

---

## Invoice Preview Modal

```
┌──────────────────────────────────────────────────────────────┐
│  Create Invoice from Proposal                         [✕]    │
│                                                              │
│  Customer:  Acme Corp                                        │
│  Currency:  ILS                                              │
│                                                              │
│  Line items (from proposal):                                 │
│  ┌──────────────────────────────────────────────────────┐    │
│  │ Website Redesign — Phase 1    ₪12,000                 │    │
│  │ Content Migration              ₪3,500                 │    │
│  │ Training Session               ₪2,500                 │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  Subtotal: ₪18,000                                           │
│  Tax: 17%    Total: ₪21,060                                  │
│                                                              │
│  Issue date:  [2026-05-31]                                   │
│  Due date:    [2026-06-30]                                   │
│  Add note:    [___________________________________]           │
│                                                              │
│  [Cancel]                     [Create invoice]               │
└──────────────────────────────────────────────────────────────┘
```

Line items are pre-populated but editable before creation. Issue date defaults to today; due date defaults to `tenant_settings.default_payment_terms_days` from today.

---

## Line Item Mapping

Line items from `proposals.content` (pricing section — JSONB array of line objects) → `invoice_lines`:

| `proposals.content` key | Invoice field |
|-------------------------|---------------|
| `name` | `description` |
| `qty` | `quantity` |
| `unit_price` | `unit_price` |
| `discount_pct` | `discount_pct` |

Tax rate: pulled from `tenant_settings.default_tax_rate` (same as regular invoice creation).

After creation: `invoices.proposal_id = proposalId` set to prevent duplicate conversion.

---

## Post-Creation State

After invoice is created:
- Redirect to `/invoices/:invoiceId`
- Proposal detail shows: "Invoice created: INV-0042 [View →]" banner (reads `invoices WHERE proposal_id = :id`)

---

## API

```
POST /api/proposals/:id/invoice
     → create invoice from accepted proposal
       body: {
         issue_date: string,         // YYYY-MM-DD
         due_date: string,           // YYYY-MM-DD
         note?: string,
         lines: [{                   // pre-populated; client can edit
           description: string,
           quantity: number,
           unit_price: number,
           discount_pct?: number
         }]
       }
       Returns: { invoiceId, invoiceNumber }
       Requires: invoices:write

GET /api/proposals/:id/invoice
    → check if invoice already created (returns { invoiceId } or null)
      Requires: marketing:read
```

Server validates:
- `proposal.status = 'ACCEPTED'` (400 if not)
- No existing invoice for this proposal (409 if duplicate)
- `proposal.tenant_id = request.tenantId` (403 otherwise)

Invoice created in `DRAFT` status (staff can review before sending).

---

## Schema Delta

```sql
ALTER TABLE invoices ADD COLUMN proposal_id UUID REFERENCES proposals(id);
CREATE INDEX idx_invoices_proposal ON invoices(proposal_id) WHERE proposal_id IS NOT NULL;
-- Dedup guard: prevents two invoices being created from the same accepted proposal.

-- default_payment_terms_days and default_tax_rate already owned by spec 125
-- (invoice-settings-page); consumed here, NOT added. tenant_settings.default_tax_rate
-- pre-fills tax on new invoices and proposal→invoice conversion.
```

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Draft on creation | Not auto-send | Staff may want to add PO numbers, adjust tax, or review before sending; accepted proposal ≠ ready-to-send invoice |
| Lines editable in modal | Not locked from proposal | Proposals may have discount or structure staff wants to adjust for invoicing; proposal is the quote, invoice is the bill |
| `invoices.proposal_id` dedup guard | Server-side check | Prevents double-click or second tab creating two invoices for one proposal |
| Proposal status stays ACCEPTED | Not changed to INVOICED | Proposal lifecycle tracks customer acceptance; a separate invoice status tracks billing; mixing them creates confusion |
