| Checkbox | Table bulk selection (BulkAction Delete, requiresConfirmation()) | select | UI: bulk delete is the only lifecycle action; rows referenced by sales orders are protected/skipped | — |
| Name | TextColumn::searchable(isIndividual)->url() | string, REQUIRED | stored: customers.display_name — the one required field on the card and what this column shows; links to the customer card | customers.id |
| First / Last / Company name | TextInput ×3 (card only, no table columns) | string, nullable each | stored: customers.first_name / last_name / company_name — Katana splits the person from the company; display_name is what every list and SO shows | — |
| E-mail address | TextColumn::searchable(isIndividual) | string, nullable | stored: customers.email — supports multiple comma-separated addresses in one field (6 of 10 rows blank) | — |
| Currency | TextColumn (badge-style); Select on the card | ISO 4217 enum | stored: customers.currency — defaulted from factory base currency (MYR) on create; card label is "Customer currency" and the base option renders as "MYR (Base)" | currencies |
| Discount % | TextInput::numeric()->suffix('%') (card only) | decimal, nullable | stored: customers.discount_percent — default discount applied to new SO lines for this customer | — |
| Price lists | Select::multiple()->relationship() (card only) | many-to-many | pivot: customer_price_list — helper "Price list price overrides discount %"; precedence: price list price > customer discount %, and the Gaia floor-price check applies after both | price_lists.id |
| Phone number | TextColumn::searchable(isIndividual)->toggleable() | string, nullable | stored: free text — empty for every row in this dataset (card label is "Phone") | — |
| Reference ID | TextColumn::searchable(isIndividual)->toggleable() | string, nullable | stored: external/accounting-system identifier for integration matching — empty on all rows | — |
| Category | TextColumn::searchable(isIndividual)->toggleable() + SelectFilter (future) | string, nullable | stored: free-text or picklist customer grouping tag — empty on all rows | — |
| Comment | TextColumn::searchable(isIndividual)->toggleable() | string, nullable | stored: free-text note — only visible in 2560px capture, empty on all rows | — |
| Billing address | Textarea (card only) | text, nullable | stored: customers.billing_address — free-text address block | — |
| Default shipping address | Textarea (card only) | text, nullable | stored: customers.default_shipping_address — NULL means "Same as billing address" (the card's placeholder) | — |