fix(articles): article ROT/RUT prefill was dead for every dashboard-created article (#1651)
* fix(articles): article ROT/RUT prefill was dead for every dashboard-created article Follow-up to #1634. The user re-tested and picking a RUT article still left the line on "Ingen": the article form has always stored the bare kind ('ROT'/'RUT'), while the prefill only recognised Skatteverket work-type codes (BYGG, STAD, ...). On prod every dashboard-created ROT/RUT article holds the bare kind, so the fix in #1634 never fired for a real user, and worse, since the helper returned null for those values, picking such an article CLEARED a deduction the user had set manually on the row. - rot-rut-rules: parseArticleHouseworkType() understands both vocabularies (code -> kind + arbetstyp; bare ROT/RUT -> kind only), plus normalizeHouseworkType()/HOUSEWORK_TYPE_VALUES/workTypeLabel(). - InvoiceEditor.applyArticle: kind-only articles pre-fill the deduction and keep a same-kind arbetstyp already chosen on the row; "Spara som artikel" round-trips the code or, lacking one, the kind. - ArticleForm: the ROT/RUT select now offers the real Skatteverket arbetstyper in ROT/RUT groups (its own hint always promised "förifyller arbetstyp"); legacy kind-only values stay selectable as "RUT (arbetstyp ej vald)" so an edit never silently drops the flag. Article detail renders "RUT · Städning" instead of the raw code. - API + MCP commit schemas normalize housework_type (case-insensitive code or ROT/RUT, '' clears) and reject anything else; the CSV article import normalizes the column the same way. Prod holds 178 articles with '0'/'1' from a boolean "Rot" column that the keyword detector mapped straight through; those now read as no flag everywhere and can no longer be created. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(articles): review follow-ups on #1651 - InvoiceEditor: switching a row's skattereduktion ROT<->RUT clears an arbetstyp from the other list, and Spara som artikel only round-trips a work type that belongs to the row's kind (CodeRabbit). - MCP update_article: null / '' / whitespace now clear housework_type (commit drops only undefined keys, so the old undefined mapping made the flag un-clearable); create keeps treating them as unset. Tests. - Article CSV import warns when a non-empty ROT/RUT value is dropped as not-an-arbetstyp instead of dropping it silently. Test. - Hint wording: arbetstyp is pre-filled only when the article carries one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
Jakob Wennberg
parent
1cd33d9d97
commit
79240cb2ed
@@ -23,6 +23,11 @@ import AccountCombobox from '@/components/bookkeeping/AccountCombobox'
|
||||
import { AddAccountDialog } from '@/components/bookkeeping/AddAccountDialog'
|
||||
import type { BASAccount, CreateArticleInput } from '@/types'
|
||||
import { INVOICE_POSTING_ACCOUNT_REGEX } from '@/lib/invoices/posting-account'
|
||||
import {
|
||||
ROT_WORK_TYPES,
|
||||
RUT_WORK_TYPES,
|
||||
normalizeHouseworkType,
|
||||
} from '@/lib/invoices/rot-rut-rules'
|
||||
|
||||
// A row from the currencies reference table (lib migration
|
||||
// 20260630110000_currencies_reference_table.sql).
|
||||
@@ -202,11 +207,22 @@ export default function ArticleForm({
|
||||
revenue_account: initialData?.revenue_account || '',
|
||||
cost_price: initialData?.cost_price ?? undefined,
|
||||
ean: initialData?.ean || '',
|
||||
housework_type: initialData?.housework_type || '',
|
||||
// Canonical form (work-type code, bare ROT/RUT, or ''): a stray value
|
||||
// from an import must not sit invisibly in the select as "Ingen".
|
||||
housework_type: normalizeHouseworkType(initialData?.housework_type) ?? '',
|
||||
notes: initialData?.notes || '',
|
||||
},
|
||||
})
|
||||
|
||||
// Legacy articles carry only the kind ('ROT'/'RUT'), stored before this
|
||||
// form offered Skatteverket work types. Keep that choice selectable so an
|
||||
// edit never silently drops the flag; the user upgrades it to a real
|
||||
// arbetstyp when they want the invoice row's work type pre-filled too.
|
||||
const legacyHouseworkKind = (() => {
|
||||
const v = normalizeHouseworkType(initialData?.housework_type)
|
||||
return v === 'ROT' || v === 'RUT' ? v : null
|
||||
})()
|
||||
|
||||
const type = watch('type')
|
||||
const watchedName = watch('name')
|
||||
const watchedUnit = watch('unit')
|
||||
@@ -454,8 +470,27 @@ export default function ArticleForm({
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
>
|
||||
<option value="">{t('housework_none')}</option>
|
||||
<option value="ROT">{t('housework_rot')}</option>
|
||||
<option value="RUT">{t('housework_rut')}</option>
|
||||
{legacyHouseworkKind && (
|
||||
<option value={legacyHouseworkKind}>
|
||||
{legacyHouseworkKind === 'ROT'
|
||||
? t('housework_legacy_rot')
|
||||
: t('housework_legacy_rut')}
|
||||
</option>
|
||||
)}
|
||||
<optgroup label={t('housework_rot')}>
|
||||
{ROT_WORK_TYPES.map((w) => (
|
||||
<option key={w.code} value={w.code}>
|
||||
{w.label}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
<optgroup label={t('housework_rut')}>
|
||||
{RUT_WORK_TYPES.map((w) => (
|
||||
<option key={w.code} value={w.code}>
|
||||
{w.label}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
</SettingsSelect>
|
||||
)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user