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>
108 lines
3.8 KiB
TypeScript
108 lines
3.8 KiB
TypeScript
'use client'
|
|
|
|
import { Button } from '@/components/ui/button'
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
|
|
import { Progress } from '@/components/ui/progress'
|
|
import type { TransactionCategory } from '@/types'
|
|
|
|
const expenseCategories: { value: TransactionCategory; label: string }[] = [
|
|
{ value: 'expense_equipment', label: 'Utrustning' },
|
|
{ value: 'expense_software', label: 'Programvara' },
|
|
{ value: 'expense_travel', label: 'Resor' },
|
|
{ value: 'expense_office', label: 'Kontor' },
|
|
{ value: 'expense_marketing', label: 'Marknadsföring' },
|
|
{ value: 'expense_professional_services', label: 'Konsulter' },
|
|
{ value: 'expense_education', label: 'Utbildning' },
|
|
{ value: 'expense_bank_fees', label: 'Bankavgift' },
|
|
{ value: 'expense_card_fees', label: 'Kortavgift' },
|
|
{ value: 'expense_currency_exchange', label: 'Valutaväxling' },
|
|
{ value: 'expense_other', label: 'Övrigt' },
|
|
]
|
|
|
|
const incomeCategories: { value: TransactionCategory; label: string }[] = [
|
|
{ value: 'income_services', label: 'Tjänster' },
|
|
{ value: 'income_products', label: 'Produkter' },
|
|
{ value: 'income_other', label: 'Övrigt' },
|
|
]
|
|
|
|
interface BatchCategorySelectorProps {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
selectedCount: number
|
|
onSelectCategory: (category: TransactionCategory) => void
|
|
progress: { done: number; total: number } | null
|
|
}
|
|
|
|
export default function BatchCategorySelector({
|
|
open,
|
|
onOpenChange,
|
|
selectedCount,
|
|
onSelectCategory,
|
|
progress,
|
|
}: BatchCategorySelectorProps) {
|
|
const isProcessing = progress !== null
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={isProcessing ? undefined : onOpenChange}>
|
|
<DialogContent className="max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{isProcessing
|
|
? `Bokför ${progress.done}/${progress.total}...`
|
|
: `Bokför ${selectedCount} transaktioner`}
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
{isProcessing
|
|
? 'Vänta medan transaktionerna bokförs'
|
|
: 'Välj en kategori som ska tillämpas på alla valda transaktioner'}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
{isProcessing ? (
|
|
<div className="py-4">
|
|
<Progress value={(progress.done / progress.total) * 100} />
|
|
<p className="text-sm text-muted-foreground mt-2 text-center">
|
|
{progress.done} av {progress.total} klara
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4 py-2">
|
|
<div>
|
|
<h4 className="text-sm font-medium text-muted-foreground mb-2">Kostnader</h4>
|
|
<div className="grid grid-cols-2 gap-1.5">
|
|
{expenseCategories.map((cat) => (
|
|
<Button
|
|
key={cat.value}
|
|
variant="outline"
|
|
size="sm"
|
|
className="justify-start text-xs"
|
|
onClick={() => onSelectCategory(cat.value)}
|
|
>
|
|
{cat.label}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h4 className="text-sm font-medium text-muted-foreground mb-2">Intäkter</h4>
|
|
<div className="grid grid-cols-2 gap-1.5">
|
|
{incomeCategories.map((cat) => (
|
|
<Button
|
|
key={cat.value}
|
|
variant="outline"
|
|
size="sm"
|
|
className="justify-start text-xs"
|
|
onClick={() => onSelectCategory(cat.value)}
|
|
>
|
|
{cat.label}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|