fix(sync): dashboard sync covers current year plus Nov-Dec of previous #47
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-10-08 12:04 CEST — fix(sync): dashboard sync covers the current year plus Nov–Dec of the previous one
|
||||||
|
|
||||||
|
- `/sync-bank` now uses one shared default window in both implementations: 1 November of the previous year to 31 December of the current year. Payments dated around New Year are picked up in January; duplicates are still filtered by Sync ID.
|
||||||
|
- Python previously used a hardcoded `2026-01-01`..`2026-12-31` and would have stopped syncing on 2027-01-01; Go used the current calendar year only.
|
||||||
|
- Added `make sync-year [YEAR=N]` with the same window; `make sync-2026` is kept. The CLI default without flags (last 30 days) is unchanged.
|
||||||
|
- Key files: `go/internal/services/banksync/sync.go` (`DefaultWindow`), `go/cmd/fuj/main.go`, `legacy/scripts/sync_fio_to_sheets.py` (`default_sync_window`), `legacy/app.py`.
|
||||||
|
|
||||||
## 2026-10-08 11:53 CEST — feat(fees): adult rates for 2026-07 through 2026-12, default 600
|
## 2026-10-08 11:53 CEST — feat(fees): adult rates for 2026-07 through 2026-12, default 600
|
||||||
|
|
||||||
- Adult default monthly rate (months not in the table): 700 → 600 CZK.
|
- Adult default monthly rate (months not in the table): 700 → 600 CZK.
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ make web-debug # Same with FLASK_DEBUG=1
|
|||||||
make test # Run all tests (unittest discover)
|
make test # Run all tests (unittest discover)
|
||||||
make test-v # Tests with verbose output
|
make test-v # Tests with verbose output
|
||||||
make fees # Print fee report from attendance sheet
|
make fees # Print fee report from attendance sheet
|
||||||
make sync-2026 # Sync Fio bank transactions for 2026 to Google Sheets
|
make sync-year # Sync Fio transactions for the current year plus Nov-Dec of the previous one (YEAR=N to override)
|
||||||
make infer # Auto-fill Person/Purpose/Amount columns in payments sheet
|
make infer # Auto-fill Person/Purpose/Amount columns in payments sheet
|
||||||
make reconcile # Print balance report from Google Sheets data
|
make reconcile # Print balance report from Google Sheets data
|
||||||
make image # Build the legacy Python Docker image
|
make image # Build the legacy Python Docker image
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: help fees match web web-py web-debug web-go go-build go-test go-test-all go-parity go-run go-sync-debug go-lint capture-fixtures parity image run sync sync-2026 test test-v docs
|
.PHONY: help fees match web web-py web-debug web-go go-build go-test go-test-all go-parity go-run go-sync-debug go-lint capture-fixtures parity image run sync sync-2026 sync-year test test-v docs
|
||||||
|
|
||||||
# Legacy Python implementation lives in legacy/ (Go in go/ is primary).
|
# Legacy Python implementation lives in legacy/ (Go in go/ is primary).
|
||||||
LEGACY := legacy
|
LEGACY := legacy
|
||||||
@@ -39,6 +39,7 @@ help:
|
|||||||
@echo " make sync - Sync Fio transactions to Google Sheets"
|
@echo " make sync - Sync Fio transactions to Google Sheets"
|
||||||
@echo " make sync-2025 - Sync Fio transactions for Q4 2025 (Oct-Dec)"
|
@echo " make sync-2025 - Sync Fio transactions for Q4 2025 (Oct-Dec)"
|
||||||
@echo " make sync-2026 - Sync Fio transactions for the whole year of 2026"
|
@echo " make sync-2026 - Sync Fio transactions for the whole year of 2026"
|
||||||
|
@echo " make sync-year [YEAR=N] - Sync Fio transactions for YEAR plus Nov-Dec of the year before (default: current year)"
|
||||||
@echo " make sync-debug [DAYS=N] - Dry-run Python sync with Fio diagnostics and txn table (default DAYS=30)"
|
@echo " make sync-debug [DAYS=N] - Dry-run Python sync with Fio diagnostics and txn table (default DAYS=30)"
|
||||||
@echo " make infer - Infer payment details (Person, Purpose, Amount) in the sheet"
|
@echo " make infer - Infer payment details (Person, Purpose, Amount) in the sheet"
|
||||||
@echo " make reconcile - Show balance report using Google Sheets data"
|
@echo " make reconcile - Show balance report using Google Sheets data"
|
||||||
@@ -130,6 +131,10 @@ sync-2025: $(PYTHON)
|
|||||||
sync-2026: $(PYTHON)
|
sync-2026: $(PYTHON)
|
||||||
$(PYTHON) $(LEGACY)/scripts/sync_fio_to_sheets.py --credentials $(CREDENTIALS) --from 2026-01-01 --to 2026-12-31 --sort-by-date
|
$(PYTHON) $(LEGACY)/scripts/sync_fio_to_sheets.py --credentials $(CREDENTIALS) --from 2026-01-01 --to 2026-12-31 --sort-by-date
|
||||||
|
|
||||||
|
YEAR ?= $(shell date +%Y)
|
||||||
|
sync-year: $(PYTHON)
|
||||||
|
$(PYTHON) $(LEGACY)/scripts/sync_fio_to_sheets.py --credentials $(CREDENTIALS) --from $(shell expr $(YEAR) - 1)-11-01 --to $(YEAR)-12-31 --sort-by-date
|
||||||
|
|
||||||
sync-debug: $(PYTHON) ## Dry-run Python sync with Fio diagnostics and txn table (default DAYS=30)
|
sync-debug: $(PYTHON) ## Dry-run Python sync with Fio diagnostics and txn table (default DAYS=30)
|
||||||
$(PYTHON) $(LEGACY)/scripts/sync_fio_to_sheets.py --credentials $(CREDENTIALS) --days $(DAYS) --dry-run --print-fio-table
|
$(PYTHON) $(LEGACY)/scripts/sync_fio_to_sheets.py --credentials $(CREDENTIALS) --days $(DAYS) --dry-run --print-fio-table
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ before new feature work starts. Companion to the Go rewrite tracker,
|
|||||||
- September and October 2026 are billed at the defaults: 700 CZK adults,
|
- September and October 2026 are billed at the defaults: 700 CZK adults,
|
||||||
500 CZK juniors.
|
500 CZK juniors.
|
||||||
- If those are not the intended rates, add the months in both files.
|
- If those are not the intended rates, add the months in both files.
|
||||||
- [ ] **T3** Remove the hardcoded 2026 window from Python `/sync-bank`
|
- [x] **T3** Done 2026-10-08, extended: both dashboards now sync the
|
||||||
|
current year plus November–December of the previous one. Original task:
|
||||||
|
remove the hardcoded 2026 window from Python `/sync-bank`
|
||||||
(`app.py`, the `sync_to_sheets` call with `2026-01-01` / `2026-12-31`).
|
(`app.py`, the `sync_to_sheets` call with `2026-01-01` / `2026-12-31`).
|
||||||
- It stops picking up transactions on 2027-01-01. Go already uses the
|
- It stops picking up transactions on 2027-01-01. Go already uses the
|
||||||
current year.
|
current year.
|
||||||
|
|||||||
+1
-3
@@ -92,9 +92,7 @@ func serverCmd(args []string) {
|
|||||||
|
|
||||||
actions := web.ActionHandlers{
|
actions := web.ActionHandlers{
|
||||||
BankSync: func(ctx context.Context, out io.Writer) error {
|
BankSync: func(ctx context.Context, out io.Writer) error {
|
||||||
yr := time.Now().Year()
|
from, to := banksync.DefaultWindow(time.Now())
|
||||||
from := time.Date(yr, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
to := time.Date(yr, 12, 31, 23, 59, 59, 0, time.UTC)
|
|
||||||
|
|
||||||
fmt.Fprintln(out, "=== Sync Fio Transactions ===")
|
fmt.Fprintln(out, "=== Sync Fio Transactions ===")
|
||||||
n, err := banksync.SyncToSheets(ctx, config.PaymentsSheetID, fioClients, sheetsCli,
|
n, err := banksync.SyncToSheets(ctx, config.PaymentsSheetID, fioClients, sheetsCli,
|
||||||
|
|||||||
@@ -36,6 +36,18 @@ type SyncOpts struct {
|
|||||||
PrintFioTable bool // with DryRun: print every fetched Fio txn with NEW/DUP status
|
PrintFioTable bool // with DryRun: print every fetched Fio txn with NEW/DUP status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultWindow is the window used by the dashboard sync: the whole current
|
||||||
|
// calendar year plus the last two months of the previous one, so payments
|
||||||
|
// dated around New Year are still picked up in January. Re-fetching is safe
|
||||||
|
// because rows are deduplicated by Sync ID.
|
||||||
|
// Mirrors default_sync_window in legacy/scripts/sync_fio_to_sheets.py.
|
||||||
|
func DefaultWindow(now time.Time) (from, to time.Time) {
|
||||||
|
yr := now.Year()
|
||||||
|
from = time.Date(yr-1, time.November, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
to = time.Date(yr, time.December, 31, 23, 59, 59, 0, time.UTC)
|
||||||
|
return from, to
|
||||||
|
}
|
||||||
|
|
||||||
// SyncToSheets fetches Fio transactions from all provided clients and appends
|
// SyncToSheets fetches Fio transactions from all provided clients and appends
|
||||||
// new ones to the payments sheet. Returns the number of rows appended.
|
// new ones to the payments sheet. Returns the number of rows appended.
|
||||||
// Ports legacy/scripts/sync_fio_to_sheets.py sync_to_sheets.
|
// Ports legacy/scripts/sync_fio_to_sheets.py sync_to_sheets.
|
||||||
|
|||||||
@@ -193,3 +193,23 @@ func syncIDFor(tx fio.Transaction) string {
|
|||||||
Sender: tx.Sender, VS: tx.VS, Message: tx.Message, BankID: tx.BankID,
|
Sender: tx.Sender, VS: tx.VS, Message: tx.Message, BankID: tx.BankID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultWindow(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
now time.Time
|
||||||
|
from, to string
|
||||||
|
}{
|
||||||
|
{time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC), "2025-11-01", "2026-12-31"},
|
||||||
|
{time.Date(2027, 1, 1, 0, 0, 0, 0, time.UTC), "2026-11-01", "2027-12-31"},
|
||||||
|
{time.Date(2027, 12, 31, 23, 0, 0, 0, time.UTC), "2026-11-01", "2027-12-31"},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
from, to := DefaultWindow(tc.now)
|
||||||
|
if got := from.Format("2006-01-02"); got != tc.from {
|
||||||
|
t.Errorf("DefaultWindow(%s) from: want %s, got %s", tc.now.Format("2006-01-02"), tc.from, got)
|
||||||
|
}
|
||||||
|
if got := to.Format("2006-01-02"); got != tc.to {
|
||||||
|
t.Errorf("DefaultWindow(%s) to: want %s, got %s", tc.now.Format("2006-01-02"), tc.to, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+6
-5
@@ -30,7 +30,7 @@ from views import (
|
|||||||
adapt_junior_members,
|
adapt_junior_members,
|
||||||
)
|
)
|
||||||
from cache_utils import get_sheet_modified_time, read_cache, write_cache, _LAST_CHECKED, flush_cache
|
from cache_utils import get_sheet_modified_time, read_cache, write_cache, _LAST_CHECKED, flush_cache
|
||||||
from sync_fio_to_sheets import sync_to_sheets
|
from sync_fio_to_sheets import sync_to_sheets, default_sync_window
|
||||||
from infer_payments import infer_payments
|
from infer_payments import infer_payments
|
||||||
|
|
||||||
def get_cached_data(cache_key, sheet_id, fetch_func, *args, serialize=None, deserialize=None, **kwargs):
|
def get_cached_data(cache_key, sheet_id, fetch_func, *args, serialize=None, deserialize=None, **kwargs):
|
||||||
@@ -128,13 +128,14 @@ def sync_bank():
|
|||||||
success = True
|
success = True
|
||||||
try:
|
try:
|
||||||
with contextlib.redirect_stdout(output), contextlib.redirect_stderr(output):
|
with contextlib.redirect_stdout(output), contextlib.redirect_stderr(output):
|
||||||
# sync_to_sheets: equivalent of make sync-2026
|
# sync_to_sheets: equivalent of make sync-year (same default window as Go)
|
||||||
output.write("=== Syncing Fio transactions (2026) ===\n")
|
date_from, date_to = default_sync_window()
|
||||||
|
output.write(f"=== Syncing Fio transactions ({date_from} to {date_to}) ===\n")
|
||||||
sync_to_sheets(
|
sync_to_sheets(
|
||||||
spreadsheet_id=PAYMENTS_SHEET_ID,
|
spreadsheet_id=PAYMENTS_SHEET_ID,
|
||||||
credentials_path=CREDENTIALS_PATH,
|
credentials_path=CREDENTIALS_PATH,
|
||||||
date_from_str="2026-01-01",
|
date_from_str=date_from,
|
||||||
date_to_str="2026-12-31",
|
date_to_str=date_to,
|
||||||
sort_by_date=True,
|
sort_by_date=True,
|
||||||
)
|
)
|
||||||
output.write("\n=== Inferring payment details ===\n")
|
output.write("\n=== Inferring payment details ===\n")
|
||||||
|
|||||||
@@ -59,6 +59,18 @@ def get_sheets_service(credentials_path: str):
|
|||||||
return _SHEETS_SERVICE
|
return _SHEETS_SERVICE
|
||||||
|
|
||||||
|
|
||||||
|
def default_sync_window(today=None) -> tuple[str, str]:
|
||||||
|
"""Window used by the dashboard sync, as (from, to) in YYYY-MM-DD.
|
||||||
|
|
||||||
|
The whole current calendar year plus the last two months of the previous
|
||||||
|
one, so payments dated around New Year are still picked up in January.
|
||||||
|
Re-fetching is safe because rows are deduplicated by Sync ID.
|
||||||
|
Mirrors DefaultWindow in go/internal/services/banksync/sync.go.
|
||||||
|
"""
|
||||||
|
year = (today or datetime.now()).year
|
||||||
|
return f"{year - 1}-11-01", f"{year}-12-31"
|
||||||
|
|
||||||
|
|
||||||
def as_text_cell(value) -> str:
|
def as_text_cell(value) -> str:
|
||||||
"""Mark a value to be stored as text when written with USER_ENTERED.
|
"""Mark a value to be stored as text when written with USER_ENTERED.
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,20 @@ class TestWebApp(unittest.TestCase):
|
|||||||
app.config['TESTING'] = True
|
app.config['TESTING'] = True
|
||||||
self.client = app.test_client()
|
self.client = app.test_client()
|
||||||
|
|
||||||
|
@patch('app.flush_cache', return_value=0)
|
||||||
|
@patch('app.infer_payments')
|
||||||
|
@patch('app.sync_to_sheets')
|
||||||
|
def test_sync_bank_uses_default_window(self, mock_sync, mock_infer, mock_flush):
|
||||||
|
"""/sync-bank syncs the current year plus Nov-Dec of the previous one, not a hardcoded year."""
|
||||||
|
from datetime import datetime
|
||||||
|
year = datetime.now().year
|
||||||
|
response = self.client.get('/sync-bank')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
kwargs = mock_sync.call_args.kwargs
|
||||||
|
self.assertEqual(kwargs['date_from_str'], f"{year - 1}-11-01")
|
||||||
|
self.assertEqual(kwargs['date_to_str'], f"{year}-12-31")
|
||||||
|
mock_infer.assert_called_once()
|
||||||
|
|
||||||
def test_index_page(self):
|
def test_index_page(self):
|
||||||
"""Test that / returns the refresh meta tag"""
|
"""Test that / returns the refresh meta tag"""
|
||||||
response = self.client.get('/')
|
response = self.client.get('/')
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
import io
|
import io
|
||||||
import unittest
|
import unittest
|
||||||
|
from datetime import datetime
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from scripts.sync_fio_to_sheets import COLUMN_LABELS, as_text_cell, sync_to_sheets
|
from scripts.sync_fio_to_sheets import COLUMN_LABELS, as_text_cell, default_sync_window, sync_to_sheets
|
||||||
|
|
||||||
|
|
||||||
|
class TestDefaultSyncWindow(unittest.TestCase):
|
||||||
|
def test_current_year_plus_last_two_months_of_previous(self):
|
||||||
|
self.assertEqual(default_sync_window(datetime(2026, 10, 8)), ("2025-11-01", "2026-12-31"))
|
||||||
|
self.assertEqual(default_sync_window(datetime(2027, 1, 1)), ("2026-11-01", "2027-12-31"))
|
||||||
|
self.assertEqual(default_sync_window(datetime(2027, 12, 31)), ("2026-11-01", "2027-12-31"))
|
||||||
|
|
||||||
|
|
||||||
class TestAsTextCell(unittest.TestCase):
|
class TestAsTextCell(unittest.TestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user