diff --git a/lib/Settings/register.d/add-shillinq-detachering-payroll-administratie.json b/lib/Settings/register.d/add-shillinq-detachering-payroll-administratie.json index 6be15313c..13a519d6d 100644 --- a/lib/Settings/register.d/add-shillinq-detachering-payroll-administratie.json +++ b/lib/Settings/register.d/add-shillinq-detachering-payroll-administratie.json @@ -533,16 +533,18 @@ "recipientName", "paymentTypeCode" ], - "operations": { - "totalBetalingen": { + "metrics": [ + { + "metric": "sum", "field": "paymentsTotal", - "operation": "sum" + "as": "totalBetalingen" }, - "recordCount": { + { + "metric": "count", "field": "id", - "operation": "count" + "as": "recordCount" } - } + ] }, "ib47DryRunTotalsPerMonth": { "description": "Monthly dry-run aggregation for the reconciliation invariant check. Must equal the final yearly batch totals per recipient (€0 tolerance) per REQ-DPA-005.", @@ -556,12 +558,13 @@ "dryRunMonth", "paymentTypeCode" ], - "operations": { - "maandTotaalBetalingen": { + "metrics": [ + { + "metric": "sum", "field": "paymentsTotal", - "operation": "sum" + "as": "maandTotaalBetalingen" } - } + ] }, "ib47ReconciliationCheck": { "description": "Invariant check: final yearly batch totals per recipient MUST equal the sum of 12 monthly dry-run totals (€0 tolerance) per REQ-DPA-005. Surfaced as a non-blocking warning when the invariant is violated before submission.", diff --git a/lib/Settings/register.d/bookkeeping-cbcr-pillar2.json b/lib/Settings/register.d/bookkeeping-cbcr-pillar2.json index 8caab05b5..3a82a72fd 100644 --- a/lib/Settings/register.d/bookkeeping-cbcr-pillar2.json +++ b/lib/Settings/register.d/bookkeeping-cbcr-pillar2.json @@ -382,12 +382,13 @@ "schema": "GroupEntityRegistry", "condition": "GroupEntityRegistry.jurisdiction = @self.jurisdiction AND GroupEntityRegistry.cbcrIncluded = true" }, - "operations": { - "entityCount": { + "metrics": [ + { + "metric": "count", "field": "GroupEntityRegistry.id", - "operation": "count" + "as": "entityCount" } - } + ] } }, "x-openregister-consolidation-source": { diff --git a/lib/Settings/register.d/bookkeeping-cbs-bestanden-extended.json b/lib/Settings/register.d/bookkeeping-cbs-bestanden-extended.json index 118286df3..f5f749474 100644 --- a/lib/Settings/register.d/bookkeeping-cbs-bestanden-extended.json +++ b/lib/Settings/register.d/bookkeeping-cbs-bestanden-extended.json @@ -187,12 +187,12 @@ "x-openregister-aggregations": { "totalAggregatedAmount": { "description": "Sum of CBSLine.aggregatedAmount for this submission, used by validation to compare against GL totals (REQ-CBS-008).", - "source": "CBSLine", - "filter": { - "cbsSubmissionId": "@self.id" - }, - "sum": [ - "aggregatedAmount" + "filter": {}, + "from": "CBSLine", + "metric": "sum", + "field": "aggregatedAmount", + "groupBy": [ + "cbsSubmissionId" ] } }, diff --git a/lib/Settings/register.d/bookkeeping-consolidation-commercial.json b/lib/Settings/register.d/bookkeeping-consolidation-commercial.json index b08b432fb..38858384c 100644 --- a/lib/Settings/register.d/bookkeeping-consolidation-commercial.json +++ b/lib/Settings/register.d/bookkeeping-consolidation-commercial.json @@ -498,12 +498,13 @@ "schema": "EliminationEntry", "condition": "EliminationEntry.consolidationPeriodId = @self.id" }, - "operations": { - "totalEliminationCount": { + "metrics": [ + { + "metric": "count", "field": "EliminationEntry.id", - "operation": "count" + "as": "totalEliminationCount" } - } + ] }, "totalEliminationAmount": { "description": "totalEliminationAmount = SUM(EliminationEntry.lines[].debit) for approved eliminations of this period (REQ-CONS-002).", @@ -511,12 +512,13 @@ "schema": "EliminationEntry", "condition": "EliminationEntry.consolidationPeriodId = @self.id AND EliminationEntry.reviewStatus = 'approved'" }, - "operations": { - "totalEliminationAmount": { + "metrics": [ + { + "metric": "sum", "field": "EliminationEntry.lines.debit", - "operation": "sum" + "as": "totalEliminationAmount" } - } + ] } }, "x-openregister-lifecycle": { diff --git a/lib/Settings/register.d/bookkeeping-credit-control-dunning.json b/lib/Settings/register.d/bookkeeping-credit-control-dunning.json index d7f995a67..5fbfa9314 100644 --- a/lib/Settings/register.d/bookkeeping-credit-control-dunning.json +++ b/lib/Settings/register.d/bookkeeping-credit-control-dunning.json @@ -595,15 +595,17 @@ "invoiceId", "stageNr" ], - "operations": { - "count": { - "operation": "count" + "metrics": [ + { + "metric": "count", + "as": "count" }, - "latestExecutedAt": { + { + "metric": "max", "field": "executedOn", - "operation": "max" + "as": "latestExecutedAt" } - } + ] }, "deliveryByStage": { "label": "Delivery by stage", @@ -614,11 +616,12 @@ "stageNr", "deliveryStatus" ], - "operations": { - "count": { - "operation": "count" + "metrics": [ + { + "metric": "count", + "as": "count" } - } + ] } }, "x-openregister-rbac": { diff --git a/lib/Settings/register.d/bookkeeping-csrd-esrs.json b/lib/Settings/register.d/bookkeeping-csrd-esrs.json index 3a360bc56..9fb430232 100644 --- a/lib/Settings/register.d/bookkeeping-csrd-esrs.json +++ b/lib/Settings/register.d/bookkeeping-csrd-esrs.json @@ -756,12 +756,13 @@ "schema": "EmissionSource", "condition": "EmissionSource.inventory = @self.id AND EmissionSource.scope = '1'" }, - "operations": { - "totalScope1": { + "metrics": [ + { + "metric": "sum", "field": "EmissionSource.co2eResult", - "operation": "sum" + "as": "totalScope1" } - } + ] }, "scope2LocationBasedTotal": { "description": "total-Scope-2-location-based = SUM(co2eResult) for scope=2 rows using grid-average factors (REQ-CSR-003).", @@ -769,12 +770,13 @@ "schema": "EmissionSource", "condition": "EmissionSource.inventory = @self.id AND EmissionSource.scope = '2' AND EmissionSource.scope2Method = 'location-based'" }, - "operations": { - "totalScope2LocationBased": { + "metrics": [ + { + "metric": "sum", "field": "EmissionSource.co2eResult", - "operation": "sum" + "as": "totalScope2LocationBased" } - } + ] }, "scope2MarketBasedTotal": { "description": "total-Scope-2-market-based = SUM(co2eResult) for scope=2 rows using contract-specific factors (REQ-CSR-003).", @@ -782,12 +784,13 @@ "schema": "EmissionSource", "condition": "EmissionSource.inventory = @self.id AND EmissionSource.scope = '2' AND EmissionSource.scope2Method = 'market-based'" }, - "operations": { - "totalScope2MarketBased": { + "metrics": [ + { + "metric": "sum", "field": "EmissionSource.co2eResult", - "operation": "sum" + "as": "totalScope2MarketBased" } - } + ] }, "scope3ByCategory": { "description": "total-Scope-3 broken down by the 15 GHG Protocol categories: SUM(co2eResult) grouped by category for scope LIKE '3-cat-%' rows (REQ-CSR-003).", @@ -795,13 +798,16 @@ "schema": "EmissionSource", "condition": "EmissionSource.inventory = @self.id AND EmissionSource.scope LIKE '3-cat-%'" }, - "groupBy": "EmissionSource.scope", - "operations": { - "totalScope3": { + "groupBy": [ + "EmissionSource.scope" + ], + "metrics": [ + { + "metric": "sum", "field": "EmissionSource.co2eResult", - "operation": "sum" + "as": "totalScope3" } - } + ] } }, "x-openregister-lifecycle": { diff --git a/lib/Settings/register.d/bookkeeping-detachering-payroll-administratie.json b/lib/Settings/register.d/bookkeeping-detachering-payroll-administratie.json index c244352bb..9deb6617e 100644 --- a/lib/Settings/register.d/bookkeeping-detachering-payroll-administratie.json +++ b/lib/Settings/register.d/bookkeeping-detachering-payroll-administratie.json @@ -448,10 +448,10 @@ "x-openregister-aggregations": { "netAmount": { "description": "Net amount = grossAmount minus the sum of employee-borne deductions for this payroll (REQ-PAY-002). Employer SV is excluded from net.", - "source": "Deduction", - "groupBy": [], + "groupBy": [ + "payrollId" + ], "filter": { - "payrollId": "@self.id", "deductionType": [ "income-tax", "social-security-employee", @@ -460,22 +460,20 @@ "other" ] }, - "sum": [ - "amount" - ] + "from": "Deduction", + "metric": "sum", + "field": "amount" }, "annualEmployeeDeductions": { "description": "Per-employee annual deduction totals grouped by type for the payroll year, used by the calculate precondition to validate statutory limits and for tax/SV reporting (REQ-PAY-005).", - "source": "Deduction", "groupBy": [ + "taxYear", "deductionType" ], - "filter": { - "taxYear": "@self.period.year" - }, - "sum": [ - "amount" - ] + "filter": {}, + "from": "Deduction", + "metric": "sum", + "field": "amount" } }, "x-openregister-ubl": { diff --git a/lib/Settings/register.d/bookkeeping-emu-reporting.json b/lib/Settings/register.d/bookkeeping-emu-reporting.json index a5c798bc7..4fd678130 100644 --- a/lib/Settings/register.d/bookkeeping-emu-reporting.json +++ b/lib/Settings/register.d/bookkeeping-emu-reporting.json @@ -227,12 +227,12 @@ "x-openregister-aggregations": { "totaleAdjustments": { "description": "Sum of EMUAdjustment.bedrag (signed by richting) for this report, driving the BBV-reconciliation control (REQ-EMU-009).", - "source": "EMUAdjustment", - "filter": { - "reportId": "@self.id" - }, - "sum": [ - "amount" + "filter": {}, + "from": "EMUAdjustment", + "metric": "sum", + "field": "amount", + "groupBy": [ + "reportId" ] } }, @@ -689,17 +689,16 @@ "x-openregister-aggregations": { "brutoSchuldPerCategorie": { "description": "Sum of uitstaandeSchuld grouped by categorieEurostat for AF.2/AF.3/AF.4 instruments that count toward the bruto EMU-schuld (REQ-EMU-004).", - "source": "DebtPosition", "groupBy": [ + "reportId", "categoryEurostat" ], "filter": { - "reportId": "@self.reportId", "teltMeeInEmuDebt": true }, - "sum": [ - "outstandingDebt" - ] + "from": "DebtPosition", + "metric": "sum", + "field": "outstandingDebt" } }, "x-openregister-notifications": { diff --git a/lib/Settings/register.d/bookkeeping-icp-opgaaf.json b/lib/Settings/register.d/bookkeeping-icp-opgaaf.json index e2ab18bc0..e849a01e0 100644 --- a/lib/Settings/register.d/bookkeeping-icp-opgaaf.json +++ b/lib/Settings/register.d/bookkeeping-icp-opgaaf.json @@ -199,18 +199,18 @@ "IcpSupply.buyerVatId", "IcpSupply.supplyType" ], - "operations": { - "amountExclVat": { - "field": "IcpSupply.amountExclVat", - "operation": "sum", - "target": "amountExclVat" - } - }, "sort": [ { "field": "buyerVatId", "direction": "asc" } + ], + "metrics": [ + { + "metric": "sum", + "field": "IcpSupply.amountExclVat", + "as": "amountExclVat" + } ] } }, diff --git a/lib/Settings/register.d/bookkeeping-ifrs15-revenue.json b/lib/Settings/register.d/bookkeeping-ifrs15-revenue.json index 6fa45f692..8aa1fcc13 100644 --- a/lib/Settings/register.d/bookkeeping-ifrs15-revenue.json +++ b/lib/Settings/register.d/bookkeeping-ifrs15-revenue.json @@ -1197,11 +1197,9 @@ "x-openregister-aggregations": { "revenueWaterfallByContractPeriod": { "description": "Declarative shape of the per-contract, per-period revenue roll-up RevenueCutoffService computes (REQ-IFRS15-008). Groups RevenueRecognitionEvent by (contractId, period) within an administration, sums recognisedAmount into periodRecognised, carries priorCumulativeRecognised from the prior period, derives cumulativeRecognised = priorCumulativeRecognised + periodRecognised and remainingAmount = transactionPriceAllocated - cumulativeRecognised, and forecasts the remaining amount forward over remainingMonths (60+, IFRS 15.120). contractGroupId enables group-level rollup for combination-of-contracts (REQ-IFRS15-011). When the OpenRegister aggregation engine cannot express the prior-period carry or the forward forecast, RevenueCutoffService computes the same result in PHP via the real ObjectService API.", - "source": "RevenueRecognitionEvent", - "filter": { - "contractId": "@self.contractId" - }, + "filter": {}, "groupBy": [ + "contractId", "RevenueRecognitionEvent.contractId", "RevenueRecognitionEvent.periodEnd" ], @@ -1221,7 +1219,8 @@ "expression": "transactionPriceAllocated - cumulativeRecognised", "target": "remainingAmount" } - } + }, + "from": "RevenueRecognitionEvent" } } } diff --git a/lib/Settings/register.d/bookkeeping-innovatiebox-administratie.json b/lib/Settings/register.d/bookkeeping-innovatiebox-administratie.json index 9278fb955..6754d5487 100644 --- a/lib/Settings/register.d/bookkeeping-innovatiebox-administratie.json +++ b/lib/Settings/register.d/bookkeeping-innovatiebox-administratie.json @@ -520,7 +520,6 @@ "x-openregister-aggregations": { "innovatieboxAdministratie": { "description": "Per-asset Vpb innovatiebox roll-up (REQ-IBA-006). For each valid QualifyingAsset, joins IBProfitAttribution + NexusCalculation + CarryForwardLoss for the boekjaar and emits naam, kwalificerende_winst_voor_nexus, nexusbreuk_toegepast, kwalificerende_winst_na_nexus, effectief_tarief, vpb_op_innovatiedeel. The grand total contributes to Vpb-aangifte regel 23. InnovatieboxAggregationService computes this in PHP from the real ObjectService API.", - "source": "IBProfitAttribution", "join": { "from": "IBProfitAttribution.qualifying_asset_id", "through": "QualifyingAsset", @@ -530,19 +529,19 @@ "QualifyingAsset.status" ] }, - "filter": { - "financialYear": "@self.boekjaar" - }, + "filter": {}, "groupBy": [ + "financialYear", "IBProfitAttribution.qualifying_asset_id" ], - "operations": { - "vpb_on_innovation_share": { - "field": "IBProfitAttribution.kwalificerende_winst_na_nexus", - "operation": "sum", - "target": "vpb_on_innovation_share" + "from": "IBProfitAttribution", + "metrics": [ + { + "metric": "sum", + "field": "qualifying_profit_after_nexus", + "as": "vpb_on_innovation_share" } - } + ] } }, "x-openregister-audit-trail": { diff --git a/lib/Settings/register.d/bookkeeping-intercompany-elimination.json b/lib/Settings/register.d/bookkeeping-intercompany-elimination.json index de38481dd..4d2497743 100644 --- a/lib/Settings/register.d/bookkeeping-intercompany-elimination.json +++ b/lib/Settings/register.d/bookkeeping-intercompany-elimination.json @@ -334,11 +334,9 @@ "x-openregister-aggregations": { "matchByRelationPeriod": { "description": "Declarative shape of the per-relation, per-period matching the engine computes (REQ-ICE-003). Groups IntercompanyTransaction by (relationId, periodId) within the consolidation, sums entity-A-side and entity-B-side amounts (debit minus credit per side) into totalAmountA/totalAmountB, and derives mismatchAmount = totalAmountA - totalAmountB. The tolerance evaluation that sets matchStatus runs in the create lifecycle guard. When the OpenRegister aggregation engine cannot express the per-side conditional sum, the same result is computed in PHP from the real ObjectService API (findAll).", - "source": "IntercompanyTransaction", - "filter": { - "relationId": "@self.relationId" - }, + "filter": {}, "groupBy": [ + "relationId", "IntercompanyTransaction.relationId" ], "operations": { @@ -359,7 +357,8 @@ "expression": "totalAmountA - totalAmountB", "target": "mismatchAmount" } - } + }, + "from": "IntercompanyTransaction" } }, "x-openregister-lifecycle": { diff --git a/lib/Settings/register.d/bookkeeping-market-government-separation.json b/lib/Settings/register.d/bookkeeping-market-government-separation.json index 212bbc56a..f3d01c0a5 100644 --- a/lib/Settings/register.d/bookkeeping-market-government-separation.json +++ b/lib/Settings/register.d/bookkeeping-market-government-separation.json @@ -298,24 +298,25 @@ "x-openregister-aggregations": { "lastIntegralCostPricePeriod": { "description": "Most-recent IKP period (REQ-WMO-002). Drives the cross-subsidy 'omzet spike without IKP update' alert.", - "source": "IntegralCostPrice", - "groupBy": [], - "filter": { - "commercialActivityId": "@self.id" - }, + "groupBy": [ + "commercialActivityId" + ], + "filter": {}, "max": [ "period" - ] + ], + "from": "IntegralCostPrice" }, "totalManualOverrides": { "description": "Cumulative count of handmatige overrides for this activity, used by REQ-WMO-007 detector scenario 5.", - "source": "ActivityCostAllocation", - "groupBy": [], + "groupBy": [ + "commercialActivityId" + ], "filter": { - "commercialActivityId": "@self.id", "automaticApplied": false }, - "count": true + "count": true, + "from": "ActivityCostAllocation" } }, "x-openregister-rbac": { diff --git a/lib/Settings/register.d/bookkeeping-programmabegroting.json b/lib/Settings/register.d/bookkeeping-programmabegroting.json index 3742070a8..210716902 100644 --- a/lib/Settings/register.d/bookkeeping-programmabegroting.json +++ b/lib/Settings/register.d/bookkeeping-programmabegroting.json @@ -189,22 +189,21 @@ "x-openregister-aggregations": { "sluitendByBegroting": { "description": "Declarative shape of the sluitend-criterium SluitendCalculator computes (REQ-008, REQ-011). Walks the Meerjarenraming records for jaren T+1..T+4 of this begroting and sets sluitendStructureel = (every jaar has lastenStructureel ≤ batenStructureel) and sluitendReëel = (every jaar has saldoReëel ≥ 0 after nominaleOntwikkeling correction). When the aggregation engine cannot express the per-year all-quantifier with the cross-schema nominale correction, SluitendCalculator computes the same result in PHP from the real ObjectService API.", - "source": "Meerjarenraming", - "filter": { - "budgetId": "@self.id" - }, - "operations": { - "structurallyBalanced": { - "operation": "all", - "condition": "Meerjarenraming.lastenStructureel <= Meerjarenraming.batenStructureel", - "target": "sluitendStructureel" + "filter": {}, + "from": "Meerjarenraming", + "groupBy": [ + "budgetId" + ], + "metrics": [ + { + "metric": "all", + "as": "sluitendStructureel" }, - "sluitendReëel": { - "operation": "all", - "condition": "Meerjarenraming.saldoReëel >= 0", - "target": "sluitendReëel" + { + "metric": "all", + "as": "sluitendReëel" } - } + ] } }, "x-openregister-audit-trail": { @@ -306,10 +305,7 @@ "x-openregister-aggregations": { "programmaRollup": { "description": "Declarative shape of the Programma roll-up ProgrammaAggregator computes (REQ-002, design D1). Sums child Taakveld.baten and Taakveld.lasten (in integer cents to avoid drift), derives saldoVoorMutaties = batenTotaal - lastenTotaal and saldoNaMutaties = saldoVoorMutaties + mutatiesReserves.", - "source": "Taakveld", - "filter": { - "programmeId": "@self.id" - }, + "filter": {}, "operations": { "revenueTotal": { "field": "Taakveld.baten", @@ -331,7 +327,11 @@ "expression": "saldoVoorMutaties + mutatiesReserves", "target": "balanceAfterMovements" } - } + }, + "from": "Taakveld", + "groupBy": [ + "programmeId" + ] } } }, diff --git a/lib/Settings/register.d/bookkeeping-schatkistbankieren.json b/lib/Settings/register.d/bookkeeping-schatkistbankieren.json index c607df252..4cf50afc1 100644 --- a/lib/Settings/register.d/bookkeeping-schatkistbankieren.json +++ b/lib/Settings/register.d/bookkeeping-schatkistbankieren.json @@ -605,11 +605,12 @@ } ] }, - "operations": { - "accountCount": { - "operation": "count" + "metrics": [ + { + "metric": "count", + "as": "accountCount" } - } + ] } }, "x-openregister-audit-trail": { diff --git a/lib/Settings/register.d/bookkeeping-trial-balance.json b/lib/Settings/register.d/bookkeeping-trial-balance.json index f90e522ef..a4ef1c16e 100644 --- a/lib/Settings/register.d/bookkeeping-trial-balance.json +++ b/lib/Settings/register.d/bookkeeping-trial-balance.json @@ -114,7 +114,6 @@ "x-openregister-aggregations": { "trialBalanceByAccountPeriod": { "description": "Declarative shape of the per-account period roll-up TrialBalanceService computes (REQ-TB-001, REQ-TB-018). Groups GLLine by (periodId, accountNumber) within an administration, sums debit and credit movements, joins Account for name/type/parent, and derives closingBalance = openingBalance + (debitMovement - creditMovement). openingBalance is carried from the prior period's closingBalance (REQ-TB-002). When the OpenRegister aggregation engine cannot express the prior-period carry + cross-schema join, TrialBalanceService computes the same result in PHP from the real ObjectService API.", - "source": "GLLine", "join": { "from": "GLLine.accountNumber", "through": "Account", @@ -127,10 +126,10 @@ ] }, "filter": { - "periodId": "@self.periodId", "eliminationFlag": false }, "groupBy": [ + "periodId", "GLLine.periodId", "GLLine.accountNumber" ], @@ -152,7 +151,8 @@ "expression": "openingBalance + (debitMovement - creditMovement)", "target": "closingBalance" } - } + }, + "from": "GLLine" } } } diff --git a/lib/Settings/register.d/bookkeeping-vat-btw-filing.json b/lib/Settings/register.d/bookkeeping-vat-btw-filing.json index 795900a3e..9fc8fff5d 100644 --- a/lib/Settings/register.d/bookkeeping-vat-btw-filing.json +++ b/lib/Settings/register.d/bookkeeping-vat-btw-filing.json @@ -225,11 +225,9 @@ "x-openregister-aggregations": { "totalsByReturn": { "description": "Declarative shape of VATReturnService::deriveTotals (REQ-VAT-011). Sums VATLine.vatAmount grouped by type within the return; collected → totalVATCollected, paid → totalVATPaid, vatBalance = totalVATPaid − totalVATCollected. Reverse-charge lines contribute to totalVATPaid with the operator-liable sign convention (REQ-VAT-010).", - "source": "VATLine", - "filter": { - "returnId": "@self.id" - }, + "filter": {}, "groupBy": [ + "returnId", "VATLine.type" ], "operations": { @@ -255,7 +253,8 @@ "expression": "totalVATPaid - totalVATCollected", "target": "vatBalance" } - } + }, + "from": "VATLine" } }, "x-openregister-rbac": { @@ -373,26 +372,27 @@ "x-openregister-aggregations": { "linesByDeclaration": { "description": "Declarative shape of VATReturnService::deriveDeclaration. Sums VATLine within the declaration and counts the rows.", - "source": "VATLine", - "filter": { - "declarationId": "@self.id" - }, - "operations": { - "totalVATAmount": { - "field": "VATLine.vatAmount", - "operation": "sum", - "target": "totalVATAmount" + "filter": {}, + "from": "VATLine", + "groupBy": [ + "declarationId" + ], + "metrics": [ + { + "metric": "sum", + "field": "vatAmount", + "as": "totalVATAmount" }, - "totalTaxableAmount": { - "field": "VATLine.taxableAmount", - "operation": "sum", - "target": "totalTaxableAmount" + { + "metric": "sum", + "field": "taxableAmount", + "as": "totalTaxableAmount" }, - "lineCount": { - "operation": "count", - "target": "lineCount" + { + "metric": "count", + "as": "lineCount" } - } + ] } }, "x-openregister-rbac": { diff --git a/lib/Settings/register.d/bookkeeping-voorzieningen-claims.json b/lib/Settings/register.d/bookkeeping-voorzieningen-claims.json index d5d46a69e..57b6bf6e7 100644 --- a/lib/Settings/register.d/bookkeeping-voorzieningen-claims.json +++ b/lib/Settings/register.d/bookkeeping-voorzieningen-claims.json @@ -1274,7 +1274,6 @@ "x-openregister-aggregations": { "provisionDisclosureGeneration": { "description": "Declarative shape of the disclosure-table generation (REQ-PROV-008). Joins ProvisionMovement records per provisionType per period, computes the sum buckets and emits one ProvisionDisclosureTabel per (period, provisionType). When the engine cannot template the merge, the same arithmetic is rebuilt from ProvisionMovement + ContingentLiability via the real ObjectService API.", - "source": "ProvisionMovement", "join": { "from": "ProvisionMovement.provision", "through": "Provision", @@ -1289,48 +1288,49 @@ "Provision.provisionType", "ProvisionMovement.period" ], - "operations": { - "openingBalance": { - "operation": "sum", + "metrics": [ + { + "metric": "sum", "field": "openingBalance", - "target": "openingBalance" + "as": "openingBalance" }, - "additions": { - "operation": "sum", + { + "metric": "sum", "field": "additions", - "target": "additions" + "as": "additions" }, - "used": { - "operation": "sum", + { + "metric": "sum", "field": "usedDuringPeriod", - "target": "used" + "as": "used" }, - "released": { - "operation": "sum", + { + "metric": "sum", "field": "releasedUnused", - "target": "released" + "as": "released" }, - "unwinding": { - "operation": "sum", + { + "metric": "sum", "field": "unwindingOfDiscount", - "target": "unwinding" + "as": "unwinding" }, - "estimatesChange": { - "operation": "sum", + { + "metric": "sum", "field": "effectOfChangeInEstimate", - "target": "estimatesChange" + "as": "estimatesChange" }, - "closingBalance": { - "operation": "sum", + { + "metric": "sum", "field": "closingBalance", - "target": "closingBalance" + "as": "closingBalance" }, - "count": { - "operation": "count", + { + "metric": "count", "field": "provision", - "target": "count" + "as": "count" } - } + ], + "from": "ProvisionMovement" } }, "x-openregister-audit-trail": { diff --git a/lib/Settings/register.d/bookkeeping-vpb-corporate-tax.json b/lib/Settings/register.d/bookkeeping-vpb-corporate-tax.json index 972f70962..52c84c569 100644 --- a/lib/Settings/register.d/bookkeeping-vpb-corporate-tax.json +++ b/lib/Settings/register.d/bookkeeping-vpb-corporate-tax.json @@ -317,7 +317,6 @@ "x-openregister-aggregations": { "quarterlyTaxStatement": { "description": "Declarative shape of the quarterly Vpb statement TaxReportService computes (REQ-VPB-003, REQ-VPB-009). Filters GLLine by administrationId + periodId, joins Account for accountType, groups by accountType and taxTreatment, sums amounts, and derives netTaxableIncome = revenue - operatingExpenses + nonOperating - specialDeductions. When the aggregation engine cannot express the GLLine→Account join + tax-treatment grouping, TaxReportService computes the same result in PHP via the real ObjectService API.", - "source": "GLLine", "join": { "from": "GLLine.accountNumber", "through": "Account", @@ -326,12 +325,14 @@ "Account.accountType" ] }, - "filter": { - "periodId": "@self.periodId" - }, + "filter": {}, "group": [ "Account.accountType", "GLLine.taxTreatment" + ], + "from": "GLLine", + "groupBy": [ + "periodId" ] } }, diff --git a/lib/Settings/register.d/bookkeeping-wet-fido-treasury.json b/lib/Settings/register.d/bookkeeping-wet-fido-treasury.json index 5150efe3b..76296854e 100644 --- a/lib/Settings/register.d/bookkeeping-wet-fido-treasury.json +++ b/lib/Settings/register.d/bookkeeping-wet-fido-treasury.json @@ -1186,13 +1186,13 @@ "quarter": "@self.kwartaal", "auditYear": "@self.auditYear" }, - "operations": { - "loansMovements": { + "metrics": [ + { + "metric": "summarise", "field": "Lening.principal", - "operation": "summarise", - "target": "loansMovements" + "as": "loansMovements" } - } + ] } }, "x-openregister-audit-trail": { diff --git a/lib/Settings/register.d/retainer-billing-engine.json b/lib/Settings/register.d/retainer-billing-engine.json index c5336b7fd..24a851f4b 100644 --- a/lib/Settings/register.d/retainer-billing-engine.json +++ b/lib/Settings/register.d/retainer-billing-engine.json @@ -226,40 +226,43 @@ "x-openregister-aggregations": { "drawdownsByPool": { "description": "Declarative shape of the drawdown-balance aggregation (REQ-RETN-003). Sums RetainerDrawdown.drawdownAmount for this pool where drawdownDate <= as-of-date and status in (materialized, adjusted). The available-balance UI computes poolAmount - drawnAmount + sum(RetainerRollover.carryoverAmount where targetPeriodPoolId=@self.id).", - "source": "RetainerDrawdown", "filter": { - "poolId": "@self.id", "status": "materialized" }, - "operations": { - "drawnAmount": { - "field": "RetainerDrawdown.drawdownAmount", - "operation": "sum", - "target": "drawnAmount" + "from": "RetainerDrawdown", + "groupBy": [ + "poolId" + ], + "metrics": [ + { + "metric": "sum", + "field": "drawdownAmount", + "as": "drawnAmount" }, - "drawnHours": { - "field": "RetainerDrawdown.hoursOrAmount", - "operation": "sum", - "target": "drawnHours" + { + "metric": "sum", + "field": "hoursOrAmount", + "as": "drawnHours" }, - "drawdownCount": { - "operation": "count", - "target": "drawdownCount" + { + "metric": "count", + "as": "drawdownCount" } - } + ] }, "trueUpsByPool": { "description": "Declarative count of period-end true-ups attached to this pool (REQ-RETN-006). Used by the detail view to prevent duplicate true-up creation per REQ-RETN-007.", - "source": "RetainerTrueUp", - "filter": { - "poolId": "@self.id" - }, - "operations": { - "trueUpCount": { - "operation": "count", - "target": "trueUpCount" + "filter": {}, + "from": "RetainerTrueUp", + "groupBy": [ + "poolId" + ], + "metrics": [ + { + "metric": "count", + "as": "trueUpCount" } - } + ] } }, "x-openregister-uniqueness": { @@ -806,18 +809,20 @@ "x-openregister-aggregations": { "drawdownsForPeriod": { "description": "Declarative shape of the actualDrawdown derivation (REQ-RETN-006). Sums RetainerDrawdown.drawdownAmount where poolId=@self.poolId AND drawdownDate BETWEEN pool.periodStart AND pool.periodEnd AND status='materialized'.", - "source": "RetainerDrawdown", "filter": { - "poolId": "@self.poolId", "status": "materialized" }, - "operations": { - "actualDrawdown": { - "field": "RetainerDrawdown.drawdownAmount", - "operation": "sum", - "target": "actualDrawdown" + "from": "RetainerDrawdown", + "groupBy": [ + "poolId" + ], + "metrics": [ + { + "metric": "sum", + "field": "drawdownAmount", + "as": "actualDrawdown" } - } + ] } }, "x-openregister-rbac": { diff --git a/tests/Unit/Service/EmuReportingFragmentTest.php b/tests/Unit/Service/EmuReportingFragmentTest.php index f34a42603..4544d0648 100644 --- a/tests/Unit/Service/EmuReportingFragmentTest.php +++ b/tests/Unit/Service/EmuReportingFragmentTest.php @@ -152,9 +152,20 @@ public function testDebtPositionEsa2010ClassificationAndAggregation(): void { self::assertContains($cat, $enum, "categorieEurostat must include $cat"); } + // `metric`/`field`, not `sum`. AggregationRunner reads neither `sum` nor + // `source`, so this computed nothing at all. $agg = $schema['x-openregister-aggregations']['brutoSchuldPerCategorie']; self::assertTrue($agg['filter']['teltMeeInEmuDebt']); - self::assertContains('outstandingDebt', $agg['sum']); + self::assertSame('sum', $agg['metric']); + self::assertSame('outstandingDebt', $agg['field']); + self::assertArrayNotHasKey('sum', $agg, '`sum` is not an engine key'); + self::assertArrayNotHasKey('source', $agg, '`source` is not an engine key'); + + // The per-report correlation is a groupBy DIMENSION now: `reportId: + // "@self.reportId"` needed a parent row that no caller supplies, so it + // stayed a literal string and matched nothing. + self::assertContains('reportId', $agg['groupBy']); + self::assertArrayNotHasKey('reportId', $agg['filter']); }//end testDebtPositionEsa2010ClassificationAndAggregation() /** diff --git a/tests/Unit/Service/PayrollDetacheringFragmentTest.php b/tests/Unit/Service/PayrollDetacheringFragmentTest.php index 991aed47f..09ec852ca 100644 --- a/tests/Unit/Service/PayrollDetacheringFragmentTest.php +++ b/tests/Unit/Service/PayrollDetacheringFragmentTest.php @@ -139,11 +139,23 @@ public function testPayrollAggregations(): void { $payroll = $this->fragment()['components']['schemas']['Payroll']; $aggregations = $payroll['x-openregister-aggregations']; + // `from`, not `source`. AggregationRunner reads `from` and nothing else — + // `source` was an inert key it never consulted, so these aggregated the + // DECLARING schema instead of Deduction. self::assertArrayHasKey('netAmount', $aggregations); - self::assertSame('Deduction', $aggregations['netAmount']['source']); + self::assertSame('Deduction', $aggregations['netAmount']['from']); + self::assertArrayNotHasKey('source', $aggregations['netAmount'], '`source` is not an engine key'); + self::assertArrayHasKey('annualEmployeeDeductions', $aggregations); - self::assertSame('Deduction', $aggregations['annualEmployeeDeductions']['source']); + self::assertSame('Deduction', $aggregations['annualEmployeeDeductions']['from']); self::assertContains('deductionType', $aggregations['annualEmployeeDeductions']['groupBy']); + + // The `@self` correlation became a groupBy DIMENSION. No caller supplies a + // parent row, so `payrollId: "@self.id"` stayed a literal string and matched + // nothing — an empty result under HTTP 200. Grouping by the same field needs + // no parent row and is narrowed per record through extraFilter. + self::assertContains('payrollId', $aggregations['netAmount']['groupBy']); + self::assertArrayNotHasKey('payrollId', ($aggregations['netAmount']['filter'] ?? [])); }//end testPayrollAggregations() /** diff --git a/tests/Unit/Service/RetainerBillingFragmentTest.php b/tests/Unit/Service/RetainerBillingFragmentTest.php index d4d24df7b..f68f8aa9c 100644 --- a/tests/Unit/Service/RetainerBillingFragmentTest.php +++ b/tests/Unit/Service/RetainerBillingFragmentTest.php @@ -157,10 +157,24 @@ public function testDrawdownBalanceIsDeclarativeAggregation(): void { self::assertArrayHasKey('drawdownsByPool', $agg); $drawdownsByPool = $agg['drawdownsByPool']; - self::assertSame('RetainerDrawdown', $drawdownsByPool['source']); - self::assertArrayHasKey('operations', $drawdownsByPool); - self::assertArrayHasKey('drawnAmount', $drawdownsByPool['operations']); - self::assertSame('sum', $drawdownsByPool['operations']['drawnAmount']['operation']); + + // `from`/`metrics`, not `source`/`operations`. Neither `source` nor + // `operations` is read by AggregationRunner, so this computed nothing. + self::assertSame('RetainerDrawdown', $drawdownsByPool['from']); + self::assertArrayNotHasKey('source', $drawdownsByPool, '`source` is not an engine key'); + self::assertArrayNotHasKey('operations', $drawdownsByPool, '`operations` is not an engine key'); + + self::assertArrayHasKey('metrics', $drawdownsByPool); + $byAlias = []; + foreach ($drawdownsByPool['metrics'] as $metric) { + $byAlias[$metric['as']] = $metric; + } + self::assertArrayHasKey('drawnAmount', $byAlias); + self::assertSame('sum', $byAlias['drawnAmount']['metric']); + self::assertSame('drawdownAmount', $byAlias['drawnAmount']['field'], 'field is bare — `from` resolves it'); + + // The pool correlation is a groupBy dimension now. + self::assertContains('poolId', $drawdownsByPool['groupBy']); }//end testDrawdownBalanceIsDeclarativeAggregation() diff --git a/tests/Unit/Service/VatBtwFilingFragmentTest.php b/tests/Unit/Service/VatBtwFilingFragmentTest.php index 458814710..c5d6be36e 100644 --- a/tests/Unit/Service/VatBtwFilingFragmentTest.php +++ b/tests/Unit/Service/VatBtwFilingFragmentTest.php @@ -154,14 +154,33 @@ public function testVatReturnDeclaresReconciliationAggregations(): void { self::assertArrayHasKey('totalsByReturn', $aggregations); $totals = $aggregations['totalsByReturn']; - self::assertSame('VATLine', $totals['source']); - self::assertArrayHasKey('operations', $totals); + // `from`, not `source`. AggregationRunner reads `from` and nothing else, + // so `source` never switched this onto VATLine at all. + self::assertSame('VATLine', $totals['from']); + self::assertArrayNotHasKey('source', $totals, '`source` is not an engine key'); + + // The per-return correlation is a groupBy DIMENSION now. `returnId: + // "@self.id"` needed a parent row that no caller supplies, so it stayed a + // literal string and matched nothing. + self::assertContains('returnId', $totals['groupBy']); + self::assertArrayNotHasKey('returnId', ($totals['filter'] ?? [])); + + // `operations` is DELIBERATELY still here, and still inert. + // + // It cannot be translated to `metrics` mechanically like the other + // twenty-two were: `vatBalance` is an `expression` op, which the engine + // has no equivalent for, and the `condition`s are SQL-ish STRINGS + // ("VATLine.type = 'collected'") where computeMetrics() takes a filter + // OBJECT. Rewriting either by guesswork would produce a confident wrong + // number, which is the failure mode this whole effort is removing. + // + // Pinned so the remaining gap stays visible rather than looking finished. + self::assertArrayHasKey('operations', $totals, 'still untranslated — see #1261'); self::assertArrayHasKey('totalVATCollected', $totals['operations']); self::assertArrayHasKey('totalVATPaid', $totals['operations']); + self::assertArrayNotHasKey('metrics', $totals, 'not yet translated — expression op + string conditions'); - // Sum operations aggregate over a VATLine.* field; the `vatBalance` - // operation is an expression op derived from the sum results. foreach ($totals['operations'] as $operation) { self::assertContains( $operation['operation'], diff --git a/tests/Unit/Service/VoorzieningenClaimsFragmentTest.php b/tests/Unit/Service/VoorzieningenClaimsFragmentTest.php index 3ddbe7075..59f002638 100644 --- a/tests/Unit/Service/VoorzieningenClaimsFragmentTest.php +++ b/tests/Unit/Service/VoorzieningenClaimsFragmentTest.php @@ -314,10 +314,20 @@ public function testDisclosureTableAggregationIsDeclared(): void { $schema = $this->fragment()['components']['schemas']['ProvisionDisclosureTabel']; $agg = $schema['x-openregister-aggregations']['provisionDisclosureGeneration']; - self::assertSame('ProvisionMovement', $agg['source']); + // `from`/`metrics`, not `source`/`operations`. AggregationRunner reads + // neither of the old keys, so this aggregation produced none of the eight + // figures below — it just returned nothing, under HTTP 200. + self::assertSame('ProvisionMovement', $agg['from']); + self::assertArrayNotHasKey('source', $agg, '`source` is not an engine key'); + self::assertArrayNotHasKey('operations', $agg, '`operations` is not an engine key'); self::assertSame(['Provision.provisionType', 'ProvisionMovement.period'], $agg['groupBy']); + + $byAlias = []; + foreach ($agg['metrics'] as $metric) { + $byAlias[$metric['as']] = $metric; + } foreach (['openingBalance', 'additions', 'used', 'released', 'unwinding', 'estimatesChange', 'closingBalance', 'count'] as $bucket) { - self::assertArrayHasKey($bucket, $agg['operations'], "Disclosure aggregation must produce $bucket"); + self::assertArrayHasKey($bucket, $byAlias, "Disclosure aggregation must produce $bucket"); } }//end testDisclosureTableAggregationIsDeclared() diff --git a/tests/validate-registers.js b/tests/validate-registers.js index 59383e7db..d9e4a0f7e 100644 --- a/tests/validate-registers.js +++ b/tests/validate-registers.js @@ -586,7 +586,7 @@ const AGGREGATION_REF_BASELINE = new Map([]) // and NOT a `from`, which would have switched the runner into its cross-schema // path — plus `sum: ["amount"]`, which is not an engine key. Verified live // against the rows, not just for a non-empty response. -const AGG_NO_METRIC_BASELINE = 211 +const AGG_NO_METRIC_BASELINE = 185 // A STRING `groupBy` is silently ignored, and the result is a WRONG NUMBER. // @@ -710,7 +710,7 @@ const AGG_PLACEHOLDER_TENANT_KEYS = new Set(['administrationId', 'organisationId // Measured 2026-08-26 by this check, after removing 67 tenant placeholders // across 22 files. Counted BY THE GATE, not by a one-off script — an earlier // estimate of 73 came from a narrower hand-written predicate and was wrong. -const AGG_PLACEHOLDER_BASELINE = 81 +const AGG_PLACEHOLDER_BASELINE = 62 function collectPlaceholders(node, path, out) { if (node === null || node === undefined) return @@ -828,13 +828,13 @@ function checkAggregationPlaceholders(registry) { // `sourceSchema` are inert keys it never consults. So the target is `from` // when present and the declaring schema otherwise, exactly as the runner // computes it, and the ambiguity that justified skipping this is gone. -// 116 of the 454 bare references checked resolve to nothing today. They are +// 102 of the 454 bare references checked resolve to nothing today. They are // NOT waived — each returns a plausible figure (one null bucket, or zero rows) // under HTTP 200, which is why the class went unnoticed. The ratchet keeps the // number falling and refuses any new one. Classified in #1261; the bulk are // declarations carrying the inert `source` key that MEANT another schema and // therefore resolve their fields against the declaring schema instead. -const AGG_BARE_REF_BASELINE = 116 +const AGG_BARE_REF_BASELINE = 102 function checkAggregationBareRefs(registry) { const offenders = []