diff --git a/DECISIONS.md b/DECISIONS.md index 55345b76..38d39000 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -371,3 +371,4 @@ One line per decision: `[YYYY-MM-DD] : `. Appended by agents and [2026-07-25] Removed invented 6-month minimum for first räkenskapsår: BFL 3 kap 3 § sets no floor (Bolagsverket: "hur kort som helst", max 18 months); the check only existed for isFirstPeriod, exactly the case the law exempts, and blocked a customer shortening an autumn-registered first year to Dec 31. [2026-07-25] Article EUR-price support bug: root cause was the edit dialog omitting currency from initialData (form defaulted SEK and PATCHed it back) plus kr-hardcoded formatCurrency calls; export gets a Valuta column + suffix-free decimalColumn instead of extending CURRENCY_FORMAT, importer Valuta detection deferred as follow-up to keep the diff scoped. [2026-07-25] Reinstated article deactivation as an explicit PATCH active-toggle button on the detail page (support: odinaero.se) instead of reverting DELETE to soft-delete: 8a9a930f intentionally made DELETE hard-delete for unused articles, but that left invoice-referenced articles (ARTICLE_IN_USE) with no retire path; the old deactivate i18n keys were still in messages/ and are reused. +[2026-07-25] Momsdeklaration period selection fused into one chip (year+quarter/month, reverse-chron, 5 years) instead of separate year and period pickers; cadence stays behind the Period chip. Standalone report pages now render their own PageHeader so Exportera sits on the title row per convention 9. diff --git a/components/reports/FocusedReport.tsx b/components/reports/FocusedReport.tsx index 8d6da661..7c7b56ca 100644 --- a/components/reports/FocusedReport.tsx +++ b/components/reports/FocusedReport.tsx @@ -113,10 +113,13 @@ function FocusedReportInner({ )} - { @@ -132,9 +135,9 @@ function FocusedReportInner({ initialPeriods={initialPeriods} initialCompanyId={initialCompanyId} /> - ) - } - /> + } + /> + )} {DATE_RANGE_SLUGS.has(slug) && selectedPeriodBounds && ( case 'vat-declaration': - return + return case 'periodisk-sammanstallning': return case 'ne-declaration': diff --git a/components/reports/SkatteverketPanel.tsx b/components/reports/SkatteverketPanel.tsx index 064bf156..39da0669 100644 --- a/components/reports/SkatteverketPanel.tsx +++ b/components/reports/SkatteverketPanel.tsx @@ -3,7 +3,6 @@ import { ENABLED_EXTENSION_IDS } from '@/lib/extensions/_generated/enabled-extensions' import React, { useState, useEffect, useCallback } from 'react' import { Button } from '@/components/ui/button' -import { Badge } from '@/components/ui/badge' import { Skeleton } from '@/components/ui/skeleton' import { DropdownMenu, @@ -49,8 +48,8 @@ interface SkatteverketStatus { * Codes from /lib/api-client.ts's SkatteverketAuthError that mean "the user * needs to reconnect with BankID before this action can succeed". When the API * returns one of these codes we flip the local status.expired flag so the - * "Session utgången" badge + "Förnya session" button surface, even if the - * upstream /status endpoint hasn't reflected the change yet. + * expired-session attn line with its "Förnya med BankID" action surfaces, + * even if the upstream /status endpoint hasn't reflected the change yet. */ const AUTH_RECONNECT_CODES = new Set([ 'NOT_CONNECTED', @@ -761,21 +760,12 @@ function SkatteverketPanelInner({ Skicka direkt till Skatteverket
- - - Ansluten - - {status.expired && ( - <> - - - Session utgången - - - + {/* Connected is the normal state here (the not-connected branch is + a different section): muted text, never a chip. The expired + session is the one exception and gets the attn sentence below + instead of a badge-and-button cluster. */} + {!status.expired && ( + Ansluten )} {/* Read-only lookups and recovery actions live in the overflow menu: the visible surface stays the forward path (validera, @@ -901,6 +891,18 @@ function SkatteverketPanelInner({
+ {status.expired && ( +

+ Sessionen mot Skatteverket har gått ut.{' '} + +

+ )}
{/* In-flight status for overflow-menu actions: their menu closes on diff --git a/components/reports/VatChecksCard.tsx b/components/reports/VatChecksCard.tsx index 9fba9167..930efe4c 100644 --- a/components/reports/VatChecksCard.tsx +++ b/components/reports/VatChecksCard.tsx @@ -3,14 +3,7 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { Button } from '@/components/ui/button' -import { Label } from '@/components/ui/label' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select' +import { ContextPicker } from '@/components/common/ContextPicker' import { DataList, DataListMeta, @@ -71,6 +64,27 @@ const SUPPLY_LABELS: Record = { service: 'tjänst', goods: 'vara', } +/** Chip labels (sentence case); SUPPLY_LABELS stays lowercase for dialog prose. */ +const SUPPLY_DISPLAY: Record = { + service: 'Tjänst', + goods: 'Vara', +} + +const SUPPLIER_ITEMS = [ + { id: 'eu_business', label: 'EU-leverantör' }, + { id: 'non_eu_business', label: 'Utanför EU' }, + { id: 'swedish_business', label: 'Svensk omvänd skattskyldighet' }, +] + +// Non-EU + goods is import VAT (ruta 50/60-62), not reverse charge: the +// goods choice disappears entirely for non-EU suppliers. +const supplyItemsFor = (supplierType: SupplierType) => + supplierType === 'non_eu_business' + ? [{ id: 'service', label: 'Tjänst' }] + : [ + { id: 'service', label: 'Tjänst' }, + { id: 'goods', label: 'Vara' }, + ] /** How many gap rows render before the "Visa alla" toggle. */ const GAP_PREVIEW_COUNT = 8 @@ -355,54 +369,38 @@ export function VatChecksCard({

) : ( <> -
-
- - -
-
- - -
+ {/* Classification chips (house context-picker style), then the + bulk action: the chip values ("EU-leverantör", "Tjänst") + are self-describing, so no field labels. */} +
+ { + const supplierType = value as SupplierType + setSharedSel((prev) => ({ + supplierType, + // Non-EU + goods is import VAT, not reverse charge: + // coerce back to service so an invalid combo can't + // be mass-applied. + supplyType: + supplierType === 'non_eu_business' ? 'service' : prev.supplyType, + })) + }} + triggerLabel={SUPPLIER_LABELS[sharedSel.supplierType]} + ariaLabel="Leverantörstyp" + disabled={busy} + /> + + setSharedSel((prev) => ({ ...prev, supplyType: value as SupplyType })) + } + triggerLabel={SUPPLY_DISPLAY[sharedSel.supplyType]} + ariaLabel="Typ av inköp" + disabled={busy} + />