fix(transactions): steer duplicate guard to matching for ledger-only vouchers (#958)
When the booking-time duplicate guard flags a ledger-only voucher candidate (transaction_id null: a verifikat from an SIE import, a paid invoice, a salary payout), the dialog's primary action is now "Matcha mot verifikatet": it links the bank transaction to the existing voucher via /api/reconciliation/bank/link (the same path MatchVoucherDialog uses) instead of double-booking the same affarshandelse. "Bokfor anda" stays available but demoted to an outline button. Sibling-transaction candidates keep today's layout: matching a second bank line onto a voucher that already has one is the N:1 edge case, not the default. Wired through both call sites: the transactions page (runCategorize) reuses handleVoucherLinked's refresh, and TransactionBookingDialog (JournalEntryForm, /api/transactions/[id]/book path) reuses the booked flow so in-dialog attached documents land on the matched verifikat and the row leaves the list, with a "Bankhandelsen kopplad" toast instead of "Bokford". No lib change: the candidate payload already carries the transaction_id discriminator, covered by existing detection tests. Closes #919. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d7e110b6b2
commit
82c66739d7
@@ -26,7 +26,7 @@ import { TemplateForm } from '@/components/settings/TemplateForm'
|
||||
import CreatePeriodDialog from '@/components/bookkeeping/CreatePeriodDialog'
|
||||
import { ActivateAccountsDialog } from '@/components/bookkeeping/ActivateAccountsDialog'
|
||||
import { AddAccountDialog } from '@/components/bookkeeping/AddAccountDialog'
|
||||
import DuplicateBookingDialog from '@/components/transactions/DuplicateBookingDialog'
|
||||
import DuplicateBookingDialog, { type DuplicateMatchTransaction } from '@/components/transactions/DuplicateBookingDialog'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import {
|
||||
useSubmitWithAccountActivation,
|
||||
@@ -82,6 +82,13 @@ interface Props {
|
||||
editEntryId?: string
|
||||
/** Fired after a successful draft edit (editEntryId path). */
|
||||
onUpdated?: () => void
|
||||
/** The bank transaction being booked (set by TransactionBookingDialog).
|
||||
* Enables the duplicate guard's "Matcha mot verifikatet" action for
|
||||
* ledger-only voucher candidates. */
|
||||
duplicateMatchTransaction?: DuplicateMatchTransaction
|
||||
/** Fired after the duplicate guard's match action links the transaction to
|
||||
* the existing voucher (no new entry was created). */
|
||||
onDuplicateMatched?: (journalEntryId: string) => void
|
||||
}
|
||||
|
||||
const BLANK_LINE: FormLine = { account_number: '', debit_amount: '', credit_amount: '', line_description: '' }
|
||||
@@ -101,6 +108,8 @@ export default function JournalEntryForm({
|
||||
bare,
|
||||
editEntryId,
|
||||
onUpdated,
|
||||
duplicateMatchTransaction,
|
||||
onDuplicateMatched,
|
||||
}: Props) {
|
||||
const { canWrite } = useCanWrite()
|
||||
const { toast } = useToast()
|
||||
@@ -1939,6 +1948,15 @@ export default function JournalEntryForm({
|
||||
processing={isSubmitting}
|
||||
onCancel={() => setDuplicateCandidate(null)}
|
||||
onBookAnyway={handleBookAnyway}
|
||||
matchTransaction={duplicateMatchTransaction ?? null}
|
||||
onMatched={
|
||||
onDuplicateMatched
|
||||
? (_transactionId, journalEntryId) => {
|
||||
setDuplicateCandidate(null)
|
||||
onDuplicateMatched(journalEntryId)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user