Fix/dependabot cus feedback (#946)

* feat(bookkeeping): per-account default VAT, oresavrundning momsfri

Add a per-account "Standard moms" setting to the chart of accounts and use
it to auto-fill the moms on a leverantorsfaktura-rad when that konto is
picked. Oresavrundning (3740) ships as "Ingen moms", so a rounding line no
longer inherits the 25 % rad-default and skews the moms.

- chart_of_accounts.default_vat_rate (0/0.06/0.12/0.25, CHECK-constrained)
- BEFORE INSERT trigger ships 3740 momsfri on every insert path; backfills
  existing 3740 rows
- kontoplan editor: dead free-text momskod replaced with a Standard moms select
- supplier-invoice rad auto-fills the rate from the konto default

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(supplier-invoices): configurable start number for the ankomstnummer series

Add a company_settings.next_arrival_number start floor so a company can continue its leverantorsfaktura numbering from a previous system (e.g. Fortnox) instead of restarting the ankomstnummer at 1. get_next_arrival_number now floors the series via GREATEST(MAX(arrival_number)+1, next_arrival_number), so the floor can never move the series backwards or collide with the (company_id, arrival_number) unique index.

The RPC is hardened while rewritten: SET search_path to empty, schema-qualified refs, and an auth.uid() membership check matching generate_invoice_number.

Includes the settings UI field, sv/en strings, migration, and pg-real coverage. The CompanySettings type and Zod schema field for this feature landed earlier in 1bf3b641 (swept into the per-account VAT commit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(dependabot): reduce open pull requests limit and group updates for better management

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-07-09 12:19:57 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent b1f85bc33e
commit bacc5914af
19 changed files with 570 additions and 26 deletions
+18 -9
View File
@@ -37,7 +37,9 @@ export function AddAccountDialog({
const [accountNumber, setAccountNumber] = useState('')
const [accountName, setAccountName] = useState('')
const [description, setDescription] = useState('')
const [defaultVatCode, setDefaultVatCode] = useState('')
// "Standard moms": the moms-sats a booking line defaults to when this konto is
// picked. 'none' = no default. SelectItem values are stringified decimals.
const [defaultVatRate, setDefaultVatRate] = useState('none')
const [sruCode, setSruCode] = useState('')
const [normalBalance, setNormalBalance] = useState<'debit' | 'credit'>('debit')
const [isSaving, setIsSaving] = useState(false)
@@ -84,7 +86,7 @@ export function AddAccountDialog({
account_type: derived?.account_type || 'expense',
normal_balance: normalBalance,
description: description || null,
default_vat_code: defaultVatCode || null,
default_vat_rate: defaultVatRate === 'none' ? null : parseFloat(defaultVatRate),
sru_code: sruCode || null,
}),
})
@@ -100,7 +102,7 @@ export function AddAccountDialog({
setAccountNumber('')
setAccountName('')
setDescription('')
setDefaultVatCode('')
setDefaultVatRate('none')
setSruCode('')
onCreated(createdAccount)
onOpenChange(false)
@@ -197,12 +199,19 @@ export function AddAccountDialog({
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Momskod <span className="text-muted-foreground">(valfritt)</span></Label>
<Input
value={defaultVatCode}
onChange={(e) => setDefaultVatCode(e.target.value)}
placeholder="T.ex. MP1"
/>
<Label>Standard moms <span className="text-muted-foreground">(valfritt)</span></Label>
<Select value={defaultVatRate} onValueChange={setDefaultVatRate}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">Ingen standard</SelectItem>
<SelectItem value="0">Ingen moms</SelectItem>
<SelectItem value="0.25">25 %</SelectItem>
<SelectItem value="0.12">12 %</SelectItem>
<SelectItem value="0.06">6 %</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>SRU-kod <span className="text-muted-foreground">(valfritt)</span></Label>
+20 -8
View File
@@ -59,7 +59,12 @@ export function EditAccountDialog({ open, onOpenChange, account, onSaved }: Edit
const { toast } = useToast()
const [accountName, setAccountName] = useState(account.account_name)
const [description, setDescription] = useState(account.description || '')
const [defaultVatCode, setDefaultVatCode] = useState(account.default_vat_code || '')
// "Standard moms": the moms-sats a booking line defaults to when this konto is
// picked (currently the leverantörsfaktura-rad). 'none' = no default. Stored
// as a decimal fraction; SelectItem values are the stringified decimals.
const [defaultVatRate, setDefaultVatRate] = useState(
account.default_vat_rate != null ? String(account.default_vat_rate) : 'none',
)
const [sruCode, setSruCode] = useState(account.sru_code || '')
const [isActive, setIsActive] = useState(account.is_active)
const [isSaving, setIsSaving] = useState(false)
@@ -250,7 +255,7 @@ export function EditAccountDialog({ open, onOpenChange, account, onSaved }: Edit
body: JSON.stringify({
account_name: accountName,
description: description || null,
default_vat_code: defaultVatCode || null,
default_vat_rate: defaultVatRate === 'none' ? null : parseFloat(defaultVatRate),
sru_code: sruCode || null,
is_active: isActive,
}),
@@ -310,12 +315,19 @@ export function EditAccountDialog({ open, onOpenChange, account, onSaved }: Edit
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Standard momskod</Label>
<Input
value={defaultVatCode}
onChange={(e) => setDefaultVatCode(e.target.value)}
placeholder="T.ex. MP1"
/>
<Label>Standard moms</Label>
<Select value={defaultVatRate} onValueChange={setDefaultVatRate}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">Ingen standard</SelectItem>
<SelectItem value="0">Ingen moms</SelectItem>
<SelectItem value="0.25">25 %</SelectItem>
<SelectItem value="0.12">12 %</SelectItem>
<SelectItem value="0.06">6 %</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>SRU-kod</Label>
@@ -50,6 +50,22 @@ export function InvoiceSettingsForm({ settings }: InvoiceSettingsFormProps) {
</div>
</div>
<div className="space-y-2">
<Label htmlFor="next_arrival_number">{t('arrival_start_label')}</Label>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<Input
id="next_arrival_number"
name="next_arrival_number"
type="number"
min="1"
defaultValue={settings.next_arrival_number || 1}
/>
</div>
<p className="text-xs text-muted-foreground">
{t('arrival_start_help')}
</p>
</div>
<div className="space-y-2">
<Label htmlFor="invoice_default_notes">{t('default_notes_label')}</Label>
<Textarea
@@ -45,6 +45,7 @@ export function InvoicingSettingsContent() {
swish: normaliseSwish(formData.get('swish') as string) || null,
invoice_prefix: (formData.get('invoice_prefix') as string) || null,
next_invoice_number: parseInt(formData.get('next_invoice_number') as string) || 1,
next_arrival_number: parseInt(formData.get('next_arrival_number') as string) || 1,
invoice_default_days: parseInt(formData.get('invoice_default_days') as string) || 30,
invoice_default_notes: (formData.get('invoice_default_notes') as string) || null,
default_our_reference: (formData.get('default_our_reference') as string) || null,
@@ -697,6 +697,18 @@ export default function NewSupplierInvoiceForm({
const desc = getAccountDescription(accountNumber)
if (desc) setValue(`items.${index}.description`, desc.name)
}
// Auto-fill the rad's moms from the konto's configured default (e.g.
// öresavrundning 3740 = ingen moms), so a rounding line stops inheriting
// the 25 % rad-default. Only when the konto carries an explicit default;
// otherwise the user's current rate stands. Reverse charge uses its own
// rate field, so leave that flow untouched. PostgREST serialises numeric
// columns as strings, so coerce: strict === comparisons on vat_rate
// (inferVatTreatment) expect a number.
const acct = accounts.find((a) => a.account_number === accountNumber)
const defaultRate = acct?.default_vat_rate == null ? null : Number(acct.default_vat_rate)
if (!watchedReverseCharge && defaultRate != null && Number.isFinite(defaultRate)) {
setValue(`items.${index}.vat_rate`, defaultRate, { shouldDirty: true })
}
}
// Periodisering per rad: kräver faktureringsmetoden; eget utlägg bokar