Accounted rebrand + swarm-skill cleanup + bank-reconciliation fixes (#643)
* feat(reconciliation): close the bank-feed loop on voucher links and re-tag mis-typed opening balances
Two related fixes to bank reconciliation correctness:
1. Auto-reconcile on voucher link. Linking an invoice or supplier invoice to
an existing voucher previously advanced only the invoice — the bank
transaction that paid it kept sitting in the Transactions inbox with a null
journal_entry_id. linkInvoiceToVoucher / linkSupplierInvoiceToVoucher now
call autoReconcileTransactionForLinkedVoucher (lib/reconciliation), which
links the bank transaction to the same verifikat when exactly one unbooked
line matches it. Best-effort and post-commit: a failure here never fails the
link. The result surfaces reconciledTransactionId; the inbox row leaves the
list and the UI shows link_success_tx_reconciled.
2. Re-tag mis-typed opening balances. getReconciliationStatus and the GL-line
matching RPCs identify a cash account's ingående balans solely by
journal_entries.source_type='opening_balance'. Companies migrated from other
systems often booked the bank IB as an ordinary voucher (source_type
'import' or 'manual'), so it was never excluded and surfaced as a phantom
reconciliation difference equal to the opening balance. Adds:
- migration mark_entry_as_opening_balance: a GUC-gated carve-out in the
immutability trigger plus a SECURITY DEFINER RPC that validates the entry
(balance-sheet lines only, dated on a fiscal-period boundary), flips the
source_type, and writes an audit row — no blanket data sweep.
- POST /api/reconciliation/bank/mark-opening-balance + MarkOpeningBalanceSchema.
- BankReconciliationView action to trigger it from the IB diff.
The gnubok_create_voucher executor now accepts a typed is_opening_balance flag
and derives source_type='opening_balance' only after validating class 1/2 lines
on the period start, so new IBs land correctly typed.
Covered by lib/reconciliation auto-reconcile tests, voucher-executors tests,
and a mark-entry-as-opening-balance pg-real test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: rebrand gnubok → Accounted and prune swarm agent skills
Product rebrand and skills housekeeping. No runtime behaviour change.
Rebrand: replace user-visible "gnubok" with "Accounted" across docs, READMEs,
in-code comments, doc-site content, MCP skill/resource prose, and the
gnubok-mcp package description. The MCP resource URI scheme is moved gnubok://
→ Accounted:// consistently across resource registrations, the event-type
comment, and the resource/skill tests. Deliberately preserved as stable
identifiers (NOT rebranded): the gnubok-company-id cookie, gnubok_sk_ / gnubok_inv_
token prefixes, the gnubok-mcp npm bridge name, and the AGI <gem:Programnamn>
value (kept 'gnubok' per its source comment — it is the software identifier sent
to Skatteverket and must not churn across visual rebrands).
Skills: remove the 27 swarm-* agent SKILL.md atoms (no longer used; already
absent from the agent_atom_registry in prod), refresh the remaining skill docs,
add the .claude/rules/ path-scoped rule set, and regenerate the
seed_agent_atom_bodies migration + .skill-body-manifest.json via
`npm run skills:generate` so the DB-backed skill bodies match the trimmed set.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
331ae11867
commit
c74b19df1b
@@ -153,6 +153,8 @@ export function BankReconciliationView() {
|
||||
const [applyLoading, setApplyLoading] = useState(false)
|
||||
const [linkLoading, setLinkLoading] = useState<string | null>(null)
|
||||
const [unlinkLoading, setUnlinkLoading] = useState<string | null>(null)
|
||||
// Per-verifikat loading for the "Märk som ingående balans" re-tag action.
|
||||
const [markLoading, setMarkLoading] = useState<string | null>(null)
|
||||
const [actionLoading, setActionLoading] = useState<string | null>(null)
|
||||
|
||||
// Opt-in: also surface vouchers already matched to a bank transaction as
|
||||
@@ -408,6 +410,34 @@ export function BankReconciliationView() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-tag a manual/import voucher that is really an ingående balans as
|
||||
* source_type='opening_balance'. Such a voucher (common after a migration
|
||||
* where the IB was booked as an ordinary verifikat) otherwise stays in the
|
||||
* period movement and shows up as a phantom difference equal to the IB. After
|
||||
* re-tagging it drops out of the diff and is surfaced as "IB — räknas inte".
|
||||
*/
|
||||
const handleMarkOpeningBalance = async (journalEntryId: string) => {
|
||||
setMarkLoading(journalEntryId)
|
||||
try {
|
||||
const res = await fetch('/api/reconciliation/bank/mark-opening-balance', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ journal_entry_id: journalEntryId }),
|
||||
})
|
||||
const result = await res.json()
|
||||
if (result.error) {
|
||||
setError(result.error)
|
||||
} else {
|
||||
await fetchAll()
|
||||
}
|
||||
} catch {
|
||||
setError('Kunde inte markera verifikationen som ingående balans')
|
||||
} finally {
|
||||
setMarkLoading(null)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline one-click booking for an unmatched transaction with no upstream
|
||||
* voucher to match against (ränteintäkter, bankavgifter, valutakurs-
|
||||
@@ -883,6 +913,9 @@ export function BankReconciliationView() {
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="mb-3 text-xs text-muted-foreground">
|
||||
Är en manuellt eller importerat bokförd verifikation egentligen en ingående balans? Markera den som IB — då räknas den inte med i avstämningen utan visas separat som ingående balans.
|
||||
</p>
|
||||
<table className="w-full text-sm">
|
||||
<thead className="[&_th]:font-medium [&_th]:text-[11px] [&_th]:uppercase [&_th]:tracking-wider [&_th]:text-muted-foreground">
|
||||
<tr className="border-b text-left">
|
||||
@@ -891,11 +924,13 @@ export function BankReconciliationView() {
|
||||
<th className="py-2">Beskrivning</th>
|
||||
<th className="py-2 w-28 text-right">Belopp</th>
|
||||
<th className="py-2 w-24">Typ</th>
|
||||
<th className="py-2 w-36"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{unmatchedGlLines.map((line) => {
|
||||
const amount = line.debit_amount > 0 ? line.debit_amount : -line.credit_amount
|
||||
const isRetaggable = line.source_type === 'manual' || line.source_type === 'import'
|
||||
return (
|
||||
<tr key={line.line_id} className="border-b last:border-0">
|
||||
<td className="py-2 font-mono text-xs">
|
||||
@@ -909,6 +944,20 @@ export function BankReconciliationView() {
|
||||
{formatCurrency(amount)}
|
||||
</td>
|
||||
<td className="py-2 text-xs text-muted-foreground">{line.source_type}</td>
|
||||
<td className="py-2 text-right">
|
||||
{isRetaggable && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8 text-xs"
|
||||
disabled={markLoading === line.journal_entry_id}
|
||||
onClick={() => handleMarkOpeningBalance(line.journal_entry_id)}
|
||||
title="Markera verifikationen som ingående balans — den utesluts då från avstämningen"
|
||||
>
|
||||
{markLoading === line.journal_entry_id ? 'Markerar…' : 'Märk som IB'}
|
||||
</Button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -594,7 +594,7 @@ function SkatteverketPanelInner({ periodType, year, period, hasData, rutor }: Sk
|
||||
)}
|
||||
|
||||
{/* Local pre-flight check results — surfaced separately from SKV's
|
||||
kontroller so the user knows these are gnubok's own sanity checks,
|
||||
kontroller so the user knows these are Accounted's own sanity checks,
|
||||
not Skatteverket's. ERRORs block the submit/validate buttons. */}
|
||||
{localChecks.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
|
||||
@@ -4,11 +4,11 @@ import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
/**
|
||||
* Tracks the last few report slugs the user opened, per company, in
|
||||
* localStorage. Mirrors the `gnubok:<key>:<companyId>` convention used by
|
||||
* localStorage. Mirrors the `Accounted:<key>:<companyId>` convention used by
|
||||
* FiscalYearSelector (STORAGE_KEY_PREFIX). Powers the "Senast öppnade" shelf
|
||||
* so returning users skip the library hop.
|
||||
*/
|
||||
const STORAGE_KEY_PREFIX = 'gnubok:report-recents:'
|
||||
const STORAGE_KEY_PREFIX = 'Accounted:report-recents:'
|
||||
const MAX_RECENTS = 4
|
||||
|
||||
export function useRecentReports(companyId: string | null | undefined) {
|
||||
|
||||
Reference in New Issue
Block a user