Skip to content

Return data

Return data

The ReturnAggregator rolls a set of assessments up into return-data — net and tax totals per taxing jurisdiction and currency — ready for filing. The engine owns the aggregation; submitting to each authority (e.g. via the UK MTD API or an EU OSS portal) is the host's concern.

A taxing jurisdiction is a country plus, where the tax is sub-federal, its subdivision. So a US set produces a line per state and an EU OSS set produces a line per member state — the granularity a filing actually needs — rather than collapsing everything to the country.

use Cbox\Tax\Contracts\ReturnAggregator;

$return = app(ReturnAggregator::class)->aggregate($assessments);

foreach ($return->lines as $line) {
    $line->country->value;                       // "US"
    $line->subdivision?->value;                  // "US-CA" (null for national lines)
    $line->currency;                             // "USD"
    (string) $line->net->getAmount();            // "200.00"
    (string) $line->tax->getAmount();            // "14.50"
    $line->count;                                // number of supplies
}

$return->lineFor(new CountryCode('DK'), 'EUR');                          // national line
$return->lineFor(new CountryCode('US'), 'USD', new SubdivisionCode('US-CA')); // per-state line

A bare-country lineFor() matches only a national (subdivision-less) line — a US state line is not returned unless you pass its subdivision.

Money of different currencies is never mixed — each currency is its own line, so a jurisdiction billed in more than one currency yields more than one line. Summing uses exact Money::plus, so aggregation introduces no rounding remainder.

Per-authority totals

A jurisdiction total is not what gets remitted in a stacked state. Kansas is paid as a state figure and a city figure, to different authorities, and a line that only said "US-KS: $81.25" left you to rebuild the split by hand from the individual assessments — the one piece of arithmetic on a signed return that should never be done twice.

foreach ($line->authorities ?? [] as $authority) {
    $authority->level;                       // JurisdictionLevel::State | County | City | …
    $authority->label();                     // the name, else the code, else the level
    (string) $authority->tax->getAmount();   // what this authority is owed for the period
}

authorities is null when the split cannot be known, and that is the only safe reading of it — treat it as missing data, never as "one authority took everything". It is null when any taxed supply in the period arrived without a rate breakdown, or carried an authority share with neither a code nor a name: you cannot remit to an authority you cannot identify.

That is stricter than the equivalent on a single document, deliberately. A document breakdown is there to be read; a return is signed and paid from. A partial roll-up would still add up to a plausible figure, which is exactly what makes the omission invisible.

The jurisdiction-level net, tax and count on the line stay correct either way — only the split goes unreported.