From d7bbb1d94dc18381dbcd76ba9243cd45648c0d08 Mon Sep 17 00:00:00 2001 From: Simon Zangerl Date: Fri, 5 Jun 2026 17:09:12 +0200 Subject: [PATCH 1/7] Initial thoughts written down after analyzing database schema and DB seed file --- NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 NOTES.md diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 000000000..e33cc6cf4 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,4 @@ +# Initial thoughts +- First red flag: Properties with same id (not actually a PK) but different tenant. Tenant check forgotten somewhere? +- Second red flag: Off by a few cents -> amounts stored in DB with sub-cent precision - good. Possibly incorrectly handled by backend or frontend? +- Wrong data for march: Frontend shows $1000 which matches revenue from batched insert statement. But separate insert into for tenant a is not taken into account. ID references timezone which could be the culprit, especially since the check in was during a leap year on feb. 29th right before midnight. Need to investigate! \ No newline at end of file From 644ac8d406e29d260a7ad8f6014af2a2335522c2 Mon Sep 17 00:00:00 2001 From: Simon Zangerl Date: Fri, 5 Jun 2026 17:12:43 +0200 Subject: [PATCH 2/7] Chore: Added .gitignore files to exclude node_modules .env files could be added but currently holds no real secrets - .env.local possibly? --- .gitignore | 22 ++++++++++++++++++++++ frontend/.gitignore | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 .gitignore create mode 100644 frontend/.gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..17167547b --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +node_modules/ + +dist/ +build/ + +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +.DS_Store +.idea/ +*.swp + +__pycache__/ +*.py[cod] +.venv/ +venv/ +.pytest_cache/ + +*.timestamp-*.mjs +ackages/react-devtools-timeline/dist \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 000000000..17167547b --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,22 @@ +node_modules/ + +dist/ +build/ + +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +.DS_Store +.idea/ +*.swp + +__pycache__/ +*.py[cod] +.venv/ +venv/ +.pytest_cache/ + +*.timestamp-*.mjs +ackages/react-devtools-timeline/dist \ No newline at end of file From 3191ddb9e30c2388d77de5554aabe213f8508593 Mon Sep 17 00:00:00 2001 From: Simon Zangerl Date: Fri, 5 Jun 2026 17:21:06 +0200 Subject: [PATCH 3/7] Bugfix: Backend cache lookup omits tenant_id from key causing shared cache hits between tenants --- NOTES.md | 2 +- backend/app/services/cache.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NOTES.md b/NOTES.md index e33cc6cf4..5813ba00a 100644 --- a/NOTES.md +++ b/NOTES.md @@ -1,4 +1,4 @@ # Initial thoughts -- First red flag: Properties with same id (not actually a PK) but different tenant. Tenant check forgotten somewhere? +- First red flag: Properties with same id (not actually a PK) but different tenant. Tenant check forgotten somewhere? Correct assumption. Cache key forgot tenant_id, shared cache hits across property_ids, cross tenant. Slight correction: Missed `PRIMARY KEY (id, tenant_id)` before, so DB schema *is* fine. - Second red flag: Off by a few cents -> amounts stored in DB with sub-cent precision - good. Possibly incorrectly handled by backend or frontend? - Wrong data for march: Frontend shows $1000 which matches revenue from batched insert statement. But separate insert into for tenant a is not taken into account. ID references timezone which could be the culprit, especially since the check in was during a leap year on feb. 29th right before midnight. Need to investigate! \ No newline at end of file diff --git a/backend/app/services/cache.py b/backend/app/services/cache.py index b81474957..672e3f9d6 100644 --- a/backend/app/services/cache.py +++ b/backend/app/services/cache.py @@ -10,7 +10,7 @@ async def get_revenue_summary(property_id: str, tenant_id: str) -> Dict[str, Any """ Fetches revenue summary, utilizing caching to improve performance. """ - cache_key = f"revenue:{property_id}" + cache_key = f"revenue:{tenant_id}:{property_id}" # Try to get from cache cached = await redis_client.get(cache_key) From 986b9495fac174e2624890df598cba3b1d6159ff Mon Sep 17 00:00:00 2001 From: Simon Zangerl Date: Fri, 5 Jun 2026 17:29:58 +0200 Subject: [PATCH 4/7] Intermediate findings --- NOTES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NOTES.md b/NOTES.md index 5813ba00a..c1d5ad137 100644 --- a/NOTES.md +++ b/NOTES.md @@ -1,4 +1,7 @@ # Initial thoughts - First red flag: Properties with same id (not actually a PK) but different tenant. Tenant check forgotten somewhere? Correct assumption. Cache key forgot tenant_id, shared cache hits across property_ids, cross tenant. Slight correction: Missed `PRIMARY KEY (id, tenant_id)` before, so DB schema *is* fine. - Second red flag: Off by a few cents -> amounts stored in DB with sub-cent precision - good. Possibly incorrectly handled by backend or frontend? -- Wrong data for march: Frontend shows $1000 which matches revenue from batched insert statement. But separate insert into for tenant a is not taken into account. ID references timezone which could be the culprit, especially since the check in was during a leap year on feb. 29th right before midnight. Need to investigate! \ No newline at end of file +- Wrong data for march: Frontend shows $1000 which matches revenue from batched insert statement. But separate insert into for tenant a is not taken into account. ID references timezone which could be the culprit, especially since the check in was during a leap year on feb. 29th right before midnight. Need to investigate! + +# Docker issues(?) +- Trying to see if the caching issue is resolved, I stumbled across the following: The data I am seeing in the dashboard is not coming from the DB and just happened to match the seed. It was also identical for both tenants which I had thought was due to the caching issue, but now I realize, upon closer inspection of the docker logs, that the connection to the DB actually fails currently, and the application is returning dummy data (see `reservations.py`s Except block). Need to fix the connection to validate cache fix worked and also cent precision. Assuming revenue for tenant a will **not** be $1000 on the dot.. \ No newline at end of file From d635daa7ae2800385803864cbf44b573dfeaac0b Mon Sep 17 00:00:00 2001 From: Simon Zangerl Date: Fri, 5 Jun 2026 17:51:20 +0200 Subject: [PATCH 5/7] Bugfix: Change monthly revenue query to be timezone aware As DB connection problems currently remain, ran in docker shell. Prev. result: 1000, Current result: 2500 --- NOTES.md | 2 +- backend/app/services/reservations.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/NOTES.md b/NOTES.md index c1d5ad137..37b61cfd9 100644 --- a/NOTES.md +++ b/NOTES.md @@ -1,7 +1,7 @@ # Initial thoughts - First red flag: Properties with same id (not actually a PK) but different tenant. Tenant check forgotten somewhere? Correct assumption. Cache key forgot tenant_id, shared cache hits across property_ids, cross tenant. Slight correction: Missed `PRIMARY KEY (id, tenant_id)` before, so DB schema *is* fine. - Second red flag: Off by a few cents -> amounts stored in DB with sub-cent precision - good. Possibly incorrectly handled by backend or frontend? -- Wrong data for march: Frontend shows $1000 which matches revenue from batched insert statement. But separate insert into for tenant a is not taken into account. ID references timezone which could be the culprit, especially since the check in was during a leap year on feb. 29th right before midnight. Need to investigate! +- Wrong data for march: Frontend shows $1000 which matches revenue from batched insert statement. But separate insert into for tenant a is not taken into account. ID references timezone which could be the culprit, especially since the check in was during a leap year on feb. 29th right before midnight. Need to investigate! Bug was indeed SQL query not being tz aware. Updated the query and validated in docker shell using psql directly! # Docker issues(?) - Trying to see if the caching issue is resolved, I stumbled across the following: The data I am seeing in the dashboard is not coming from the DB and just happened to match the seed. It was also identical for both tenants which I had thought was due to the caching issue, but now I realize, upon closer inspection of the docker logs, that the connection to the DB actually fails currently, and the application is returning dummy data (see `reservations.py`s Except block). Need to fix the connection to validate cache fix worked and also cent precision. Assuming revenue for tenant a will **not** be $1000 on the dot.. \ No newline at end of file diff --git a/backend/app/services/reservations.py b/backend/app/services/reservations.py index 384bd00ab..c222a8952 100644 --- a/backend/app/services/reservations.py +++ b/backend/app/services/reservations.py @@ -19,10 +19,13 @@ async def calculate_monthly_revenue(property_id: str, month: int, year: int, db_ query = """ SELECT SUM(total_amount) as total FROM reservations - WHERE property_id = $1 - AND tenant_id = $2 - AND check_in_date >= $3 - AND check_in_date < $4 + JOIN properties p + ON p.id = reservations.property_id + AND p.tenant_id = reservations.tenant_id + WHERE reservations.property_id = $1 + AND reservations.tenant_id = $2 + AND (check_in_date AT TIME ZONE p.timezone) >= $3 + AND (check_in_date AT TIME ZONE p.timezone) < $4 """ # In production this query executes against a database session. From 2e23bf11fa76a1667c140382b9f699983208844a Mon Sep 17 00:00:00 2001 From: Simon Zangerl Date: Fri, 5 Jun 2026 18:00:19 +0200 Subject: [PATCH 6/7] Bugfix: Stop converting postgres decimal to python float - Floating point errors (IEEE 754) cause mismatch in shown data Adjusted frontend drift warning code by coercing string to number. Should really be removed entirely after this fix --- backend/app/api/v1/dashboard.py | 2 +- frontend/src/components/RevenueSummary.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/app/api/v1/dashboard.py b/backend/app/api/v1/dashboard.py index 1ec352d7e..b1ab80230 100644 --- a/backend/app/api/v1/dashboard.py +++ b/backend/app/api/v1/dashboard.py @@ -15,7 +15,7 @@ async def get_dashboard_summary( revenue_data = await get_revenue_summary(property_id, tenant_id) - total_revenue_float = float(revenue_data['total']) + total_revenue_float = revenue_data['total'] return { "property_id": revenue_data['property_id'], diff --git a/frontend/src/components/RevenueSummary.tsx b/frontend/src/components/RevenueSummary.tsx index dbb6d0629..787b67c5c 100644 --- a/frontend/src/components/RevenueSummary.tsx +++ b/frontend/src/components/RevenueSummary.tsx @@ -3,7 +3,7 @@ import { SecureAPI } from '../lib/secureApi'; interface RevenueData { property_id: string; - total_revenue: number; + total_revenue: string; currency: string; reservations_count: number; } @@ -61,7 +61,7 @@ export const RevenueSummary: React.FC = ({ propertyId = 'pr if (error) return
{error}
; if (!data) return null; - const displayTotal = Math.round(data.total_revenue * 100) / 100; + const displayTotal = Math.round(+data.total_revenue * 100) / 100; return (
@@ -104,7 +104,7 @@ export const RevenueSummary: React.FC = ({ propertyId = 'pr {/* Precision Warning Area */}
- {Math.abs(data.total_revenue - displayTotal) > 0.000001 && showRaw && ( + {Math.abs(+data.total_revenue - displayTotal) > 0.000001 && showRaw && (
From 19d363d35bc410b2af2df367807223f47740faa2 Mon Sep 17 00:00:00 2001 From: Simon Zangerl Date: Fri, 5 Jun 2026 18:12:23 +0200 Subject: [PATCH 7/7] Reverted previous update of /dashboard/summary endpoint and created new /v2 version to keep backward-compatibility Implementation needs to be cleaned up and abstracted nicer, issue for now solved, without breaking possible API integrations --- backend/app/api/v1/dashboard.py | 2 +- backend/app/api/v2/dashboard.py | 25 ++++++++++++++++++++++ frontend/src/components/RevenueSummary.tsx | 2 +- frontend/src/lib/secureApi.ts | 20 +++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 backend/app/api/v2/dashboard.py diff --git a/backend/app/api/v1/dashboard.py b/backend/app/api/v1/dashboard.py index b1ab80230..1ec352d7e 100644 --- a/backend/app/api/v1/dashboard.py +++ b/backend/app/api/v1/dashboard.py @@ -15,7 +15,7 @@ async def get_dashboard_summary( revenue_data = await get_revenue_summary(property_id, tenant_id) - total_revenue_float = revenue_data['total'] + total_revenue_float = float(revenue_data['total']) return { "property_id": revenue_data['property_id'], diff --git a/backend/app/api/v2/dashboard.py b/backend/app/api/v2/dashboard.py new file mode 100644 index 000000000..b1ab80230 --- /dev/null +++ b/backend/app/api/v2/dashboard.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, Depends, HTTPException +from typing import Dict, Any +from app.services.cache import get_revenue_summary +from app.core.auth import authenticate_request as get_current_user + +router = APIRouter() + +@router.get("/dashboard/summary") +async def get_dashboard_summary( + property_id: str, + current_user: dict = Depends(get_current_user) +) -> Dict[str, Any]: + + tenant_id = getattr(current_user, "tenant_id", "default_tenant") or "default_tenant" + + revenue_data = await get_revenue_summary(property_id, tenant_id) + + total_revenue_float = revenue_data['total'] + + return { + "property_id": revenue_data['property_id'], + "total_revenue": total_revenue_float, + "currency": revenue_data['currency'], + "reservations_count": revenue_data['count'] + } diff --git a/frontend/src/components/RevenueSummary.tsx b/frontend/src/components/RevenueSummary.tsx index 787b67c5c..4846ec6ea 100644 --- a/frontend/src/components/RevenueSummary.tsx +++ b/frontend/src/components/RevenueSummary.tsx @@ -27,7 +27,7 @@ export const RevenueSummary: React.FC = ({ propertyId = 'pr try { // Use SecureAPI to handle authentication automatically // We pass the simulatedTenant option which SecureAPI will attach as a header - const response = await SecureAPI.getDashboardSummary(propertyId, { + const response = await SecureAPI.getDashboardSummaryV2(propertyId, { simulatedTenant: activeTenant, timestamp: Date.now() }); diff --git a/frontend/src/lib/secureApi.ts b/frontend/src/lib/secureApi.ts index f85f04c90..ef73c2345 100644 --- a/frontend/src/lib/secureApi.ts +++ b/frontend/src/lib/secureApi.ts @@ -1468,6 +1468,26 @@ export class SecureAPIClient { return this.request(`/api/v1/dashboard/summary?${queryParams}`, requestOptions); } + // ============= DASHBOARD API ============= + /** + * Get dashboard summary with optional simulation header (V2!) + */ + async getDashboardSummaryV2(propertyId: string, options?: { simulatedTenant?: string, timestamp?: number }) { + const queryParams = new URLSearchParams({ property_id: propertyId }); + if (options?.timestamp) { + queryParams.append('_t', options.timestamp.toString()); + } + + const requestOptions: RequestInit = {}; + if (options?.simulatedTenant) { + requestOptions.headers = { + 'X-Simulated-Tenant': options.simulatedTenant + }; + } + + return this.request(`/api/v2/dashboard/summary?${queryParams}`, requestOptions); + } + async uploadCompanyLogo(logo_url: string) { return this.request('/api/v1/company-settings/logo', { method: 'POST',