From bacc5914afa01f76ed4f24ec956ee4aa09f59127 Mon Sep 17 00:00:00 2001 From: Mattsson <111893710+mattssonn@users.noreply.github.com> Date: Thu, 9 Jul 2026 12:19:57 +0200 Subject: [PATCH] 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) * 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) * fix(dependabot): reduce open pull requests limit and group updates for better management --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .github/dependabot.yml | 38 +++-- .../accounts/__tests__/accounts.test.ts | 42 +++++ app/api/bookkeeping/accounts/route.ts | 1 + components/bookkeeping/AddAccountDialog.tsx | 27 ++-- components/bookkeeping/EditAccountDialog.tsx | 28 +++- components/settings/InvoiceSettingsForm.tsx | 16 ++ .../sections/InvoicingSettingsContent.tsx | 1 + .../NewSupplierInvoiceForm.tsx | 12 ++ lib/api/__tests__/schemas.test.ts | 33 ++++ lib/api/schemas.ts | 10 ++ lib/import/__tests__/account-mapper.test.ts | 1 + messages/en.json | 2 + messages/sv.json | 2 + ...0260709120000_account_default_vat_rate.sql | 54 +++++++ ...09130000_supplier_invoice_start_number.sql | 67 ++++++++ .../account-default-vat-rate.pg.test.ts | 108 +++++++++++++ tests/helpers.ts | 1 + tests/pg/supplier-arrival-number.pg.test.ts | 146 ++++++++++++++++++ types/index.ts | 7 + 19 files changed, 570 insertions(+), 26 deletions(-) create mode 100644 supabase/migrations/20260709120000_account_default_vat_rate.sql create mode 100644 supabase/migrations/20260709130000_supplier_invoice_start_number.sql create mode 100644 supabase/migrations/__tests__/account-default-vat-rate.pg.test.ts create mode 100644 tests/pg/supplier-arrival-number.pg.test.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b424d59c..70382047 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,4 +1,13 @@ version: 2 +# Deliberately throttled to avoid a PR flood. Two knobs do the work: +# - open-pull-requests-limit: 1 -> at most ONE open PR per ecosystem at a +# time. Dependabot will not open next week's PR until the current one is +# merged or closed, so PRs can never pile up. +# - groups (patterns: "*") -> every available bump (major/minor/patch) +# is batched into that single PR instead of one PR per package. +# Combined with the weekly schedule this means: normally one npm PR a week (or +# none), and only in a rare week where Docker/Actions also move do you see more +# than one PR at all. updates: # Base images in the root Dockerfile (node:22-alpine). - package-ecosystem: docker @@ -6,7 +15,11 @@ updates: schedule: interval: weekly day: monday - open-pull-requests-limit: 5 + open-pull-requests-limit: 1 + groups: + docker: + patterns: + - "*" labels: - dependencies - docker @@ -17,7 +30,11 @@ updates: schedule: interval: weekly day: monday - open-pull-requests-limit: 5 + open-pull-requests-limit: 1 + groups: + docker-cron: + patterns: + - "*" labels: - dependencies - docker @@ -28,7 +45,11 @@ updates: schedule: interval: weekly day: monday - open-pull-requests-limit: 5 + open-pull-requests-limit: 1 + groups: + github-actions: + patterns: + - "*" labels: - dependencies - ci @@ -39,16 +60,15 @@ updates: schedule: interval: weekly day: monday - open-pull-requests-limit: 10 + open-pull-requests-limit: 1 labels: - dependencies - npm groups: - # Batch low-risk minor/patch bumps so the reviewer queue stays small. - minor-and-patch: - update-types: - - minor - - patch + # Batch ALL bumps (major/minor/patch) into a single weekly PR. + npm: + patterns: + - "*" ignore: # @anthropic-ai/bedrock-sdk is PINNED to an exact version in package.json. # 0.32.0 arrived inside a grouped minor-and-patch bump (#884) and broke diff --git a/app/api/bookkeeping/accounts/__tests__/accounts.test.ts b/app/api/bookkeeping/accounts/__tests__/accounts.test.ts index 9e8117fc..80255d72 100644 --- a/app/api/bookkeeping/accounts/__tests__/accounts.test.ts +++ b/app/api/bookkeeping/accounts/__tests__/accounts.test.ts @@ -131,6 +131,29 @@ describe('POST /api/bookkeeping/accounts', () => { expect(status).toBe(409) expect(body.error).toContain('5010') }) + + it('forwards default_vat_rate into the insert', async () => { + const { supabase, calls } = createCapturingSupabase([ + { data: { account_number: '3740', default_vat_rate: 0 } }, + ]) + auth(supabase) + const req = createMockRequest('/api/bookkeeping/accounts', { + method: 'POST', + body: { + account_number: '3740', + account_name: 'Öres- och kronutjämning', + account_type: 'revenue', + normal_balance: 'debit', + default_vat_rate: 0, + }, + }) + const { status } = await parseJsonResponse(await createPOST(req, routeParams)) + expect(status).toBe(200) + const insertArg = calls.find((c) => c.method === 'insert')?.args[0] as { + default_vat_rate?: number | null + } + expect(insertArg?.default_vat_rate).toBe(0) + }) }) describe('DELETE /api/bookkeeping/accounts/[number]', () => { @@ -218,6 +241,25 @@ describe('PUT /api/bookkeeping/accounts/[number]', () => { expect(status).toBe(200) expect(body.data.account_name).toBe('Nytt namn') }) + + it('forwards default_vat_rate into the update', async () => { + const { supabase, calls } = createCapturingSupabase([ + { data: { account_number: '3740', default_vat_rate: 0 } }, + ]) + auth(supabase) + const req = createMockRequest('/api/bookkeeping/accounts/3740', { + method: 'PUT', + body: { default_vat_rate: 0 }, + }) + const { status } = await parseJsonResponse( + await PUT(req, { params: Promise.resolve({ number: '3740' }) }) + ) + expect(status).toBe(200) + const updateArg = calls.find((c) => c.method === 'update')?.args[0] as { + default_vat_rate?: number | null + } + expect(updateArg?.default_vat_rate).toBe(0) + }) }) describe('POST /api/bookkeeping/accounts/activate', () => { diff --git a/app/api/bookkeeping/accounts/route.ts b/app/api/bookkeeping/accounts/route.ts index a50ca43c..d51671bb 100644 --- a/app/api/bookkeeping/accounts/route.ts +++ b/app/api/bookkeeping/accounts/route.ts @@ -79,6 +79,7 @@ export const POST = withRouteContext( is_system_account: false, description: body.description || null, default_vat_code: body.default_vat_code || null, + default_vat_rate: body.default_vat_rate ?? null, sru_code: body.sru_code || null, sort_order: parseInt(body.account_number), }) diff --git a/components/bookkeeping/AddAccountDialog.tsx b/components/bookkeeping/AddAccountDialog.tsx index d40c89ff..ff42e280 100644 --- a/components/bookkeeping/AddAccountDialog.tsx +++ b/components/bookkeeping/AddAccountDialog.tsx @@ -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({
- - setDefaultVatCode(e.target.value)} - placeholder="T.ex. MP1" - /> + +
diff --git a/components/bookkeeping/EditAccountDialog.tsx b/components/bookkeeping/EditAccountDialog.tsx index 47121c3f..d1b38de2 100644 --- a/components/bookkeeping/EditAccountDialog.tsx +++ b/components/bookkeeping/EditAccountDialog.tsx @@ -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
- - setDefaultVatCode(e.target.value)} - placeholder="T.ex. MP1" - /> + +
diff --git a/components/settings/InvoiceSettingsForm.tsx b/components/settings/InvoiceSettingsForm.tsx index 412e68ff..01b1bc1f 100644 --- a/components/settings/InvoiceSettingsForm.tsx +++ b/components/settings/InvoiceSettingsForm.tsx @@ -50,6 +50,22 @@ export function InvoiceSettingsForm({ settings }: InvoiceSettingsFormProps) {
+
+ +
+ +
+

+ {t('arrival_start_help')} +

+
+