Add/pdf and templates (#705)
* fix(invoices): apply configured voucher series to payments + preview next voucher The booking engine resolves the series from default_voucher_series_per_source_type, but the global "Standardserie" dropdown wrote a separate field the engine ignored, and cash-method invoice payments (invoice_cash_payment) weren't exposed in settings — so configured series were silently dropped to "A". - Expose cash/private payment source types in the per-source-type form - Write the global default through to the map on save, keeping overrides - Resolve voucher-sequences/next by source_type (+date) to match the engine - Show the upcoming voucher (V2) in the payment dialog title - Share resolveInvoicePaymentSourceType so preview and booking can't drift Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(salary): keep AGI panel in sync with Skatteverket signing state The AGI panel mixed run-scoped generation state (agi_generated_at, agi_declarations) with period-scoped submission state (extension_data agi_submission_{period}), so the two could drift and present contradictory UI. Reconcile them: - Auto-detect a Mina Sidor BankID signature: while awaiting_signing, poll /agi/kvittenser on mount and on tab refocus so the panel flips to "signed" (hiding the signing actions) without a manual "Hamta kvittens" click. - Warn instead of offering to sign when the locked granskningsunderlag predates the run's latest AGI generation (draftIsStale) — avoids filing superseded figures. - Self-heal a stale "AGI-XML saknas" error once the run's AGI is (re)generated out-of-band (MCP/API/other tab). - Refetch the salary run on tab focus so agi_generated_at reflects out-of-band generation without a hard reload. - /agi/lasUpp now clears the cached agi_submission_{period} record, so unlocking drops the panel back to the pre-submission state instead of stranding it on a released "redo att signeras" draft. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: Implement VAT registration handling and invoice item line types - Added VAT registration check in commitCreateInvoice to set VAT rate to 0% for non-VAT registered companies. - Updated invoice creation logic to reflect 'exempt' VAT treatment and adjusted related fields accordingly. - Introduced support for free-text and blank spacer rows in invoice items by adding a new line_type field. - Enhanced invoice and credit note handling to accommodate new line types. - Added new localized messages for text rows in English and Swedish. - Created tests for salary run approval logic, ensuring bank details are validated correctly. - Implemented effective net payout calculation for salary runs, considering tax overrides. - Added SQL migrations to support new invoice item line types and accounting method awareness for linking invoices to vouchers. * feat(articles): artikelregister with revenue account + VAT rate per article Article register (non-inventory) with per-article VAT rate and optional BAS class-3 revenue-account override. Includes API routes, UI pages, MCP tools, pending-operation staging, and the activate-or-create account flow (ACCOUNTS_NOT_IN_CHART -> ActivateAccountsDialog, unknown numbers -> AddAccountDialog) reusing the journal entry UX. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bookkeeping): no-doc-required batch + bulk-missing endpoints Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(payments): supplier payment lines + cash-method invoice matching Shared payment-line proposal for supplier invoices, improved match-invoice/match-supplier-invoice flows (kontantmetoden-aware), and voucher-link support without requiring a 151x clearing entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bookkeeping): new journal entry dialog, SIE import tweaks, misc New journal entry dialog component, journal list/page updates, invoice editor updates, SIE import adjustments, transaction ingest and api-key tweaks, pr-agent workflow update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(invoices): implement tax reduction features and localization updates * feat(tests): add VAT registration gate to pending operations commit tests --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
21cfcbe180
commit
f9ea9c0082
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useForm, Controller } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
@@ -13,7 +13,9 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
import { ChevronDown, Loader2, Lock } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useCanWrite } from '@/lib/hooks/use-can-write'
|
||||
import type { CreateArticleInput } from '@/types'
|
||||
import AccountCombobox from '@/components/bookkeeping/AccountCombobox'
|
||||
import { AddAccountDialog } from '@/components/bookkeeping/AddAccountDialog'
|
||||
import type { BASAccount, CreateArticleInput } from '@/types'
|
||||
|
||||
// Unit list mirrors the invoice line editor (app/(dashboard)/invoices/new/page.tsx).
|
||||
const UNITS = ['st', 'tim', 'dag', 'månad', 'km', 'kg'] as const
|
||||
@@ -35,6 +37,28 @@ export default function ArticleForm({
|
||||
}: ArticleFormProps) {
|
||||
const { canWrite } = useCanWrite()
|
||||
const t = useTranslations('form_article')
|
||||
// Active class-3 (revenue) accounts for the combobox. The combobox accepts
|
||||
// unknown 4-digit numbers optimistically — the API answers with
|
||||
// ACCOUNTS_NOT_IN_CHART for activatable BAS accounts, and the host page's
|
||||
// ActivateAccountsDialog flow takes over (same UX as the journal entry form).
|
||||
const [revenueAccounts, setRevenueAccounts] = useState<BASAccount[]>([])
|
||||
// Inline account creation: what the user typed in the combobox when they hit
|
||||
// "Skapa konto" — non-null opens AddAccountDialog prefilled with it.
|
||||
const [createAccountPrefill, setCreateAccountPrefill] = useState<string | null>(null)
|
||||
|
||||
async function fetchRevenueAccounts() {
|
||||
try {
|
||||
const res = await fetch('/api/bookkeeping/accounts?class=3')
|
||||
const body = await res.json()
|
||||
setRevenueAccounts((body?.data as BASAccount[]) || [])
|
||||
} catch {
|
||||
// Non-fatal: the combobox degrades to free 4-digit entry.
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchRevenueAccounts()
|
||||
}, [])
|
||||
// Open the advanced section by default when it already holds data, so an
|
||||
// edit never hides a value the user previously set.
|
||||
const [advancedOpen, setAdvancedOpen] = useState(
|
||||
@@ -73,6 +97,7 @@ export default function ArticleForm({
|
||||
handleSubmit,
|
||||
watch,
|
||||
control,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
@@ -232,13 +257,18 @@ export default function ArticleForm({
|
||||
<div className="space-y-4 pt-4">
|
||||
{/* Revenue account */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="revenue_account">{t('revenue_account_label')}</Label>
|
||||
<Input
|
||||
id="revenue_account"
|
||||
inputMode="numeric"
|
||||
placeholder={t('revenue_account_placeholder')}
|
||||
className="tabular-nums"
|
||||
{...register('revenue_account')}
|
||||
<Label>{t('revenue_account_label')}</Label>
|
||||
<Controller
|
||||
name="revenue_account"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<AccountCombobox
|
||||
value={field.value || ''}
|
||||
accounts={revenueAccounts}
|
||||
onChange={field.onChange}
|
||||
onCreateAccount={(prefill) => setCreateAccountPrefill(prefill)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">{t('revenue_account_hint')}</p>
|
||||
</div>
|
||||
@@ -332,6 +362,31 @@ export default function ArticleForm({
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Inline custom-account creation (renders in a portal, outside the form).
|
||||
After create: refresh the chart and select the new number as the
|
||||
article's revenue account — mirrors the journal entry form. */}
|
||||
<AddAccountDialog
|
||||
open={createAccountPrefill != null}
|
||||
onOpenChange={(next) => {
|
||||
if (!next) setCreateAccountPrefill(null)
|
||||
}}
|
||||
initialAccountNumber={
|
||||
createAccountPrefill && /^\d{1,4}$/.test(createAccountPrefill)
|
||||
? createAccountPrefill
|
||||
: undefined
|
||||
}
|
||||
initialAccountName={
|
||||
createAccountPrefill && !/^\d{1,4}$/.test(createAccountPrefill)
|
||||
? createAccountPrefill
|
||||
: undefined
|
||||
}
|
||||
onCreated={async (account) => {
|
||||
await fetchRevenueAccounts()
|
||||
setValue('revenue_account', account.account_number, { shouldDirty: true })
|
||||
setCreateAccountPrefill(null)
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user