feat(accounting): consolidate VAT/financial config into Settings → Accounting

- Remove the orphaned "Default VAT rate %" from Business profile; the rates
  are the Accounting VAT codes. The invoice/quote VAT picker (VatRateSelect)
  is now code-only — options are exactly the Accounting output codes, no
  free-text custom rate. Off-list legacy values on existing invoices are
  preserved as a read-only "(not configured)" option so issued documents
  aren't silently changed.
- Move VAT label + default hourly rate to the Accounting tab (new
  AccountingProfileFields card; storage stays on business_profile, own save).
  Wire vat_label onto the PDF VAT-line label via the issuer block (covers
  invoices + quotes), falling back to the locale default when blank.
- Default currency stays on Business profile but becomes a normalizing
  dropdown (an old free-text "chf" auto-selects "CHF"; unknown values
  preserved). Add a moved-note callout. Strip the moved fields from the
  Business-profile save so it can't clobber an Accounting-tab edit.
This commit is contained in:
Luca
2026-06-18 15:10:44 +02:00
parent 315d15afd4
commit dc7b87bb87
7 changed files with 183 additions and 58 deletions
+30
View File
@@ -0,0 +1,30 @@
/**
* Currency codes for the Business-profile "Default currency" dropdown.
* CH/LI-first ordering (picpeak's primary scope), then the common EUR/USD/GBP
* and a broad set of other ISO 4217 codes. Stored value is the bare 3-letter
* code (e.g. "CHF").
*/
export const CURRENCY_CODES: string[] = [
'CHF', 'EUR', 'USD', 'GBP',
'AUD', 'CAD', 'CNY', 'CZK', 'DKK', 'HKD', 'HUF', 'ILS', 'INR', 'JPY',
'NOK', 'NZD', 'PLN', 'RON', 'SEK', 'SGD', 'THB', 'TRY', 'ZAR',
];
/**
* Normalise a stored/typed currency value to a known code. Upper-cases + trims
* (so an old free-text "chf" resolves to "CHF"). Returns the matched code, or
* the cleaned input if it isn't in the known list (caller preserves it as an
* extra option so nothing is lost), or '' for empty input.
*/
export function normalizeCurrency(value: string | null | undefined): string {
const cleaned = (value || '').trim().toUpperCase();
if (!cleaned) return '';
return CURRENCY_CODES.includes(cleaned) ? cleaned : cleaned;
}
/** Build the option list, prepending an unknown-but-set value so it's preserved. */
export function currencyOptions(current: string | null | undefined): string[] {
const cur = normalizeCurrency(current);
if (cur && !CURRENCY_CODES.includes(cur)) return [cur, ...CURRENCY_CODES];
return CURRENCY_CODES;
}