Repository navigation
feat(reports): show feed cost per dozen - #1306
Merged
Merged
Conversation
Sum FeedUsage.EstimatedCost over the report range in GetProfitAsync and divide by the official eggs the production report counts (× 12), returned as feedCostMinorUnits and feedCostPerDozenMinorUnits — the latter null for a range with no eggs and rounded to a whole minor unit, midpoint to even. Every feed row is already in the account's current currency (FeedUsageRepository is an ICurrencyBoundRowSource), so no per-row filter. The Money section shows the figure between expenses and profit with the range total beneath it, with en/es/tl strings, a glossary entry and Help prose covering the zero-cost-lot caveat. Integration tests pin the range filter, the official-only denominator, the to-even rounding and the null; the Manager 403 joins the Worker's in the role-split test. Closes #1298
mforce
force-pushed
the
feat/1298-feed-cost-per-dozen
branch
from
October 15, 2026 01:49
3ffbde6 to
bc574aa
Compare
…403, tl typos Four review findings on #1306: - Reports Help (en/es/tl) described the KPI as feed over eggs collected with no factor of twelve; now "feed cost in the range divided by dozens collected (official eggs ÷ 12)", matching the glossary. - The two positive-denominator fixtures had no cracked/dirty/discarded eggs, so a sellable-eggs denominator passed every rate assertion. Both now carry 24 discarded of 240 (grades reconciled to 216) and the rate is asserted against the production response's TotalEggs. The sellable mutation was run and goes red at 1,944 vs 1,750. - RangeGuards_And_RoleSplit gains a Sales user asserting 403 on /reports/profit; the comment no longer calls the Manager a non-admin (AdminOnly is Owner + Manager since #103). - Tagalog: dosezena → dosena (label, Help, glossary), italog → itlog.
Owner
Author
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.




Spec §19.3 defines feed cost per dozen as feed cost over dozens produced. The cost was already recorded: every
FeedUsagerow carriesEstimatedCost(lot-cost costing, §12.4). This PR computes the figure server-side inReportQueries.GetProfitAsyncand shows it in the Reports page's admin Money section, between Expenses and profit.Closes #1298
The formula
The denominator is the same eggs the production report counts: the
OfficialStatusesfilterReportQueriesalready uses (Submitted, Locked, ManagerAdjusted). Drafts and voided entries contribute nothing. It reuses that filter rather than writing a second copy of the rule./reports/profitis alreadyAdminOnly, so no new endpoint and no auth change. The response addsfeedCostMinorUnitsandfeedCostPerDozenMinorUnits, both in minor units with the currency fields the report already carries.Rounding
The per-dozen minor units are rounded to a whole minor unit with
Math.Round's default midpoint rule, to even: 2,050 × 12 / 240 = 102.5 exactly answers 102, not 103. A range with no eggs returns null, shown as "—", never 0 or infinity. Both rules have their own integration tests.Currency
The farm currency cannot change once feed usage rows exist, so no per-row currency filter is needed and no row can be left out:
FeedUsageRepositoryimplements Farm'sICurrencyBoundRowSourcewithAnyAsyncreadingdb.FeedUsages(refactor(arch): move repositories and EF configurations into their modules #1097).UpdateFarmSettingsHandlerprobes everyICurrencyBoundRowSourceinside its transaction and passes the result toAccount.UpdateSettings, which refuses a currency change withAccount.CurrencyLockedwhen any such row exists.So a farm with feed rows is pinned to the currency those rows were stamped in, and summing all
EstimatedCostvalues in the range never mixes currencies. The "feed in another currency is excluded" test from the issue does not apply, because the state cannot arise.Zero-cost lots
Feed taken from a lot with no unit cost adds 0, so the figure can read low. The Help text says so plainly (en, es, tl) and the glossary repeats it; the UI does not hide it.
UI
One row in the Money section, after Expenses: the per-dozen figure via
fmt.money, with the range total beneath it as the secondary line, like the expenses row. Strings in en, es and tl;specs/product/GLOSSARY.mdand the in-app glossary define the term; the Reports Help section covers it.Tests
Integration (real Postgres,
tests/Cluckwork.Api.IntegrationTests/ReportsTests.cs):Profit_FeedCostPerDozen_SumsInRangeFeed_OverOfficialEggsseeds 240 submitted eggs and feed worth 35,000 in range plus 7,500 at day −10; the report answers 1,750 per dozen.Profit_FeedCostPerDozen_RoundsMidpointToEvenseeds 2,050 over 240 eggs and expects 102.Profit_ZeroEggRange_NullPerDozenrecords feed but no official eggs (a Draft's 50 eggs don't count) and expects null with the total still reported.RangeGuards_And_RoleSplitpins the role boundary: a Worker gets 403 on the money reports and 200 on production. A Manager reads the money reports, becauseAdminOnlyhas meant Owner + Manager since F27: Roles + scoped permissions — role model, flock-scoped workers, RBAC UI (builds on #73) #103 (AuthPolicies.cs:50), and the SPA'sisAdminmatches. The issue's draft criterion said a Manager cannot read the figure; that does not hold on this platform, so the test pins the real behavior instead. The figure is in the existing Money section, which has always been visible to Managers.ReportsPage.test.tsxcovers the row for an admin and the dash for a null figure.Mutations
Each mutation was applied to
ReportQueries.GetProfitAsync, rebuilt, and confirmed to turn the named test red, then reverted:Profit_FeedCostPerDozen_SumsInRangeFeed_OverOfficialEggs(35,000 becomes 42,500)Profit_FeedCostPerDozen_SumsInRangeFeed_OverOfficialEggs(1,750 becomes 146)Profit_ZeroEggRange_NullPerDozenProfit_FeedCostPerDozen_SumsInRangeFeed_OverOfficialEggs(1,750 becomes 1,944; the fixture carries 24 discarded of 240)OfficialStatusesfrom the eggs query)Profit_ZeroEggRange_NullPerDozenModule ledger
ReportQueriesnow readsFeedUsages, so theInsights → GeneralInventoryedge's symbol list gainsReportQueries(it already namedExportQueriesfor the same owner).ModuleLedgerRealTreeTests,SeamSurfaceRealAssemblyTests,AdapterReachRealTreeTestsandCouplingMatrixRealTreeTestspass; the coupling matrix is regenerated.Precache
PRECACHE_CEILING_KIBmoves from 1,665 to 1,675 KiB in its own commit (chore(web): raise the precache ceiling to 1675 KiB for the report KPIs), per the maintainer's approval covering #1298–#1300 in one decision. This branch measures 1,665.81 KiB (npm run build+verify:sw).Screenshots
Before/after at desktop 1280×800 and phone 390×844, same scenario, admin, default 7-day range, from two isolated stacks (before
9aad11a0oncw1298b, after the merged head oncw1298), fixture priced at 42,500 minor units of feed in range: #1306 (comment)