From 2bc0db6f33fc484972c14806d9b68ec5bccd47c5 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Thu, 1 Oct 2026 12:27:24 +0200 Subject: [PATCH] fix(statistics): feeding totals per unit instead of adding litres to kilograms The feeding statistics summed every amount regardless of its unit, so 5 l syrup and 2 kg fondant showed as 7. The API now also returns byUnit (units normalised: "L" = "l"), the old fields stay for compatibility, and the statistics page shows one list per unit. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../src/statistics/statistics.service.spec.ts | 20 ++++ .../src/statistics/statistics.service.ts | 16 ++- .../src/app/(app)/statistics/page.tsx | 99 +++++++++++-------- ApiCore-Frontend/src/lib/statistics/types.ts | 2 + 4 files changed, 92 insertions(+), 45 deletions(-) diff --git a/ApiCore-Backend/src/statistics/statistics.service.spec.ts b/ApiCore-Backend/src/statistics/statistics.service.spec.ts index 7a0f827..29cd5a1 100644 --- a/ApiCore-Backend/src/statistics/statistics.service.spec.ts +++ b/ApiCore-Backend/src/statistics/statistics.service.spec.ts @@ -212,5 +212,25 @@ describe('StatisticsService', () => { expect(result.byColony).toEqual({ c1: 8, c2: 2 }); expect(result.bySeason).toEqual({ '2025': 8, '2024': 2 }); }); + + it('never adds litres and kilograms together', async () => { + prisma.feeding.findMany.mockResolvedValueOnce([ + { colonyId: 'c1', date: new Date('2025-09-01'), amount: 5, unit: 'l' }, + { colonyId: 'c1', date: new Date('2025-09-15'), amount: 2, unit: 'kg' }, + { + colonyId: 'c1', + date: new Date('2025-10-01'), + amount: 1, + unit: ' L ', + }, + ]); + + const result = await service.feeding(); + + expect(result.byUnit).toEqual({ + l: { byColony: { c1: 6 }, bySeason: { '2025': 6 } }, + kg: { byColony: { c1: 2 }, bySeason: { '2025': 2 } }, + }); + }); }); }); diff --git a/ApiCore-Backend/src/statistics/statistics.service.ts b/ApiCore-Backend/src/statistics/statistics.service.ts index 26d8301..fc6e39e 100644 --- a/ApiCore-Backend/src/statistics/statistics.service.ts +++ b/ApiCore-Backend/src/statistics/statistics.service.ts @@ -206,15 +206,27 @@ export class StatisticsService { select: { colonyId: true, date: true, amount: true, unit: true }, }); + // Litres and kilograms cannot be added up: totals are kept per unit + // (byUnit). byColony/bySeason stay for older clients and are only + // meaningful when a single unit is in use. const byColony: Record = {}; const bySeason: Record = {}; + const byUnit: Record< + string, + { byColony: Record; bySeason: Record } + > = {}; for (const feeding of feedings) { + const year = feeding.date.getFullYear().toString(); + const unit = feeding.unit.trim().toLowerCase() || 'ohne Einheit'; byColony[feeding.colonyId] = (byColony[feeding.colonyId] ?? 0) + feeding.amount; - const year = feeding.date.getFullYear().toString(); bySeason[year] = (bySeason[year] ?? 0) + feeding.amount; + const totals = (byUnit[unit] ??= { byColony: {}, bySeason: {} }); + totals.byColony[feeding.colonyId] = + (totals.byColony[feeding.colonyId] ?? 0) + feeding.amount; + totals.bySeason[year] = (totals.bySeason[year] ?? 0) + feeding.amount; } - return { byColony, bySeason }; + return { byColony, bySeason, byUnit }; } } diff --git a/ApiCore-Frontend/src/app/(app)/statistics/page.tsx b/ApiCore-Frontend/src/app/(app)/statistics/page.tsx index 59adc18..cbd2491 100644 --- a/ApiCore-Frontend/src/app/(app)/statistics/page.tsx +++ b/ApiCore-Frontend/src/app/(app)/statistics/page.tsx @@ -293,49 +293,62 @@ function FeedingStatsPanel({ colonyLabels }: { colonyLabels: Map return ( - {(data) => ( -
- - Summe der eingetragenen Futtermengen in Liter bzw. kg, so wie sie bei der Fütterung - erfasst wurden. - - - - Futterverbrauch pro Volk - - - b - a) - .slice(0, 10) - .map(([colonyId, value]) => ({ - label: colonyLabels.get(colonyId) ?? colonyId, - value, - unit: FEED_UNIT, - }))} - formatValue={kg} - emptyText="Noch keine Fütterungen erfasst." - /> - - - - - Futterverbrauch pro Saison - - - ({ - label: year, - value, - unit: FEED_UNIT, - }))} - formatValue={kg} - emptyText="Noch keine Fütterungen erfasst." - /> - - -
- )} + {(data) => { + // One block per unit: litres and kilograms are never added up. + const units = Object.entries( + data.byUnit ?? { [FEED_UNIT]: { byColony: data.byColony, bySeason: data.bySeason } }, + ); + return ( +
+ + Summe der eingetragenen Futtermengen, getrennt nach Einheit – so wie sie bei der + Fütterung erfasst wurden. + + {units.length === 0 ? ( +

Noch keine Fütterungen erfasst.

+ ) : null} + {units.map(([unit, totals]) => ( +
+ + + Futterverbrauch pro Volk ({unit}) + + + b - a) + .slice(0, 10) + .map(([colonyId, value]) => ({ + label: colonyLabels.get(colonyId) ?? colonyId, + value, + unit, + }))} + formatValue={kg} + emptyText="Noch keine Fütterungen erfasst." + /> + + + + + Futterverbrauch pro Saison ({unit}) + + + ({ + label: year, + value, + unit, + }))} + formatValue={kg} + emptyText="Noch keine Fütterungen erfasst." + /> + + +
+ ))} +
+ ); + }}
); } diff --git a/ApiCore-Frontend/src/lib/statistics/types.ts b/ApiCore-Frontend/src/lib/statistics/types.ts index b21a102..668d463 100644 --- a/ApiCore-Frontend/src/lib/statistics/types.ts +++ b/ApiCore-Frontend/src/lib/statistics/types.ts @@ -48,4 +48,6 @@ export interface HoneyStats { export interface FeedingStats { byColony: Record; bySeason: Record; + /** Totals per unit ("l", "kg", ...) - units are never added together. */ + byUnit?: Record; bySeason: Record }>; }