The "Import a first file" step counted `imported_files` rows in status
`completed`. `executeImport` writes that row before its first insert, so
an import that added nothing (a file holding only its header, or one
whose rows were all in error in the preview) ticked the step: the Import
card then said "Done." at rest and sent the user to /transactions, which
had nothing to categorize and led back to the import.
The counter now keeps a completed file only when a transaction points at
it (`EXISTS` on `transactions.file_id`, covered by
`idx_transactions_file`). Replayed with sqlite3 on
`consolidated_schema.sql`: 0 after an import without a valid row, 0
before a successful import's first insert and 1 after, 0 once the file
turns to `error`. Duplicates only change nothing: their transactions
came with an earlier completed file, and a restore writes its own.
The tests run without SQLite, so the rule is pinned on the query, and
the Import page is pinned at rest after such an import: step 4 again,
on the first source card, without "Done.".
Review of #398 (PR #404), option A chosen by Max.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add src/services/dataEvents.ts (ADR 0022): notifyDataChanged() and
subscribeDataChanged(), without React, on the model of logService.subscribe.
A throwing subscriber is logged and does not stop the others.
Notify after every successful write of the tables the getting-started
counters read, plus the import folder: setImportFolder, createSource,
deleteSource, createImportedFile, updateFileStatus, both import deletions,
insertBatch, updateTransactionCategory, applyKeywordWithReassignment (after
COMMIT), the four budget writes and runRestore (after COMMIT, one point for
the three restore modes). executeImport notifies once more when it ends,
succeeded or not. A failed write notifies nothing; transactional writes
notify once withTransaction has returned. applyTemplate notifies once for
the whole template through a private writeBudgetEntry helper.
Each service suite proves the notification after a success and its absence
after a failure; executeImport is driven as a plain function with React's
hooks stubbed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue #142 / Bilan #4 — non-regressive transfer awareness in the
transactions table + clean error mapping on bulk delete.
- `TransactionTable.tsx`: optional new prop
`linkedTransfersByTxId?: Map<txId, links[]>`. When supplied, a small
`<Link2>` icon appears next to the description for every linked
transaction; tooltip lists the account name(s) and direction(s).
Without the prop, the table renders byte-for-byte identical to
before — preserves the spec's non-regression invariant.
- `TransactionsPage.tsx`: loads the linked-transfers map once on mount
via `listAllLinkedTransfersForTooltip()` (one batch SELECT) and
threads it through to the table. Failure to load the map degrades
gracefully to an empty map (icon simply doesn't appear).
- `transactionService.ts`: new `deleteTransaction(id)` helper +
`TransactionLinkedToBalanceError` (typed FK guard). Pre-checks
`balance_account_transfers` before attempting the DELETE so the
error carries the offending account names; falls back to the FK
pattern matcher if a race linked the transaction between the
SELECT and the DELETE.
- `importedFileService.ts`: both bulk delete paths
(`deleteImportWithTransactions`, `deleteAllImportsWithTransactions`)
now pre-check for any linked transfer and surface the same typed
error before they would explode on FK RESTRICT. The pre-check has
a `LIMIT 50` safety cap on the global path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Change imported_files UNIQUE constraint from (source_id, file_hash) to
(source_id, filename) so files with identical content but different names
each get their own record. Update createImportedFile to look up existing
records by filename instead of hash.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change all currency formatters from EUR/fr-FR to CAD/en-CA
- Add onProgress callback to insertBatch for real-time progress bar updates
- Replace binary skip/include duplicates with per-row checkboxes and type badges (DB/batch)
- Clean up orphaned import_sources when deleting imports with no remaining files
- Filter dashboard recent transactions to show expenses only
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make duplicate file detection cross-source by removing sourceId from
existsByHash query. Add import history table below source list with
per-import and delete-all functionality.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- importedFileService now upserts: if file hash already exists for a
source (e.g. from a previous failed import), it updates the existing
record instead of hitting the UNIQUE constraint.
- Replaced Tailwind amber/red/emerald colors with the app's CSS
variables (--negative, --positive, --accent) for proper contrast
on the cream background theme.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>