A tax return is not a report. A report is asked for and answers; a declaration leaves the building, an administration keeps a copy, money follows it, and months later somebody asks what was sent. This page is the whole cycle: what each step does, which function does it, and what is free and what is operated.
The short version: everything up to the transmission is open core, and
everything that comes back is too. What Ekwo sells in the middle is holding
the credentials, the certificate and the responsibility of sending on time —
one of the optional services of cloud-services.md, and
the only one this cycle needs.
The eight steps
| # | Step | The function | Free | Operated |
|---|---|---|---|---|
| 1 | Compute | vat_return() |
✅ | |
| 2 | Freeze | prepare_filing() |
✅ | |
| 3 | Produce the file | a brick of packages/formats/ |
✅ | |
| 4 | Send | — | ✅ by hand on the portal | the credentials and the deadline |
| 5 | Record what came back | file_filing(), record_filing_outcome() |
✅ | |
| 6 | Archive, and prove | attachments, record_filing_files(), ekwo proof stamp |
✅ | |
| 7 | Settle and pay | settle_filing(), auto_settle() |
✅ | the bank connection |
| 8 | Correct | reopen_filing(), supersede_filing() |
✅ |
1. Compute
vat_return(company, from, to, report_code) reads the ledger and answers box by
box. Which boxes there are, what each of them sums and which are computed from
others is pack data (tax_report.json); the function contains no country
rule. A figure belongs to the period its tax fell due in — tax_point_of() —
and not to the date of the entry, where a country says the two differ.
Nothing is stored. Asked twice, it answers twice, and after an entry lands in the period it answers differently. That is right for preparing and wrong for having filed, which is the reason for step 2.
2. Freeze
prepare_filing(company, from, to) computes the return and keeps the
answer, one row per box in tax_filing_boxes, identified by the box and by
what the box holds — a form is free to print a base and a tax on the same line.
From the moment the declaration leaves draft, a trigger refuses any change to
those figures. Called again on a draft, it refreshes; called on a declaration
that has gone, it refuses and names what is needed instead: a corrective.
filing_deadline() says when the return is due, where the pack carries the
rule; upcoming_filings() lists what is due between two dates. A pack that
declares no rule answers null rather than a date that would be wrong.
3. Produce the file
A brick of packages/formats/ turns the frozen
figures into the file one administration takes. The form says which brick
writes it, in tax_report_templates.file_format, named after the format and
never after the country. Bricks exist today for a periodic VAT return
(vat-consignment) and for recapitulative statements of supplies within the
European Union (intra-consignment, des, ecdf, vd), fed by
ec_sales_list().
Most forms have no brick, and that is not a blocker: filing by hand on a
portal is complete and free, and file_format stays null until somebody
writes one.
4. Send
This is the only operated step, and it is operated for a reason that has nothing to do with features: sending needs credentials, often a certificate, usually a session on a portal, and a channel somebody keeps running. Operating the channel is not filing on anyone's behalf: the content of the return, its date and the decision to send it stay with the business and its accountant.
A company that files by itself downloads the file and uploads it — that path is complete, and the two steps around it record what happened either way.
5. Record what came back
file_filing(filing, reference, at, channel, service) records the send as a row
of tax_filing_deposits: the channel (portal or service), the service's
name where there was one, and the deposit number the administration gave back.
record_filing_outcome(filing, state, reference, message) writes what came
back — accepted, rejected or paid — on the send it answers, with the
administration's own words. A declaration sent three times keeps its three
references and its refusals.
A refused declaration was never received. It is sent again, not corrected:
file_filing() accepts a rejected declaration, and each send is a new deposit.
Where the refusal is about the figures themselves, reopen_filing() takes the
declaration back to draft so they can be worked out again.
6. Archive
The file that was sent and the receipt are attachments of the declaration, and
the deposit names which is which (sent_file_id, acknowledgement_id).
record_filing_files(filing, sent, acknowledgement) names them on the last
send, under the right that records the send, and refuses by name a declaration
that was not sent, one a corrective replaced, and a file kept on anything but
this declaration. Nothing else writes a deposit: file_filing(),
record_filing_outcome() and record_filing_files() are the only way in, each
asking filings.write, and the table is read under filings.read. They
belong to the company: stopping a subscription takes away the sending, never the
proof of having sent.
They leave with it, too. A company that moves to another installation
(company-archive.md) arrives with its declarations in
the state they were in, the figures each was frozen with and the deposits that
prove it went.
A company may also prove a filed file existed on a given day:
ekwo proof stamp <file> --filing commits its SHA-256 to a public timestamp
ledger, and anybody holding the file can check it later without an account.
Only the hash leaves. filing-proofs.md is the detail.
7. Settle and pay
settle_filing(filing, credit, reference, contact, date) clears the tax
accounts the declared period moved on that form and carries the net to the
account the pack names — tax_payable, or tax_receivable where the period
ends in a credit and the chart keeps the two apart. One entry per declaration,
through post_entry(), and a unique index so replaying is refused by the
database. The debt matures on the filing's due_date, which prepare_filing()
writes from the pack's deadline rule.
What "on that form" means is filing_tax_movements(): a tax line whose box is
on the form, or a tax line with no box whose tax prints its base in a box of
the form — the form that computes the tax from the base. A tax the pack books
on a liability no box of the form carries — the buyer's self-assessed tax on a
service from abroad, a withholding, a second return of another authority —
stays on its account, for its own return or an entry the company writes; so
does a tax due on collection that still waits on its transition account. A
period in which nothing else moved has nothing to settle, and says so.
A credit is not settled until the company says what happens to it: carried to the next declaration, or claimed back. Both are ordinary, and the choice is not the schema's.
The debt is then an open item like any other, and it names the administration.
The pack says who that is (defaults.tax_payee); tax_payee(company) finds
the company's contact for it — the one contact that books to the account the
pack names — or creates it under the pack's name, in the company's language
where the pack gives one, and settle_filing() names it when its caller names
nobody. Give the entry the reference the payment will
quote, and auto_settle() matches the payment against it — by reference, like
any other payment.
When that debt is matched in full, by whatever path, the declaration is
paid: the outcome is written on it and on the send it answers, as
record_filing_outcome(…, 'paid') writes it, whoever matched the payment —
somebody who keeps the books without filing included. Undoing the matching
takes it back to accepted. A declaration whose debt was matched before this
existed was recorded paid by 20261023160000.
8. Correct
Two different things, and calling them by the same name is how books go wrong:
- A refusal — the administration never accepted it. Send again, or
reopen_filing()and redo the figures. - A corrective — the administration accepted it and the books have moved
since.
supersede_filing()opens a new declaration pointing at the old one, which becomessuperseded. Nothing is overwritten: what was sent stays as it was sent, and the corrective's settlement carries the difference and not the period all over again.
filings_touched_since() is what makes step 8 start on time: it lists the
declarations whose period the ledger moved after they were filed. A late entry
is not refused — a supplier invoice arrives when it arrives — but it is never
silent. A company that wants the period shut says so with lock_filed_period(),
which moves the tax lock forward only, and refuses while there are still tax
accounts to clear.
Schemes and instalments
Which declarations a company files at all is decided by its VAT scheme, and most countries have more than one. A normal scheme files a periodic return every month or quarter. A simplified scheme files one return a year and pays part of the tax on account during it. A small business exemption charges no VAT and files no periodic return. The scheme is a fact about the company, and the core never chooses it.
What a pack says. packs/<cc>/vat_schemes.json lists the schemes of the
country (packs.md): for each, the
forms filed and on which cadences, whether VAT is charged, the ceilings up to
which a company may be on it — an amount, a currency and the article that
sets it, never a figure without its text — and the instalments it asks for. A
pack without the file has no scheme, and its companies file as they always
did.
What a company says. A row of company_vat_schemes: this scheme, from the
first day of this financial year. There is no default. A company that has
recorded nothing keeps exactly the behaviour it had before schemes existed:
its periodic return, its cadences, no instalments.
What the scheme governs. Recording a scheme writes the cadences it implies
into company_filing_periods — the scheme's default for each form, or the
only cadence it allows — and removes the cadences of the forms it does not
file. The table remains the one place a cadence is written; the scheme checks
it, and a cadence it does not allow is refused (wrong_declaration_period).
vat_return() and prepare_filing() refuse, by name, a form the scheme does
not file (form_not_in_vat_scheme), and with no form named they take the one
the scheme files — or say that it files none (vat_scheme_files_no_return).
| Normal scheme | Simplified scheme | Exemption | |
|---|---|---|---|
| Forms | the periodic return | the annual return | none |
| VAT on sales | charged | charged | refused at posting (vat_charged_under_exemption) |
| Invoice mention | — | — | the pack's small_business mention |
| During the year | — | instalments | — |
An annual return restates the periodic one. The taxes of a pack post for
its periodic return. An annual form does not get a second set of postings: it
names the form whose postings it reads (reads_postings_of), and each of its
boxes lists the boxes of that form it adds up. Line 5A of the French CA12 is
what line 08 of the CA3 would have received over the year, and the two forms
can never disagree about a posting.
Instalments. A scheme that asks for payments on account declares them as a schedule: in which months, which percentage, of which box of the previous period's return, and the base under which nothing is due.
instalment_plan(company, from, to)says what is due on account of the declaration of a period: the month (and the day, where the law gives one day to everybody), the base read on the previous period's return asprepare_filing()froze it — or as the books compute it where nothing was frozen — the amount due, what was paid and what is outstanding.declaration_instalmentsrecords what was paid: the instalment, the day, the amount, and the entry that carried it where there is one.- The annual return deducts them in the box that reads them —
instalments_paid, orinstalments_duefor a form that deducts what was due whether paid or not — and its totals carry the balance. upcoming_filings()lists every instalment beside the returns, one row each, withinstalmentset to its number and the month it falls due in as its period.portfolio_upcoming_filings()does the same across companies,ekwo statusprints the scheme and the next instalments, and the MCP toolinstalment_planreads the plan.
A ceiling is an alert. vat_scheme_ceilings_crossed(company, from, to)
compares the turnover of the books with the ceilings of the scheme the company
is on. Nothing changes a scheme by itself: leaving one is a decision the
company records, from the first day of a financial year.
Several companies at once
Everything above takes a company. Whoever keeps several — a firm and its clients in one installation, which is the normal case — asks the two questions that matter across all of them:
| One company | Every company you may read | |
|---|---|---|
| What falls due | upcoming_filings(company, from, to) |
portfolio_upcoming_filings(from, to) |
| What moved after it went | filings_touched_since(company, from, to) |
portfolio_filings_touched_since(from, to) |
"Portfolio" is the word of the profession — a firm's client portfolio — and
firms.md defines it. It is not a list anybody maintains: it is the companies on which the
caller holds filings.read. An accountant of forty companies gets forty, the
person who runs one of them gets that one, and the administrator of the
instance — who creates companies and keeps no books — gets none.
Two things differ from the per-company readings. The window is on the day a
return is due, not on the period. And every company is in the answer: a
row with no date carries a reason — no_deadline_rule where the pack names
no day for the form, nothing_due, no_form — because a company missing from
the list a firm plans its fortnight on is worse than a company listed without a
date.
What each pack can do today
ekwo pack list prints one line per pack:
filing — BE-VAT-PERIODIC (31 boxes) · cadence: month · deadline: in the pack ·
file: vat-consignment · settles to: 451900
Every "no" is printed rather than left out: a return filed by hand on the
portal, with no file written for it, is the ordinary state of most packs, and a
deadline a pack has not declared is printed not declared. The same answers
are asserted end to end, pack by pack, by the test suite: the golden year is
replayed, the return computed and frozen, the file written and read back to the
cent where a brick exists, and the settlement posted where the pack names an
account.
Not modelled yet
- What a country does with a correction — a replacement return, an adjustment carried on the next period, a threshold below which nothing is filed. None of these is universal.
- Rounding the debt to the whole unit, which some administrations do.
- A working-day shift on a deadline, which needs a calendar of public holidays.
- A deadline counted in months after the period, which an annual return often has; such a form is listed without a date until the closed vocabulary of deadline rules carries it.
- Recording an instalment from a payment. A payment on account is recorded
in
declaration_instalments, by the caller; nothing matches a bank line to an instalment yet.