Find UK VAT reverse-charge purchase invoices sitting in Sage 50 at the wrong tax code, and calculate the Box 1 / Box 4 VAT return adjustment to correct it.
Project description
rcvatkit
Finds UK VAT reverse-charge purchase invoices sitting in Sage 50 at the wrong tax code, and calculates the VAT return manual adjustment needed to correct it.
If this saves you time, consider buying me a coffee.
What problem this solves
If you receive foreign-currency (non-GBP) supplier invoices and use an
OCR/import tool such as AutoEntry, Dext, or Hubdoc, there's a good
chance those invoices are landing in Sage at the default "no VAT" tax
code (often T0) instead of the reverse-charge code Sage expects
(often T18). HMRC needs Boxes 1 and 4 of your VAT return to reflect
these transactions correctly.
Sage 50 won't let you post the reverse-charge code on a manual journal ("This tax code cannot be used for Journal transactions"), so the fix isn't a journal at all -- it's a manual adjustment on the VAT return itself: add the same amount to Box 1 (output VAT) and Box 4 (input VAT). The net effect on your VAT bill is nil, but the boxes are populated the way HMRC expects.
This toolkit finds the transactions and does the arithmetic. It does not touch Sage or submit anything -- applying the adjustment is a manual step in Sage's VAT Return "Adjustments" screen.
Features
- Finds non-GBP purchase invoices/credits not yet coded with your reverse-charge tax code, using sagekit for the Sage 50 ODBC connection and its column auto-detection
- Calculates the Box 1 / Box 4 adjustment amount
- Built-in helper for UK VAT "stagger groups" -- works out which VAT quarter just finished without you having to calculate the dates by hand
- One worked example, ready to adapt: a CLI script with date prompts, a results table, and a saved log file
Installation
pip install -r requirements.txt
or, to install as an editable package:
pip install -e .
This installs sagekit automatically -- rcvatkit builds on it rather than reimplementing the Sage connection. You'll also need the Sage Line 50 ODBC driver (installed alongside Sage 50 itself) and Sage 50 running while you query it -- see sagekit's README for how to find your DSN.
Configuration
Copy .env.example to .env if you want to pre-fill values:
cp .env.example .env
SAGE_DSN=SageLine50v33
SAGE_UID=MANAGER
SAGE_PWD=
RCVAT_STAGGER=1
RCVAT_TARGET_CODE=T18
RCVAT_VAT_RATE=0.20
.env is already in .gitignore -- never commit real credentials.
Which VAT stagger group are you in?
HMRC assigns every VAT-registered business to one of three stagger groups, which determine which months your VAT quarters end in. Check your VAT registration certificate or HMRC online account if you're not sure.
| Stagger | Quarters end |
|---|---|
| 1 | Mar / Jun / Sep / Dec |
| 2 | Jan / Apr / Jul / Oct |
| 3 | Feb / May / Aug / Nov |
Usage
from datetime import date
from sagekit import connect_or_exit
from rcvatkit import find_reverse_charge_candidates, compute_adjustment, last_completed_quarter
conn = connect_or_exit()
cursor = conn.cursor()
# Work out the last completed quarter automatically...
date_from, date_to = last_completed_quarter(stagger=1)
# ...or just pass your own dates:
# date_from, date_to = date(2026, 1, 1), date(2026, 3, 31)
lines = find_reverse_charge_candidates(cursor, date_from, date_to)
adjustment = compute_adjustment(lines)
print(f"{len(lines)} invoice line(s) found, net GBP {adjustment['net_gbp']:.2f}")
print(f"Add £{adjustment['box_1']:.2f} to Box 1 AND Box 4")
conn.close()
Example script
python examples/check_reverse_charge.py
Prompts for a date range (defaulting to the last completed quarter for your stagger group), connects to Sage, prints a table of the affected invoices grouped by supplier plus the Box 1/Box 4 adjustment, and saves everything to a timestamped log file alongside the script. Copy and adapt it -- it's a starting point, not a fixed report.
Concept reference
| Concept | What it means |
|---|---|
| Reverse charge | The VAT treatment HMRC requires for certain foreign-currency purchases -- both output and input VAT are recorded, netting to nil but keeping Boxes 1 and 4 accurate |
T18 |
Sage's common default reverse-charge tax code (yours may differ -- check your Sage tax code list) |
| Stagger group | Which three months your VAT quarters end in -- see table above |
AUDIT_HEADER / AUDIT_SPLIT / PURCHASE_LEDGER |
The three Sage ODBC tables this toolkit joins to find purchase invoices, their tax code, and the supplier's currency |
A Sage ODBC quirk worth knowing
Sage's ODBC driver rejects INNER JOIN across three or more tables
("Invalid outer join specification"). find_reverse_charge_candidates
uses the older implicit (comma) join style for exactly this reason --
if you're adapting the SQL yourself and add a fourth table, keep using
commas rather than INNER JOIN.
Also, Sage's ODBC currency IDs are offset by one from what the Sage 50
UI dropdown shows (UI "0 -- Pound Sterling" is ODBC value 1, UI
"1 -- US Dollar" is ODBC value 2, and so on). base_currency_id
defaults to 1 (the near-universal UK base currency ID), but if your
own install differs, pass your own value -- run
SELECT DISTINCT CURRENCY FROM PURCHASE_LEDGER via sagekit to check.
Testing
pip install pytest
pytest
Tests use a fake cursor object, so they run offline without a real Sage install, ODBC driver, or sagekit dependency needing to connect anywhere.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Invalid outer join specification |
SQL changed to use INNER JOIN across 3+ tables |
Use the comma-join style -- see above |
Column not found / COLUMN DETECTION ERROR |
Column name varies by Sage version | Check the error message's "Available" list against your install |
| No rows returned | Wrong date range, or no non-GBP suppliers in that period | Confirm Sage is open, widen the date range, check RCVAT_TARGET_CODE matches what your install actually uses |
| Adjustment looks too big/small | Wrong RCVAT_VAT_RATE or wrong base_currency_id |
Confirm your VAT rate and check PURCHASE_LEDGER.CURRENCY values for your GBP accounts |
This tax code cannot be used for Journal transactions |
Expected -- you tried to journal the reverse charge instead of using a manual VAT return adjustment | Use the Adjustments screen on the VAT return, not a journal |
Notes
- This calculates the adjustment; it does not post anything to Sage or submit your VAT return. Always sanity-check the figures before applying them.
- Not tax advice -- if you're unsure whether reverse charge applies to your purchases, check with HMRC guidance or your accountant.
License
MIT -- see LICENSE. Do whatever you like with this; a credit or a Ko-fi tip is appreciated but never required.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rcvatkit-0.1.0.tar.gz.
File metadata
- Download URL: rcvatkit-0.1.0.tar.gz
- Upload date:
- Size: 11.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b156522266226d13d882edde66cd83a85d83c598bdd8b375d03678b3aff07c74
|
|
| MD5 |
90eadcf8623776aa7ca4c2f8840f1349
|
|
| BLAKE2b-256 |
ab5921314ac2d7b72b8ecaea6dbf74b41753c18b334b0c01044cefcbd0eb1d24
|
File details
Details for the file rcvatkit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: rcvatkit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ab3ac57f8a0cf8741c72c54c6697f4e67bda7b2202fa444b35456df70def5ba
|
|
| MD5 |
57f2864b66ecda903c39e4a4d100961c
|
|
| BLAKE2b-256 |
02c6fc2219fdf465cb85098709d76ff364a1bfdf41289aef1874a64913eafe7d
|