LF-5301: Add Profitability widget Redux slice, date-range hook, and aggregation utils#4182
Open
litefarm-pr-bot wants to merge 1 commit into
Conversation
…ggregation utils Adds the non-UI foundations of the Profitability home widget: a createSlice-based date-range slice persisted alongside the other Home state under tempStateReducer, a useProfitabilityDateRange hook that mirrors the existing useFinancesDateRange pattern but reads/writes the new slice, and a utils module with pure aggregation functions that the widget's data hook (built in a follow-up PR) will compose. The slice's initial state captures the current Year-to-Date dates at module load so a first-time user sees the correct range with no extra dispatch; useDateRange's on-mount recompute (extended in the prior PR in this story) keeps that fresh and also leaves caller-supplied dynamic options like a calendar year untouched. The slice is registered under tempStateReducer next to homeReducer; Redux Persist covers the store globally so the selection survives a refresh without any whitelist change. The utils module deliberately stays pure and i18n-free: revenue groups return sub-keys (CROP_SALES / ANIMAL_SALES / FARM_GENERAL) for the component to localise, and expense categories return either the caller's expense_name (custom types) or an i18n key path (system types and the synthetic Labour entry). aggregateByEntity walks crop_variety_sale, animal_sale, farm_expense_crop_variety, and farm_expense_animal to attribute revenue and expense per-entity; revenue is returned as null (rendered as "Not yet" by the table) for entities that have expense allocations but no sales in the date range, and the all-tab includes a synthetic farm_general row when there are unallocated revenues or expenses. 21 Vitest tests cover each public function — KPI sums across mixed sale shapes, YoY direction across two same-length windows, group/category aggregation including the implicit Labour category, per-entity attribution including the Not yet sentinel and the all-tab synthetic row, year extraction, and the dynamic-year option shape. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The Profitability home widget needs three things that have no place in any existing module: a per-widget date-range state (so the user's range selection survives navigation and refresh independently of the Finances page's own selection), a hook that exposes that state plus the list of years for which there is data, and a pure-functional aggregation layer the widget's data hook will compose to produce KPIs, sources/categories bars, and per-entity rows. This PR ships those three foundations with no UI surface yet — the presentational components and the smart container both depend on this slice + hook + utils but ship in follow-up PRs.
slice.tsis acreateSlice-based date-range slice with a singleupdateDateRangeaction, registered undertempStateReducernext tohomeReducer. Initial state captures the current Year-to-Date dates at module load so a first-time user sees the correct range with no extra dispatch; the shareduseDateRangehook (extended in the prior PR in this story) recomputes those dates on mount so they do not go stale across days, and leaves caller-supplied dynamic options like a persisted calendar year untouched. Redux Persist covers the whole store globally so no whitelist change was required.useProfitabilityDateRangemirrors the existinguseFinancesDateRangepattern but reads/writes the new slice and additionally derivesavailableYearsfrom the legacy Finances sales+expense selectors.utils.tsstays deliberately pure and i18n-free: revenue groups return sub-keys (CROP_SALES/ANIMAL_SALES/FARM_GENERAL) for the component to localise, expense categories return either the caller'sexpense_name(custom types) or an i18n key path (system types and the synthetic Labour entry), andaggregateByEntitywalkscrop_variety_sale,animal_sale,farm_expense_crop_variety, andfarm_expense_animalto attribute revenue and expense per-entity. Revenue is returned asnull(rendered as "Not yet" by the table) for entities that have expense allocations within the date range but no sales; the'all'tab includes a syntheticfarm_generalrow when there are unallocated revenues or expenses.The convention this PR establishes for the widget: keep aggregation utilities pure (no i18n calls, no selector reads) so they can be exhaustively unit-tested with synthetic fixtures, and have the consuming hook/container compose them with Redux selectors and RTK Query results. When totals could come from multiple shapes — for example a sale with crop allocations vs an animal-row sale vs a farm-general sale — collapse them through a single helper (
sumSaleValue) rather than branching at every call site.Jira link: https://lite-farm.atlassian.net/browse/LF-5301
Type of change
How Has This Been Tested?
pnpm exec vitest run src/tests/profitabilityWidget.test.js— 21 tests pass, covering each public function: filter helpers, KPI sums across mixed sale shapes, YoY direction on two same-length consecutive windows, group/category aggregation including the implicit Labour category and the i18n-key contract, per-entity attribution including the "Not yet" sentinel and the all-tab syntheticfarm_generalrow, year extraction excluding the current year, and the dynamic-year option shape.NODE_OPTIONS=--max_old_space_size=3000 pnpm exec tsc --noEmitfrompackages/webapppasses with no errors. No UI changed in this PR — visual review is not applicable; it will land with the presentational-components PR that follows.Checklist:
pnpm i18nto help with this)