From 58f9f5dd766c57661b5ab3985fb516cd7869e099 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 08:52:43 +0000 Subject: [PATCH] fix(getag): render fees breakdown when metadata has no inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `external_fees_metadata.inputs` is annotated client-side by the Journey renderer right after the GetAG compute call — the pricing API never returns it. Price items coming from carts submitted through the public API therefore carry the compute result without it. `processExternalFeesDetails` dereferenced `inputs.type` and `inputs.consumptionHT/NT` unguarded and threw `Cannot read properties of undefined`, which takes down the entire order table variable for those items, not just the breakdown. Optional-chain the reads and type `ExternalFeesMetadata['inputs']` as optional to match the API schema. `getConsumptionBasedAmounts` already treats an unknown consumption as `0` and renders `-`, so the breakdown comes out complete apart from the consumption-based yearly figures. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Us3D1FV7drsasWCs791fAX --- .../getag-fees-details-without-inputs.md | 16 ++++ src/variables/getag/network-fees-details.ts | 2 +- src/variables/getag/other-fees-details.ts | 2 +- src/variables/getag/utils.test.ts | 76 +++++++++++++++++++ src/variables/getag/utils.ts | 8 +- src/variables/types.ts | 7 +- 6 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 .changeset/getag-fees-details-without-inputs.md diff --git a/.changeset/getag-fees-details-without-inputs.md b/.changeset/getag-fees-details-without-inputs.md new file mode 100644 index 0000000..1e399b9 --- /dev/null +++ b/.changeset/getag-fees-details-without-inputs.md @@ -0,0 +1,16 @@ +--- +'@epilot/pricing': patch +--- + +Render the GetAG fees breakdown when `external_fees_metadata.inputs` is absent. + +`inputs` is annotated client-side by the Journey renderer after the GetAG compute +call — the pricing API never returns it — so price items coming from carts submitted +through the public API carry fee metadata without it. `processExternalFeesDetails` +dereferenced `inputs.type` and `inputs.consumptionHT/NT` unguarded and threw +`Cannot read properties of undefined`, taking down the whole order table variable for +those items. + +The reads are now optional-chained and `ExternalFeesMetadata['inputs']` is typed as +optional to match. Consumption-based yearly amounts already fall back to `-` when the +consumption is unknown, so the breakdown renders with everything except those figures. diff --git a/src/variables/getag/network-fees-details.ts b/src/variables/getag/network-fees-details.ts index 13e65e8..8ce3ca9 100644 --- a/src/variables/getag/network-fees-details.ts +++ b/src/variables/getag/network-fees-details.ts @@ -13,7 +13,7 @@ export const processNetworkOperatingFeesDetails = ( tax?: Tax | TaxItem, variableUnit?: string, ) => { - const type = externalFeesMetadata.inputs.type || 'power'; + const type = externalFeesMetadata.inputs?.type || 'power'; if (!result.groups) { result.groups = {}; diff --git a/src/variables/getag/other-fees-details.ts b/src/variables/getag/other-fees-details.ts index 1e74abe..b114023 100644 --- a/src/variables/getag/other-fees-details.ts +++ b/src/variables/getag/other-fees-details.ts @@ -15,7 +15,7 @@ export const processOtherFeesDetails = ( tax?: Tax | TaxItem, variableUnit?: string, ) => { - const type = externalFeesMetadata.inputs.type || 'power'; + const type = externalFeesMetadata.inputs?.type || 'power'; if (!result.groups) { result.groups = {}; diff --git a/src/variables/getag/utils.test.ts b/src/variables/getag/utils.test.ts index 2650ccd..dc9014d 100644 --- a/src/variables/getag/utils.test.ts +++ b/src/variables/getag/utils.test.ts @@ -214,6 +214,82 @@ describe('getMarkupDetailsFee', () => { }, }; + describe('when the metadata carries no inputs', () => { + /** + * `inputs` is annotated by the Journey renderer after the GetAG compute call, + * so it is absent on carts submitted through the public API. The breakdown must + * still render — only the consumption-based yearly amounts are unknown. + */ + const metadataWithoutInputs = { + billing_period: 'monthly', + breakdown: { + static: {}, + variable: {}, + variable_ht: {}, + }, + } as ExternalFeesMetadata; + + it('should render the work price markup without yearly amounts', () => { + const priceGetAgConfig: PriceGetAg = { + category: 'power', + markup_amount: 10, + markup_amount_decimal: '0.10', + markup_amount_gross_decimal: '0.10', + unit_amount_gross: 0, + unit_amount_net: 0, + }; + + const result = getMarkupDetailsFee({ + ...defaultParams, + priceGetAgConfig, + externalFeesMetadata: metadataWithoutInputs, + options: { type: 'work_price', tariffType: 'HT' as TariffTypeGetAg }, + }); + + expect(result).toEqual({ + label: 'Work Price Markup', + amount: '10.00 cents/kWh', + amount_decimal: '0.10', + amount_yearly_decimal: '0', + amount_yearly: '-', + }); + }); + + it('should render the procurement markup without yearly amounts', () => { + const priceGetAgConfig: PriceGetAg = { + category: 'power', + markup_amount: 10, + markup_amount_decimal: '0.10', + markup_amount_gross_decimal: '0.10', + unit_amount_gross: 0, + unit_amount_net: 0, + additional_markups_enabled: true, + additional_markups: { + procurement: { + amount: 5, + amount_decimal: '0.05', + amount_gross_decimal: '0.05', + }, + }, + }; + + const result = getMarkupDetailsFee({ + ...defaultParams, + priceGetAgConfig, + externalFeesMetadata: metadataWithoutInputs, + options: { type: 'additional_markup', tariffType: 'NT' as TariffTypeGetAg, key: 'procurement' }, + }); + + expect(result).toEqual({ + label: 'Procurement Markup', + amount: '5.00 cents/kWh', + amount_decimal: '0.05', + amount_yearly_decimal: '0', + amount_yearly: '-', + }); + }); + }); + describe('when priceGetAgConfig is undefined', () => { it('should return undefined', () => { const result = getMarkupDetailsFee({ diff --git a/src/variables/getag/utils.ts b/src/variables/getag/utils.ts index dc7b0a8..e982472 100644 --- a/src/variables/getag/utils.ts +++ b/src/variables/getag/utils.ts @@ -198,8 +198,8 @@ const getMarkupDetailsFee = ({ ? getConsumptionBasedAmounts( procurementMarkup?.amount_gross_decimal, options.tariffType === 'HT' - ? externalFeesMetadata.inputs.consumptionHT - : externalFeesMetadata.inputs.consumptionNT, + ? externalFeesMetadata.inputs?.consumptionHT + : externalFeesMetadata.inputs?.consumptionNT, billingPeriod, ).yearlyAmountDecimal : undefined; @@ -266,8 +266,8 @@ const getMarkupDetailsFee = ({ ? getConsumptionBasedAmounts( priceGetAgConfig?.markup_amount_gross_decimal, options.tariffType === 'HT' - ? externalFeesMetadata.inputs.consumptionHT - : externalFeesMetadata.inputs.consumptionNT, + ? externalFeesMetadata.inputs?.consumptionHT + : externalFeesMetadata.inputs?.consumptionNT, billingPeriod, currency, ).yearlyAmountDecimal diff --git a/src/variables/types.ts b/src/variables/types.ts index 445204e..7f70f17 100644 --- a/src/variables/types.ts +++ b/src/variables/types.ts @@ -23,7 +23,12 @@ export type GetTieredUnitAmountOptions = { export type ExternalFeesMetadata = { billing_period: string; - inputs: { + /** + * Client-side annotation of the GetAG compute request. The pricing API never + * returns it, so it is absent whenever the price item was not built by the + * Journey renderer (e.g. carts submitted through the public API). + */ + inputs?: { consumptionHT?: number; consumptionNT?: number; type?: 'power' | 'gas';