Several bank statements for one bank ledger
What changed, why it is safe, and what you will see in the app.
- Before: one bank ledger could take only one PDF per import.
- Now: many PDFs can share one bank ledger (for example March and April of HDFC).
- The risk: two PDFs that overlap print the same entries. If both are exported, Miracle books them twice.
- The fix: the app finds those repeated entries and brings each one in once.
- The app also warns you when a statement seems to be missing, or when two PDFs may belong to two accounts.
1. What changed for the user
April could not use HDFC. The app said “Already connected to another PDF — choose another”. You had to run a second import.
Both PDFs connect to HDFC in one import. You review all HDFC entries together.
2. The danger: overlapping statements
Banks often give statements that overlap. For example, you download March on 31 March, and then April from 30 March. The two days in the middle are in both PDFs.
March.pdf
| Date | Narration | Debit | Balance |
|---|---|---|---|
| 01/03 | NEFT ACME | +500.00 | 1,500.00 |
| 30/03 | RENT | 300.00 | 1,200.00 |
| 31/03 | SMS CHARGES | 10.00 | 1,190.00 |
April.pdf
| Date | Narration | Debit | Balance |
|---|---|---|---|
| 30/03 | RENT | 300.00 | 1,200.00 |
| 31/03 | SMS CHARGES | 10.00 | 1,190.00 |
| 02/04 | UPI SHOP | 90.00 | 1,100.00 |
amber rows are in both PDFs. If we only removed the old rule, the rent would go to Miracle twice.
This is why the old rule existed. Removing it safely needed the work below.
3. How the app finds a repeated entry
An entry in a later PDF is a copy of an entry in an earlier PDF when all of these are true:
- Both PDFs are connected to the same bank ledger.
- Same date.
- Same side (debit or credit) and same amount.
- Same reference (UTR, cheque no.). If the bank prints no reference, the same narration (spaces do not matter).
- If both rows print a balance, it is the same balance. The running balance is the strongest proof that two rows are one transaction.
One important exception
Two equal entries inside one PDF are never copies of each other. A bank can really charge ₹10 twice on one day. So the app counts:
| March.pdf has | April.pdf has | Result |
|---|---|---|
| 2 × “SMS ₹10” on 31/03 | 2 × “SMS ₹10” on 31/03 | 2 entries both of April’s are copies |
| 2 × “SMS ₹10” on 31/03 | 3 × “SMS ₹10” on 31/03 | 3 entries 2 copies + 1 new |
| — | 2 × “SMS ₹10” on 31/03 | 2 entries nothing removed |
The same PDF uploaded twice is all copies, so it adds nothing.
4. What happens to a copy
All copies are one transaction. It comes in once when any PDF’s date range brings it in. The app shows the copy that already has a saved ledger or contra pair (if any), otherwise the first one. The other copies are left out of everything:
| Place | What happens |
|---|---|
| Review steps (Contra, Suggestions, Suspense, Review) | The entry shows once. You choose its ledger once. |
| All entries | Listed once, under the first PDF that brings it in. |
| Export (Miracle voucher file) | Written once. no double booking |
| Contra (bank-to-bank transfers) | A transfer pairs with one copy only. A deposit paired with the other copy is still left out of the export, as before. |
| Check against Miracle’s books | Each Miracle posting matches one entry, never two copies. Before, two copies could fight for one posting, and the loser looked “not booked” and was written again. |
| Server: possible contra entries | Before, two copies looked like two candidates, so no transfer was suggested. Now copies count as one, and rejecting a suggestion covers every copy. |
5. What you will see in the app
Bank ledgers step
The ledger you already used is still in the list and is still suggested. A short note says what sharing does:
Review: a box above every step
It shows only when a bank has more than one PDF. Nothing in it stops you.
Tables now name the PDF
| Sr. no. | Bank | Date | Narration |
|---|---|---|---|
| 2 | HDFC 1234 · March.pdf | 30/03/2026 | RENT |
| 3 | HDFC 1234 · March.pdf | 31/03/2026 | SMS CHARGES |
| 3 | HDFC 1234 · April.pdf | 02/04/2026 | UPI SHOP |
- Sr. no. counts each PDF’s own rows. Before, it counted across the whole bank.
- The All entries switcher lists each PDF, not each bank.
- Alt+Enter (statement around an entry) shows only that entry’s PDF, so the Balance column does not jump between PDFs.
6. The four messages, and when they appear
| Message | When | What to do |
|---|---|---|
| Came in once | A PDF repeats entries of another PDF. | Nothing. This is information. |
| Balance gap | The balance at the end of one PDF is not the balance at the start of the next one. | A statement for the days between is probably missing. Add it. |
| No match | Two PDFs have entries on the same days, but not one entry matches. | Check that both are really this bank account. |
| Look-alike | An entry matches another PDF’s entry on everything except the balance. Both are kept. | Open All entries and check it. If it is a real repeat, map one of them to suspense or leave it out. |
7. Decisions I made (please check)
- Different balance = keep both, and warn. I chose not to remove an entry when its balance differs. Removing a real transaction is worse than showing a warning. You answer the question in the issue with this; change it if you prefer.
- Which copy is shown: the one that already has a saved choice (a contra pair first), so earlier work is not lost.
- Changing a choice clears the copies’ old choices. The server stores a ledger per entry, not per transaction. So when you set or clear the ledger of the entry shown, the app removes the old ledgers its hidden copies still hold. Otherwise an old ledger could come back later. Contra pairs are never touched.
- Same PDF twice is not refused. Every entry is a copy, so it adds nothing, and the box says so.
- One voucher file per PDF stays. Copies are left out of the later file.
- Copy finding runs in the app, not on the server. It needs no new API, so older servers and older installed apps are not affected. The rule is the same as the server’s rule for entries already exported.
- The box shows in the review only, not on the Bank ledgers list. The list does not load the entries, and loading them there would slow it down.
8. Limits
- The balance check needs balances. A PDF that prints no balance on its first or last day is not checked.
- A month with no transactions cannot be uploaded, so a date gap alone is not a warning. Only a balance that does not carry on is.
- If you type different From/To dates for two PDFs of one bank, an entry comes in when any of the two ranges includes it (once).
- Design note (recommended follow-up): the server keeps one choice per entry, but the review treats copies as one transaction. The app keeps them in step (it clears copies’ old choices and locks them while it does). The fully robust fix is for the server to know copy groups and save one choice per transaction in one step. The review flagged three variations of this; all are fixed, but the server change would remove the whole class.
- The Miracle books check now uses one entry per transaction. That part is covered by the full review tests, but it has no test with real company books.
9. How it was checked
- Frontend: 492 tests pass (23 new ones for the copy finder, full review tests with two overlapping PDFs, a test that a saved choice is kept, a test that old copy choices are cleared, and an export test for transfers). Lint and build are clean.
- Server: all tests pass, including the database tests, and
go vet. New tests cover copies in contra suggestions. - Not done: no test in the desktop app with real customer PDFs. Nothing is pushed or deployed.
10. Independent review (OpenQodex)
Six review rounds. Each finding was fixed and tested. The last round: Passed: no findings
| Round | Found | Fix |
|---|---|---|
| 1 | A saved choice on the hidden copy was lost. A copy outside its date range was not linked, so a transfer could be written twice. | Show the copy that holds the choice. Link every copy, in range or not. |
| 2 | A copy without a balance could merge two different transactions. A cleared choice could come back through another copy. | Remember a balance once a copy prints it. Clear copies’ old choices. |
| 3 | Server grouped two different withdrawals as copies. False “no match” warning with three PDFs. | Server also needs same narration and balance. Compare copy groups. |
| 4 | False gap warning when one PDF is inside another. | Compare with the PDF that reaches latest. |
| 5 | A race: a new choice could be deleted by the cleanup. | Lock the copies until the cleanup ends. |
| 6 | No findings. | |
The Go linter (golangci) failed to start in every round. That is a tool problem, not a code finding.
11. Where the code is
| File | What it does |
|---|---|
frontend/src/lib/statement-copies.ts | New. Finds copies and checks the balance chain. |
frontend/src/lib/use-statement-entries.ts | Hides copies, one entry per transaction for the books check, Sr. no. per PDF. |
frontend/src/components/same-bank-notes.tsx | New. The box in the review. |
frontend/src/components/statement-import.tsx | Removes the one-PDF rule; adds the “also connected” note. |
frontend/src/lib/voucher-export.ts | A transfer paired with a copy is still left out once. |
server/contra_suggest.go | Copies count as one candidate for possible contra entries. |
docs/statement-import.md and others | Updated docs. |