55 aggregations filter on a placeholder that does not exist, and return empty over live data
This is #1216 again, ×55.
That issue was closed by fixing one aggregation; the same defect was left in
every other declaration that scopes by administration.
Each of these declares:
"filter": { "administrationId": "@self.administrationId" }
@self.administrationId is not a placeholder OpenRegister implements.
PlaceholderResolver::resolve() only acts on values beginning with $ (it
implements $currentUser and the date expressions); a value starting with @
fails str_starts_with($value, '$') and is returned unchanged. So the filter
that actually runs is the literal string comparison
administrationId = '@self.administrationId', which matches nothing.
Measured
On a live instance, against GLLine (which has rows):
| query |
rows |
objects/shillinq/GLLine?_limit=1 |
3 |
objects/shillinq/GLLine?administrationId=@self.administrationId |
0 |
aggregations/shillinq/GLLine/byCostCenter |
"groups": [] |
Nothing errors. A filter on a value nothing holds returns an empty set with
HTTP 200, which renders as the page's own empty state and reads as "no data
yet" — exactly how #1216 hid.
The design answer
Administrations are a shillinq-specific layer over OpenRegister's organisation
tenancy. Being shillinq-specific, an administration is not @self metadata —
@self is OpenRegister's own envelope. administrationId is a normal
property, and it should be filtered like any other property, by the caller.
That is what the fix for #1216 did, and it is the pattern to apply:
- remove
administrationId from the declaration's filter;
- the caller passes it as a narrowing filter —
?filter[administrationId]=<active> — which
openregister#2852
accepts and can never relax.
⚠️ Order matters, and getting it backwards is a tenant leak. Today these
return nothing, which is the safe direction. Removing the declared filter
without a caller that supplies one turns "returns nothing" into "returns every
administration". Update the caller first, or land both in one commit.
The join half comes free
15 of these also declare a join, and in every one of the 15 the gap is
administrationId and the joined schema declares it too. Those do not need
anything added to join.filter — a tenant cannot be hardcoded there, it varies
per caller — because
openregister#2869
forwards the caller's narrowing filter to the joined aggregate, restricted to
keys the joined schema declares. So step 2 above scopes both halves.
(A further 7 join declarations have parent filter keys absent from
join.filter, but the joined schema does not declare those keys, so there is
nothing to mirror and no gap.)
Callers
Some have one, some do not. An aggregation with no caller must not simply
have its filter deleted — it would be left open for whoever adds the first
caller.
| aggregation |
caller |
TrialBalanceLine.trialBalanceByAccountPeriod |
lib/Service/TrialBalanceService.php |
TrialBalance.trialBalanceByAccount |
lib/Service/TrialBalanceService.php |
QuarterlyTaxStatement.quarterlyTaxStatement |
lib/Service/TaxReportService.php |
BudgetLine.budgetVsActuals |
lib/Service/BudgetGridReader.php |
VatReturn.rubrieken |
lib/Reporting/Generator/VatReturnReportGenerator.php |
GLLine.byCostCenter / byCostCenterHierarchy / byProject / byAnalyticalDimension |
src/views/bookkeeping/dimensions/SegmentPnLDashboard.vue |
BbvAccountMapping.byProgrammaCode / byAutorisatieniveau, GLLine.byCostObject |
none found |
Full inventory
Total: 55 aggregations across 22 fragment files.
30-bookkeeping-ib-aangifte-zzp.json — 2
IBWinstOpgave.costOfRevenue
IBWinstOpgave.revenueExcludingVat
add-shillinq-bbv-compliance.json — 2
BbvAccountMapping.byAutorisatieniveau (declares a join)
BbvAccountMapping.byProgrammaCode (declares a join)
add-shillinq-bookkeeping-operations.json — 5
BbvAccountMapping.byTaakveld
BcfClaim.compensableVatByAccount
IcpStatement.icpTransactions
SchatkistPosition.position
VatReturn.rubrieken
bookkeeping-btw-oss-eu.json — 2
OssReturn.linesByCountryRate
OssThresholdCounter.b2cEuTurnoverByYear
bookkeeping-cost-centers-dimensions.json — 5
GLLine.byAnalyticalDimension (declares a join)
GLLine.byCostCenter (declares a join)
GLLine.byCostCenterHierarchy (declares a join)
GLLine.byCostObject (declares a join)
GLLine.byProject (declares a join)
bookkeeping-detachering-payroll-administratie.json — 1
Payroll.annualEmployeeDeductions
bookkeeping-icp-opgaaf.json — 1
IcpSupply.icpLedgerByBuyerSupplyType
bookkeeping-ifrs-rj-dual-gaap.json — 1
ReconciliationBridge.bridgeByPeriodStandard
bookkeeping-ifrs15-revenue.json — 1
RevenueWaterfall.revenueWaterfallByContractPeriod
bookkeeping-innovatiebox-administratie.json — 1
IBProfitAttribution.innovatieboxAdministratie (declares a join)
bookkeeping-intercompany-elimination.json — 2
CounterpartyBalance.counterpartyBalanceByPair
IntercompanyMatch.matchByRelationPeriod
bookkeeping-kor-kleine-ondernemersregeling.json — 1
KORAnnualTurnover.korTurnoverByYear
bookkeeping-programmabegroting.json — 2
Programma.programmaRollup
Programmabegroting.sluitendByBegroting
bookkeeping-rechtmatigheidsverantwoording.json — 2
Rechtmatigheidsbevinding.foutenPerBoekjaar
Rechtmatigheidstoets.clusteringPerLeverancier
bookkeeping-sbr-xbrl-reporting.json — 4
SBRDocumentType.glBalanceDelta
SBRDocumentType.mappingCoveragePercent
SBRDocumentType.unmappedAccountCount
SBRDocumentType.unmappedAccountList
bookkeeping-trial-balance.json — 1
TrialBalanceLine.trialBalanceByAccountPeriod (declares a join)
bookkeeping-vpb-corporate-tax.json — 1
QuarterlyTaxStatement.quarterlyTaxStatement (declares a join)
bookkeeping-waterschappen-bbv-variant-02-aggregation-compliance.json — 2
BBVProgramme.totalBudget
BBVProgramme.ytdSpend (declares a join)
bookkeeping-wet-fido-treasury.json — 1
KasgeldLimiet.kasgeldRolling3Month
budget-core-schema.json — 1
BudgetLine.budgetVsActuals (declares a join)
inventory-stock-movement-ledger.json — 3
StockMove.movesByType
StockMove.netQuantityForLocation
StockMove.reservedQuantityForLocation
shillinq_register.json — 14
BalanceSheet.balanceSheetTotals (declares a join)
IbAangifteExport.costs
IbAangifteExport.revenue
IcpStatement.lines
Iv3Export.buckets (declares a join)
KorRegime.ytdRevenue
SisaReport.findingCounts
SisaReport.onTimeSettlement
TrialBalance.trialBalanceByAccount (declares a join)
TrialBalance.trialBalanceTotals
VatReturn.rubrieken (declares a join)
ZzpDeduction.taxableProfit
ZzpDeduction.ytdQualifyingHours
kernGegevensConfig.kerngegevensRatios (declares a join)
55 aggregations filter on a placeholder that does not exist, and return empty over live data
This is #1216 again, ×55.
That issue was closed by fixing one aggregation; the same defect was left in
every other declaration that scopes by administration.
Each of these declares:
@self.administrationIdis not a placeholder OpenRegister implements.PlaceholderResolver::resolve()only acts on values beginning with$(itimplements
$currentUserand the date expressions); a value starting with@fails
str_starts_with($value, '$')and is returned unchanged. So the filterthat actually runs is the literal string comparison
administrationId = '@self.administrationId', which matches nothing.Measured
On a live instance, against
GLLine(which has rows):objects/shillinq/GLLine?_limit=1objects/shillinq/GLLine?administrationId=@self.administrationIdaggregations/shillinq/GLLine/byCostCenter"groups": []Nothing errors. A filter on a value nothing holds returns an empty set with
HTTP 200, which renders as the page's own empty state and reads as "no data
yet" — exactly how #1216 hid.
The design answer
Administrations are a shillinq-specific layer over OpenRegister's organisation
tenancy. Being shillinq-specific, an administration is not
@selfmetadata —@selfis OpenRegister's own envelope.administrationIdis a normalproperty, and it should be filtered like any other property, by the caller.
That is what the fix for #1216 did, and it is the pattern to apply:
administrationIdfrom the declaration'sfilter;?filter[administrationId]=<active>— whichopenregister#2852
accepts and can never relax.
The join half comes free
15 of these also declare a
join, and in every one of the 15 the gap isadministrationIdand the joined schema declares it too. Those do not needanything added to
join.filter— a tenant cannot be hardcoded there, it variesper caller — because
openregister#2869
forwards the caller's narrowing filter to the joined aggregate, restricted to
keys the joined schema declares. So step 2 above scopes both halves.
(A further 7 join declarations have parent filter keys absent from
join.filter, but the joined schema does not declare those keys, so there isnothing to mirror and no gap.)
Callers
Some have one, some do not. An aggregation with no caller must not simply
have its filter deleted — it would be left open for whoever adds the first
caller.
TrialBalanceLine.trialBalanceByAccountPeriodlib/Service/TrialBalanceService.phpTrialBalance.trialBalanceByAccountlib/Service/TrialBalanceService.phpQuarterlyTaxStatement.quarterlyTaxStatementlib/Service/TaxReportService.phpBudgetLine.budgetVsActualslib/Service/BudgetGridReader.phpVatReturn.rubriekenlib/Reporting/Generator/VatReturnReportGenerator.phpGLLine.byCostCenter/byCostCenterHierarchy/byProject/byAnalyticalDimensionsrc/views/bookkeeping/dimensions/SegmentPnLDashboard.vueBbvAccountMapping.byProgrammaCode/byAutorisatieniveau,GLLine.byCostObjectFull inventory
Total: 55 aggregations across 22 fragment files.
30-bookkeeping-ib-aangifte-zzp.json— 2IBWinstOpgave.costOfRevenueIBWinstOpgave.revenueExcludingVatadd-shillinq-bbv-compliance.json— 2BbvAccountMapping.byAutorisatieniveau(declares a join)BbvAccountMapping.byProgrammaCode(declares a join)add-shillinq-bookkeeping-operations.json— 5BbvAccountMapping.byTaakveldBcfClaim.compensableVatByAccountIcpStatement.icpTransactionsSchatkistPosition.positionVatReturn.rubriekenbookkeeping-btw-oss-eu.json— 2OssReturn.linesByCountryRateOssThresholdCounter.b2cEuTurnoverByYearbookkeeping-cost-centers-dimensions.json— 5GLLine.byAnalyticalDimension(declares a join)GLLine.byCostCenter(declares a join)GLLine.byCostCenterHierarchy(declares a join)GLLine.byCostObject(declares a join)GLLine.byProject(declares a join)bookkeeping-detachering-payroll-administratie.json— 1Payroll.annualEmployeeDeductionsbookkeeping-icp-opgaaf.json— 1IcpSupply.icpLedgerByBuyerSupplyTypebookkeeping-ifrs-rj-dual-gaap.json— 1ReconciliationBridge.bridgeByPeriodStandardbookkeeping-ifrs15-revenue.json— 1RevenueWaterfall.revenueWaterfallByContractPeriodbookkeeping-innovatiebox-administratie.json— 1IBProfitAttribution.innovatieboxAdministratie(declares a join)bookkeeping-intercompany-elimination.json— 2CounterpartyBalance.counterpartyBalanceByPairIntercompanyMatch.matchByRelationPeriodbookkeeping-kor-kleine-ondernemersregeling.json— 1KORAnnualTurnover.korTurnoverByYearbookkeeping-programmabegroting.json— 2Programma.programmaRollupProgrammabegroting.sluitendByBegrotingbookkeeping-rechtmatigheidsverantwoording.json— 2Rechtmatigheidsbevinding.foutenPerBoekjaarRechtmatigheidstoets.clusteringPerLeverancierbookkeeping-sbr-xbrl-reporting.json— 4SBRDocumentType.glBalanceDeltaSBRDocumentType.mappingCoveragePercentSBRDocumentType.unmappedAccountCountSBRDocumentType.unmappedAccountListbookkeeping-trial-balance.json— 1TrialBalanceLine.trialBalanceByAccountPeriod(declares a join)bookkeeping-vpb-corporate-tax.json— 1QuarterlyTaxStatement.quarterlyTaxStatement(declares a join)bookkeeping-waterschappen-bbv-variant-02-aggregation-compliance.json— 2BBVProgramme.totalBudgetBBVProgramme.ytdSpend(declares a join)bookkeeping-wet-fido-treasury.json— 1KasgeldLimiet.kasgeldRolling3Monthbudget-core-schema.json— 1BudgetLine.budgetVsActuals(declares a join)inventory-stock-movement-ledger.json— 3StockMove.movesByTypeStockMove.netQuantityForLocationStockMove.reservedQuantityForLocationshillinq_register.json— 14BalanceSheet.balanceSheetTotals(declares a join)IbAangifteExport.costsIbAangifteExport.revenueIcpStatement.linesIv3Export.buckets(declares a join)KorRegime.ytdRevenueSisaReport.findingCountsSisaReport.onTimeSettlementTrialBalance.trialBalanceByAccount(declares a join)TrialBalance.trialBalanceTotalsVatReturn.rubrieken(declares a join)ZzpDeduction.taxableProfitZzpDeduction.ytdQualifyingHourskernGegevensConfig.kerngegevensRatios(declares a join)