| Quote # | TextColumn::url() | string | stored: quotes.quote_no — Q-{yyyy}-{seq} series (admin-settings numbering) | — |
| Customer | TextColumn::url() | FK | stored: quotes.customer_id (nullable for walk-ins pre-conversion) | customers.id |
| Customer ref | TextInput | string, nullable | stored: quotes.customer_ref — printed on the quote PDF | — |
| Requested delivery date | DatePicker | date, nullable | stored: quotes.requested_delivery_date — engine flags when MO-schedule lead time exceeds it | — |
| Currency | Select + snapshot | string + decimal | stored: quotes.currency (default base MYR) + quotes.exchange_rate — rate snapshotted at quote time; floor-price comparison always happens in base MYR | — |
| Product | TextColumn::url() | FK — one of two | stored: quote_lines.variant_id (nullable) OR quote_lines.formulation_version_id (nullable) — exactly one set per line; custom lines point at R&D, standard lines at the catalogue | variants.id / formulation_versions.id |
| Qty tiers / Unit price | Repeater (form) · TextColumn summary (table) | rows | stored: quote_lines — one row per tier (quote_id, qty_tier, unit_price) | quotes.id |
| Line: Tax | Select (per tier line) | FK | stored: quote_lines.tax_rate_id — default '5% - Sales Tax', same tax_rates table as SO | tax_rates.id |
| Shipping fee | TextInput group | string / money / FK | stored: quotes.shipping_fee_description / shipping_fee_amount / shipping_fee_tax_rate_id — carried onto the one-click SO on acceptance | tax_rates.id |
| Notes | RichEditor | rich text, nullable | stored: quotes.notes — customer-facing, on the quote PDF (distinct from the internal per-row note) | — |
| Margin % | TextColumn (owner role only) | derived | quote_lines.margin_pct — computed from cost snapshotted at quote time; hidden from sales role | — |
| Source | Badge | enum | stored: quotes.source — auto (machine) | rnd (routed through R&D) | — |
| Status | Badge; tab filter | enum | stored: draft → awaiting_rnd → sent → accepted / expired | — |
| Valid until | TextColumn::date() | date | stored: quotes.valid_until — default sent_at + 30 days | — |
| CRM deal | TextColumn::url() | FK | stored: quotes.deal_id — quote events push the deal stage (sent → Quoted, accepted → Won) | deals.id |
| R&D request | Relation manager + queue card | 1:1 | stored: quote_requests (quote_id, description, target_price, rnd_status queued → with_rnd → cost_received, formulation_id reply) | formulations.id |
| Auto-quote rules | Settings table (owner-editable) | config | stored: auto_quote_rules (category, tier_bands JSON qty breaks, target_margin_pct per band, moq) | categories.id |