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:
co-authored by
Claude Opus 4.8
parent
b1f85bc33e
commit
bacc5914af
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user