91e2c1705a
Per-line VAT rates: - Add generatePerRateLines() to group invoice items by vat_rate with separate revenue + VAT lines per rate group (invoice-entries.ts) - Add getAvailableVatRates() and getVatTreatmentForRate() (vat-rules.ts) - PDF template shows per-line VAT column and per-rate totals for mixed-rate invoices - Invoice create/review UI supports per-line rate selection - Types: add vat_rate/vat_amount to InvoiceItem, vat_rate to CreateInvoiceItemInput Invoice document types (proforma, delivery note): - Add InvoiceDocumentType, document_type and converted_from_id to Invoice type - PDF hides prices for delivery notes, adds proforma notice - Email templates support all document types - mark-paid skips journal entries for non-invoice document types - Migration 031: invoice_document_type Accounting method support: - Add AccountingMethod type (accrual/cash) - Migration 032: add_accounting_method column to company_settings VAT declaration rewrite: - Rewrite to read directly from general ledger (26xx/3xxx account lines) instead of aggregating invoices/transactions/receipts - ACCOUNT_RUTA mapping drives momsdeklaration boxes from GL balances Bank reconciliation: - Transaction ingest now pre-fetches unlinked GL lines and attempts auto-reconciliation during import - Add transaction.reconciled event type - Add ReconciliationMethod type and reconciliation_method on Transaction - Migration 030: bank_reconciliation - New reconciliation engine, API routes, and BankReconciliationView component Pagination (fetchAllRows): - New lib/supabase/fetch-all.ts overcomes PostgREST 1000-row limit - Adopted in all report generators, SIE/SRU export, account list APIs Fiscal period validation: - New validate-period-duration.ts enforces max 18 months per BFL 3 kap. - Applied in period-service.ts and fiscal-periods API Account mapper simplification: - Remove Levenshtein/fuzzy matching, use exact account number match only Swedbank parser improvements: - Support abbreviated headers (Clnr, Bokfdag, Radnr) - Use Referens column as counterparty Chart of accounts management: - Add DELETE endpoint with system account and usage protection - PUT uses partial updates - New AccountCombobox, AddAccountDialog, EditAccountDialog, ChartOfAccountsManager Tax deadline corrections: - Rewrite inkomstdeklaration_ab using Skatteverket lookup table - Rewrite arsredovisning deadline to 7 months after FY end per ÅRL 8:3 Onboarding first fiscal year: - Add first fiscal year toggle with date pickers and 18-month validation UI terminology: - Change "okategoriserad/kategorisera" to "obokförd/bokföra" throughout Report column fix: - Fix start_date/end_date to period_start/period_end in report queries Supplier invoice input: - CreateSupplierInvoiceItemInput uses amount field (legacy quantity/unit_price kept) Misc: - SIE import uses upsert for idempotent account creation - account-descriptions.ts falls back to BAS reference data - Add invoice_default_notes to CompanySettings - Update CLAUDE.md to reflect current project state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogFooter,
|
|
} from '@/components/ui/dialog'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Label } from '@/components/ui/label'
|
|
import { Textarea } from '@/components/ui/textarea'
|
|
import { Switch } from '@/components/ui/switch'
|
|
import { Loader2 } from 'lucide-react'
|
|
import type { BASAccount } from '@/types'
|
|
|
|
interface EditAccountDialogProps {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
account: BASAccount
|
|
onSaved: () => void
|
|
}
|
|
|
|
export function EditAccountDialog({ open, onOpenChange, account, onSaved }: EditAccountDialogProps) {
|
|
const [accountName, setAccountName] = useState(account.account_name)
|
|
const [description, setDescription] = useState(account.description || '')
|
|
const [defaultVatCode, setDefaultVatCode] = useState(account.default_vat_code || '')
|
|
const [sruCode, setSruCode] = useState(account.sru_code || '')
|
|
const [isActive, setIsActive] = useState(account.is_active)
|
|
const [isSaving, setIsSaving] = useState(false)
|
|
|
|
async function handleSave() {
|
|
setIsSaving(true)
|
|
try {
|
|
const response = await fetch(`/api/bookkeeping/accounts/${account.account_number}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
account_name: accountName,
|
|
description: description || null,
|
|
default_vat_code: defaultVatCode || null,
|
|
sru_code: sruCode || null,
|
|
is_active: isActive,
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const data = await response.json()
|
|
throw new Error(data.error || 'Kunde inte uppdatera kontot')
|
|
}
|
|
|
|
onSaved()
|
|
onOpenChange(false)
|
|
} catch {
|
|
// Error handled silently — toast is in parent
|
|
} finally {
|
|
setIsSaving(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
Redigera konto {account.account_number}
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4 py-2">
|
|
<div className="space-y-2">
|
|
<Label>Kontonamn</Label>
|
|
<Input
|
|
value={accountName}
|
|
onChange={(e) => setAccountName(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>Beskrivning</Label>
|
|
<Textarea
|
|
value={description}
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
placeholder="Kort beskrivning av kontots användning"
|
|
rows={2}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid 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"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>SRU-kod</Label>
|
|
<Input
|
|
value={sruCode}
|
|
onChange={(e) => setSruCode(e.target.value)}
|
|
placeholder="T.ex. 7201"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between rounded-lg border p-3">
|
|
<div>
|
|
<p className="text-sm font-medium">Aktivt konto</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
Inaktiva konton visas inte i bokföringsformulär
|
|
</p>
|
|
</div>
|
|
<Switch checked={isActive} onCheckedChange={setIsActive} />
|
|
</div>
|
|
|
|
{account.is_system_account && (
|
|
<p className="text-xs text-muted-foreground bg-muted rounded p-2">
|
|
Detta är ett systemkonto och kan inte tas bort.
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
Avbryt
|
|
</Button>
|
|
<Button onClick={handleSave} disabled={isSaving || !accountName.trim()}>
|
|
{isSaving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
Spara
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|