feat(import): detect and import the article register's Valuta column (#1183)

Fixes #1167. The register export gained a Valuta column in #1166 but
the importer ignored it, so re-imported non-SEK articles silently
became SEK, breaking the export -> edit -> re-import round-trip.

- Column detector recognizes valuta/valutakod/currency (claimed before
  generic columns; no keyword collision with Momskod).
- Parser normalizes to upper-case ISO shape, drops malformed codes
  with a file-level warning, and carries currency per row.
- Execute route validates codes lazily against the currencies table
  (FK stays the backstop when the reference read fails), imports valid
  codes, defaults absent to SEK, and in merge mode only overwrites
  when the file explicitly carries a valid currency.
- Edit step shows a muted currency marker next to non-SEK prices;
  manual column mapping offers Valuta.
- Export docblock caveat removed.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-07-25 12:59:23 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent aead2bc1d1
commit 36a1df4f6b
10 changed files with 180 additions and 8 deletions
@@ -192,3 +192,50 @@ describe('parseArticlesFile', () => {
expect(result.rows[1].name).toBe('Kärra')
})
})
describe('parseArticlesFile currency (Valuta) column', () => {
it('parses and normalizes a Valuta column', () => {
const buffer = buildXlsx([
['Benämning', 'Försäljningspris', 'Valuta'],
['EU-konsulting', '950', 'eur'],
['Svensk tjänst', '500', 'SEK'],
['Utan valuta', '100', ''],
])
const result = parseArticlesFile(buffer, 'valuta.xlsx')
expect(result.detected_columns.currency_col).toBe(2)
expect(result.rows[0].currency).toBe('EUR')
expect(result.rows[1].currency).toBe('SEK')
// Blank cell = not specified: the execute route imports it as SEK.
expect(result.rows[2].currency).toBeNull()
expect(result.warnings).toEqual([])
})
it('drops malformed currency codes with a warning', () => {
const buffer = buildXlsx([
['Benämning', 'Pris', 'Valuta'],
['A', '100', 'EURO'],
['B', '200', 'EUR'],
])
const result = parseArticlesFile(buffer, 'valuta.xlsx')
expect(result.rows[0].currency).toBeNull()
expect(result.rows[1].currency).toBe('EUR')
expect(result.warnings.some((w) => w.includes('valutakod'))).toBe(true)
})
it('does not let Valuta steal the price or VAT columns', () => {
const buffer = buildXlsx([
['Benämning', 'Valuta', 'Försäljningspris', 'Moms %'],
['A', 'EUR', '100', '25'],
])
const result = parseArticlesFile(buffer, 'valuta.xlsx')
expect(result.detected_columns.currency_col).toBe(1)
expect(result.rows[0].price_excl_vat).toBe(100)
expect(result.rows[0].vat_rate).toBe(25)
})
})