Your supplier sends the same invoice twice. Once as a photocopy, once as a phone photo. The first one has GSTIN 27AAPFU1234A1ZF. The second has 27AAPFU1234A1ZE. One digit is different because the second scan is blurrier. Both have the same invoice number. Both get posted. You pay twice.
This is not a hypothetical. It happens in every AP department that processes scanned invoices. The duplicate is not always identical. Sometimes the GSTIN is slightly different. Sometimes the total is rounded differently. Sometimes the date is typed as 15/01 instead of 01/15. The duplicate is there, but it does not look like a duplicate.
Three kinds of duplicates
| Type | What it looks like | How to catch it |
|---|---|---|
| Exact duplicate | Same invoice number, same GSTIN, same amount | Simple uniqueness check |
| Near-duplicate | Same invoice number, different GSTIN (typo in second scan) | Manual review; the constraint only fires on the same GSTIN |
| Amount split | Same supplier, same date, amounts that sum to the original | Supplier + date + amount pattern |
The exact duplicate is easy. Conditional uniqueness catches it: same invoice number and same seller GSTIN cannot become a second row. On ingest the existing row is updated; the manual create path returns 409.
The near-duplicate is harder. The invoice number is the same, but the GSTIN is different. This happens when the same invoice is scanned twice, and the second scan is blurrier. The OCR reads a different digit in the GSTIN. The result is two rows with the same invoice number but different GSTINs. The conditional constraint allows this because the GSTINs differ. The system does not run a separate invoice-number-only duplicate flag today; a person has to spot it.
The amount split is the rarest. The supplier sends two invoices that together cover the original amount. Maybe the original was for ₹100,000 and the supplier sent two invoices for ₹60,000 and ₹40,000. Or maybe the supplier corrected a mistake and sent a credit note. These are not true duplicates, but they need to be investigated.
How EntryLedger catches them
EntryLedger uses conditional uniqueness. The database constraint is: the same invoice number plus the same seller GSTIN is a duplicate. But if the GSTIN is empty (unidentified invoice), multiple rows can coexist. This handles both the exact duplicate and the near-duplicate where the GSTIN was mistyped in one scan.
The conditional part is important. Not every invoice has a clear GSTIN. Some scans are too blurry to read the GSTIN. Some invoices from small suppliers do not carry a GSTIN at all. If the uniqueness check required a GSTIN, these invoices would be blocked even when they are legitimate. By making the constraint conditional (GSTIN must match only if both are non-empty), EntryLedger handles the real-world messiness of scanned invoices.
When the same invoice number and seller GSTIN already exist for the tenant, the new read is merged into that row on ingest, and the manual create endpoint returns 409. There is no separate duplicate review screen and no side-by-side comparison yet. The same file bytes are skipped before extraction. A near-duplicate with a different GSTIN is not caught by the constraint.
Why this matters
Duplicate invoices cost money. If you pay the same invoice twice, the cost is the full invoice amount. If you post the same invoice twice to Tally, your books are wrong. The reconciliation shows a discrepancy. Someone spends time tracing it. The time costs money.
The cost is not just financial. Duplicate entries distort your AP aging. They inflate your expenses. They make your financial statements inaccurate. At audit time, duplicate entries raise questions. The questions take time to answer. The answers cost money.
Catching duplicates at entry time is cheap. Catching them at audit time is expensive. The difference is a few seconds of validation versus hours of investigation.
What this looks like in practice
An invoice comes in as a photocopy. The invoice number and seller GSTIN are extracted. EntryLedger checks whether both already exist for the tenant. If they do, the database refuses a second row: ingest merges the new read into the existing row, and the manual create endpoint returns 409. No side-by-side duplicate screen exists today, so a near-duplicate with a different GSTIN still needs a person to spot it.
The process takes seconds. The duplicate is caught at entry time, before it reaches Tally. The books stay clean. The reconciliation stays clean. The audit stays clean.
What Tally will and will not save you from
Tally can reject a duplicate voucher number in some setups. It will not help if the second scan got a different invoice number because OCR read O as 0. It will not help if the clerk “fixed” a failed import by changing the number. Detection has to happen on the scan identity (number + seller GSTIN) before export, not after a human has already improvised.
Empty GSTIN is a special case. Unidentified invoices must be allowed to sit in the queue without crashing uniqueness. If you make GSTIN mandatory in the database, you either invent GSTINs (criminal) or you cannot ingest the scan. Conditional uniqueness is the boring engineering that matches paper. Block when both keys are present and equal. Allow when a key is still missing. Review the missing ones.
Payment runs are where duplicates become cash. AP that posts twice and pays twice is a recovery project. AP that refuses the second row is a click. Build the click. Do not wait for the bank statement.
If your volume is small and one person remembers every invoice, you may not need software for this. If two clerks share a pile and email-in is on, you do. Memory does not scale to 40,000 pages. Constraints do.
- EntryLedgerentryledger.shop
Duplicate handling: same invoice number and seller GSTIN, when both are present. Empty GSTIN rows are not forced unique.
How does duplicate detection work?+
Conditional uniqueness: same invoice number + seller GSTIN = duplicate. Empty GSTINs coexist.
What happens when a duplicate is found?+
An identical re-send is merged into the one row, silently. A re-arrival with different figures is held for review with a duplicate reason naming the field that changed, so the earlier amounts are never quietly replaced. The create endpoint returns 409 for a second row with the same number, supplier and type.