diff --git a/routes/aboutAdmin.cts b/routes/aboutAdmin.cts index 33cbf48..4a8c7f6 100644 --- a/routes/aboutAdmin.cts +++ b/routes/aboutAdmin.cts @@ -1,4 +1,3 @@ -// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services). import type { Req, Res, Next } from '../types/http'; const express = require('express'); const { log } = require('../utils/logger.cts'); @@ -9,7 +8,7 @@ const { ValidationError } = require('../utils/apiError.cts'); const router = express.Router(); -let pkg; +let pkg: any; try { pkg = require('../package.json'); } catch { @@ -23,7 +22,7 @@ const ALLOWED_FILES = { }; // Priority emoji to label mapping -const PRIORITY_MAP = { +const PRIORITY_MAP: Record = { '🔴': 'CRITICAL', '🟠': 'HIGH', '🟡': 'MEDIUM', @@ -34,7 +33,7 @@ const PRIORITY_MAP = { /** * Generate a slug from a title: lowercase, hyphens, strip emojis */ -function slugify(title) { +function slugify(title: any) { return title .replace( /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{FE00}-\u{FE0F}\u{1F000}-\u{1FAFF}]/gu, @@ -49,7 +48,7 @@ function slugify(title) { * Extract effort estimate from implementation notes text. * Matches patterns like "Estimated effort: 3-4 hours", "Estimated effort: 8 hours" */ -function extractEffort(text) { +function extractEffort(text: any) { if (!text) return null; const match = text.match(/Estimated effort:\s*(\d+(?:\s*-\s*\d+)?\s*hours?)/i); if (!match) return null; @@ -61,10 +60,10 @@ function extractEffort(text) { * Parse FUTURE.md into structured roadmap items. * Filters out completed/strikethrough items and template/meta sections. */ -function parseFutureMd(content) { +function parseFutureMd(content: any) { if (!content) return { items: [], counts: {} }; - const items = []; + const items: any[] = []; const counts = { critical: 0, high: 0, medium: 0, low: 0, niceToHave: 0 }; const lines = content.split('\n'); @@ -279,7 +278,7 @@ function parseFutureMd(content) { /** * Add a parsed item to the items array and update counts. */ -function _addItem(items, counts, emoji, label, title, bodyLines) { +function _addItem(items: any, counts: any, emoji: any, label: any, title: any, bodyLines: any) { const body = bodyLines.join('\n'); const description = _extractField(body, 'Description'); const rationale = _extractField(body, 'Rationale'); @@ -297,15 +296,17 @@ function _addItem(items, counts, emoji, label, title, bodyLines) { // Map priority label to count key const countKey = - { - CRITICAL: 'critical', - HIGH: 'high', - MEDIUM: 'medium', - LOW: 'low', - 'NICE TO HAVE': 'niceToHave', - NICE_TO_HAVE: 'niceToHave', - MEH: 'niceToHave', - }[label] || 'medium'; + ( + { + CRITICAL: 'critical', + HIGH: 'high', + MEDIUM: 'medium', + LOW: 'low', + 'NICE TO HAVE': 'niceToHave', + NICE_TO_HAVE: 'niceToHave', + MEH: 'niceToHave', + } as Record + )[label] || 'medium'; counts[countKey]++; items.push({ @@ -327,7 +328,7 @@ function _addItem(items, counts, emoji, label, title, bodyLines) { * Extract a named field from markdown body text. * Looks for **Field Name:** and captures everything until the next ** field or ### heading or end. */ -function _extractField(body, fieldName) { +function _extractField(body: any, fieldName: any) { // Match **FieldName:** followed by content until next ** or ### heading const regex = new RegExp( `\\*\\*${fieldName}:\\*\\*\\s*\n([\\s\\S]*?)(?=\\n\\*\\*[^*]|\\n###|$)`, @@ -342,7 +343,7 @@ function _extractField(body, fieldName) { * Parse DEVELOPMENT_LOG.md into structured log entries. * Returns entries sorted by date descending. */ -function parseDevLogMd(content) { +function parseDevLogMd(content: any) { if (!content) return []; const entries = []; @@ -367,10 +368,10 @@ function parseDevLogMd(content) { } // Sort by date descending - entries.sort((a, b) => { + entries.sort((a: any, b: any) => { const dateA = a.date ? new Date(a.date) : new Date(0); const dateB = b.date ? new Date(b.date) : new Date(0); - return dateB - dateA; + return dateB.getTime() - dateA.getTime(); }); return entries; @@ -379,7 +380,7 @@ function parseDevLogMd(content) { /** * Parse a single dev log entry block. */ -function _parseDevLogEntry(block, version, title) { +function _parseDevLogEntry(block: any, version: any, title: any) { // Status const statusMatch = block.match(/\*\*Status:\*\*\s*(.+)/); const status = statusMatch ? statusMatch[1].trim() : null; @@ -423,7 +424,7 @@ function _parseDevLogEntry(block, version, title) { const filesModified = filesMatch ? filesMatch[1] .split(',') - .map((f) => f.trim().replace(/^`|`$/g, '')) + .map((f: any) => f.trim().replace(/^`|`$/g, '')) .filter(Boolean) : []; @@ -433,7 +434,7 @@ function _parseDevLogEntry(block, version, title) { if (workMatch) { const items = workMatch[1].match(/- \[[ x]\] .+/g); if (items) { - workCompleted.push(...items.map((item) => item.replace(/^- \[[ x]\]\s*/, '').trim())); + workCompleted.push(...items.map((item: any) => item.replace(/^- \[[ x]\]\s*/, '').trim())); } } @@ -454,7 +455,7 @@ function _parseDevLogEntry(block, version, title) { * @param {string} content - The content to redact * @returns {string} - The redacted content */ -function redactSensitiveContent(content) { +function redactSensitiveContent(content: any) { if (!content) return content; return ( @@ -493,7 +494,7 @@ function redactSensitiveContent(content) { // ── Forgejo issues cache ────────────────────────────────────────────────────── const FORGEJO_BASE = 'https://dream.scheller.ltd/api/v1/repos/null/BillTracker'; -let _forgejoCache = null; +let _forgejoCache: any = null; let _forgejoCacheTs = 0; const FORGEJO_TTL_MS = 5 * 60 * 1000; @@ -524,7 +525,7 @@ router.get('/', requireAuth, requireAdmin, (req: Req, res: Res) => { future: sanitizedFutureContent, developmentLog: sanitizedDevLogContent, }); - } catch (err) { + } catch (err: any) { // Generic error message to prevent path disclosure log.error('[aboutAdmin] Error reading files'); res.status(500).json({ @@ -546,7 +547,7 @@ router.get('/roadmap', requireAuth, requireAdmin, async (req: Req, res: Res) => _forgejoCache = { issues, fetchedAt: new Date().toISOString() }; _forgejoCacheTs = now; res.json(_forgejoCache); - } catch (err) { + } catch (err: any) { log.error('[aboutAdmin] Forgejo issues error:', err.message); if (_forgejoCache) return res.json({ ..._forgejoCache, stale: true }); res @@ -562,7 +563,7 @@ router.get('/dev-log', requireAuth, requireAdmin, (req: Req, res: Res) => { const sanitized = redactSensitiveContent(devLogContent); const entries = parseDevLogMd(sanitized); res.json({ entries, version: pkg.version }); - } catch (err) { + } catch (err: any) { log.error('[aboutAdmin] Error reading DEVELOPMENT_LOG.md for dev-log'); res.status(500).json({ error: 'Failed to read dev log data', @@ -578,7 +579,7 @@ router.post('/check-updates', requireAuth, requireAdmin, async (req: Req, res: R try { const result = await checkForUpdates(true); res.json(result); - } catch (err) { + } catch (err: any) { res.status(500).json({ error: 'Update check failed' }); } }); diff --git a/routes/bills.cts b/routes/bills.cts index 7308399..be2a051 100644 --- a/routes/bills.cts +++ b/routes/bills.cts @@ -1,4 +1,3 @@ -// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services). import type { Req, Res, Next } from '../types/http'; const express = require('express'); const { log } = require('../utils/logger.cts'); @@ -87,7 +86,7 @@ router.get('/deleted', (req: Req, res: Res) => { ) .all(req.user.id); res.json( - rows.map((row) => ({ + rows.map((row: any) => ({ ...serializeBill(row), category_name: row.category_name, deleted_at: row.deleted_at, @@ -137,7 +136,7 @@ router.put('/reorder', (req: Req, res: Res) => { const update = db.prepare( "UPDATE bills SET sort_order = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?", ); - const applyOrder = db.transaction((items) => { + const applyOrder = db.transaction((items: any) => { for (const item of items) update.run(item.sortOrder, item.billId, req.user.id); }); applyOrder(entries); @@ -208,7 +207,7 @@ router.post('/:id/snooze-drift', (req: Req, res: Res) => { // Bill templates store money fields (expected_amount, current_balance, minimum_payment) // in integer cents, matching validateBillData's normalized output. Convert back to // dollars for API responses, mirroring serializeBill. -function serializeTemplateData(data) { +function serializeTemplateData(data: any) { if (!data) return data; const out = { ...data }; if (out.expected_amount != null) out.expected_amount = fromCents(out.expected_amount); @@ -232,7 +231,7 @@ router.get('/templates', (req: Req, res: Res) => { .all(req.user.id); res.json( - rows.map((row) => ({ + rows.map((row: any) => ({ ...row, data: serializeTemplateData(parseTemplateData(row.data)), })), @@ -812,7 +811,7 @@ router.get('/:id/transactions', (req: Req, res: Res) => { ) .all(billId, req.user.id, billId); - const transactions = rows.map((row) => + const transactions = rows.map((row: any) => decorateTransaction({ ...row, linked_payment: row.linked_payment_id @@ -1020,7 +1019,7 @@ router.post('/:id/history-ranges', (req: Req, res: Res) => { 'end_year', ); } - if (ey != null) { + if (ey != null && em != null) { const startVal = sy * 12 + sm; const endVal = ey * 12 + em; if (endVal < startVal) @@ -1174,7 +1173,7 @@ router.get('/:id/amortization', (req: Req, res: Res) => { current_balance: balance, minimum_payment: minPmt, }); - const total_interest = schedule.reduce((s, r) => s + r.interest, 0); + const total_interest = schedule.reduce((s: number, r: any) => s + r.interest, 0); res.json({ bill_id: billId, @@ -1185,7 +1184,7 @@ router.get('/:id/amortization', (req: Req, res: Res) => { summary: { months: schedule.length, total_interest: roundMoney(total_interest), - total_paid: sumMoney(schedule, (r) => r.payment), + total_paid: sumMoney(schedule, (r: any) => r.payment), capped: schedule.length >= maxMonths && schedule[schedule.length - 1]?.balance > 0, }, apr_snapshot, @@ -1258,14 +1257,14 @@ router.patch('/:id/balance', (req: Req, res: Res) => { // ── Merchant rule helpers ───────────────────────────────────────────────────── -function requireBill(db, billId, userId) { +function requireBill(db: any, billId: any, userId: any) { return db .prepare('SELECT id, name FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL') .get(billId, userId); } // Count unmatched transactions that would match a normalized merchant string. -function previewMatchCount(db, userId, normalized) { +function previewMatchCount(db: any, userId: any, normalized: any) { if (!normalized || normalized.length < 2) return 0; const txRows = db .prepare( @@ -1281,14 +1280,14 @@ function previewMatchCount(db, userId, normalized) { `, ) .all(userId); - return txRows.filter((tx) => { + return txRows.filter((tx: any) => { const txMerchant = normalizeMerchant(tx.payee || tx.description || tx.memo || ''); return txMerchant && merchantMatches(txMerchant, normalized); }).length; } // Find bills (other than this one) that already claim this merchant. -function findConflicts(db, userId, billId, normalized) { +function findConflicts(db: any, userId: any, billId: any, normalized: any) { return db .prepare( ` @@ -1336,7 +1335,7 @@ router.get('/:id/merchant-rules', (req: Req, res: Res) => { `, ) .all(req.user.id) - .map((tx) => { + .map((tx: any) => { const raw = tx.payee || tx.description || tx.memo || ''; const normalized = normalizeMerchant(raw); return { @@ -1347,7 +1346,7 @@ router.get('/:id/merchant-rules', (req: Req, res: Res) => { date: tx.posted_date || String(tx.transacted_at || '').slice(0, 10), }; }) - .filter((s) => s.normalized.length >= 2); + .filter((s: any) => s.normalized.length >= 2); res.json({ rules, suggestions }); }); @@ -1422,7 +1421,7 @@ router.get('/:id/merchant-rules/candidates', (req: Req, res: Res) => { const rules = db .prepare('SELECT merchant FROM bill_merchant_rules WHERE user_id = ? AND bill_id = ?') .all(req.user.id, billId) - .map((r) => r.merchant); + .map((r: any) => r.merchant); if (rules.length === 0) return res.json({ candidates: [] }); @@ -1455,14 +1454,14 @@ router.get('/:id/merchant-rules/candidates', (req: Req, res: Res) => { 'SELECT transaction_id FROM payments WHERE bill_id = ? AND transaction_id IS NOT NULL AND deleted_at IS NULL', ) .all(billId) - .map((r) => r.transaction_id), + .map((r: any) => r.transaction_id), ); const candidates = []; for (const tx of txRows) { const txMerchant = normalizeMerchant(tx.payee || tx.description || tx.memo || ''); if (!txMerchant) continue; - const matches = rules.some((r) => merchantMatches(txMerchant, r)); + const matches = rules.some((r: any) => merchantMatches(txMerchant, r)); if (!matches) continue; const paidDate = @@ -1523,7 +1522,7 @@ router.post('/:id/merchant-rules/import-historical', (req: Req, res: Res) => { `); let imported = 0; - const lateAttributions = []; + const lateAttributions: any[] = []; try { db.transaction(() => { @@ -1578,7 +1577,7 @@ router.post('/:id/merchant-rules/import-historical', (req: Req, res: Res) => { } } })(); - } catch (err) { + } catch (err: any) { log.error('[import-historical] Transaction failed:', err.message); throw new ApiError('DB_ERROR', 'Import failed', 500); } diff --git a/routes/snowball.cts b/routes/snowball.cts index 01132e1..650b60f 100644 --- a/routes/snowball.cts +++ b/routes/snowball.cts @@ -1,4 +1,3 @@ -// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services). import type { Req, Res, Next } from '../types/http'; const express = require('express'); const router = express.Router(); @@ -23,7 +22,7 @@ const DEBT_LIKE_CLAUSES = `( ) )`; -function isRamseyMode(userId) { +function isRamseyMode(userId: any) { const db = getDb(); const row = db .prepare( @@ -37,7 +36,7 @@ function isRamseyMode(userId) { return row ? row.value !== 'false' && row.value !== '0' : true; } -function getUserBoolSetting(userId, key, fallback = false) { +function getUserBoolSetting(userId: any, key: string, fallback = false) { const db = getDb(); const row = db .prepare( @@ -52,7 +51,7 @@ function getUserBoolSetting(userId, key, fallback = false) { return row.value === 'true' || row.value === '1'; } -function upsertUserSetting(db, userId, key, value) { +function upsertUserSetting(db: any, userId: any, key: string, value: any) { db.prepare( ` INSERT INTO user_settings (user_id, key, value, updated_at) @@ -64,7 +63,7 @@ function upsertUserSetting(db, userId, key, value) { ).run(userId, key, String(value)); } -function getDebtQuery(ramseyMode) { +function getDebtQuery(ramseyMode: any) { const orderBy = ramseyMode ? ` CASE WHEN b.current_balance IS NULL THEN 1 ELSE 0 END ASC, @@ -89,7 +88,7 @@ function getDebtQuery(ramseyMode) { `; } -function getDebtBills(userId, ramseyMode) { +function getDebtBills(userId: any, ramseyMode?: any) { const db = getDb(); const mode = ramseyMode !== undefined ? ramseyMode : isRamseyMode(userId); return db.prepare(getDebtQuery(mode)).all(userId); @@ -179,7 +178,7 @@ router.get('/projection', (req: Req, res: Res) => { // Money fields on `bills` are stored as integer cents; the snowball/APR math // and the API response are dollar-denominated, so convert before computing. - const billsForMath = bills.map((b) => ({ + const billsForMath = bills.map((b: any) => ({ ...b, current_balance: fromCents(b.current_balance), minimum_payment: fromCents(b.minimum_payment), @@ -194,17 +193,17 @@ router.get('/projection', (req: Req, res: Res) => { : fromCents(user?.snowball_extra_payment ?? 0); // Build a lookup of APR snapshots keyed by bill id (computed once from current balances) - const aprByBill = {}; + const aprByBill: Record = {}; for (const b of billsForMath) { const snap = debtAprSnapshot(b); if (snap) aprByBill[b.id] = snap; } // Enrich each debt result with its APR snapshot - function enrich(projection) { + function enrich(projection: any) { return { ...projection, - debts: projection.debts.map((d) => ({ + debts: projection.debts.map((d: any) => ({ ...d, apr_snapshot: aprByBill[d.id] ?? null, })), @@ -223,7 +222,7 @@ router.get('/projection', (req: Req, res: Res) => { }); // Build a summary comparing snowball to the minimum-only baseline -function buildComparison(snowball, minimum_only) { +function buildComparison(snowball: any, minimum_only: any) { const sbMonths = snowball.months_to_freedom; const moMonths = minimum_only.months_to_freedom; const sbInterest = snowball.total_interest_paid; @@ -259,7 +258,7 @@ router.patch('/order', (req: Req, res: Res) => { } // Validate every row before touching the DB — no silent skips - const parsed = []; + const parsed: any[] = []; for (let i = 0; i < items.length; i++) { const row = items[i]; const id = parseInt(row?.id, 10); @@ -292,7 +291,7 @@ router.patch('/order', (req: Req, res: Res) => { // ── Snowball Plan helpers ───────────────────────────────────────────────────── -function enrichPlanWithProgress(db, plan) { +function enrichPlanWithProgress(db: any, plan: any) { let snapshot; try { snapshot = JSON.parse(plan.plan_snapshot); @@ -302,8 +301,8 @@ function enrichPlanWithProgress(db, plan) { // Fetch every snapshot bill in one query (user-scoped), not one-per-debt. const snapDebts = snapshot?.debts ?? []; - const billIds = [...new Set(snapDebts.map((d) => d.bill_id).filter(Boolean))]; - const billsById = {}; + const billIds = [...new Set(snapDebts.map((d: any) => d.bill_id).filter(Boolean))]; + const billsById: Record = {}; if (billIds.length) { const placeholders = billIds.map(() => '?').join(','); for (const b of db @@ -315,7 +314,7 @@ function enrichPlanWithProgress(db, plan) { } } - const currentDebts = snapDebts.map((d) => { + const currentDebts = snapDebts.map((d: any) => { const bill = billsById[d.bill_id]; const currentBalance = bill && !bill.deleted_at ? fromCents(bill.current_balance) : null; const startingBalance = d.starting_balance ?? 0; @@ -360,7 +359,7 @@ router.post('/plans', (req: Req, res: Res) => { const ramseyMode = isRamseyMode(userId); const debts = getDebtBills(userId, ramseyMode); - const activeDebts = debts.filter((b) => (b.current_balance ?? 0) > 0); + const activeDebts = debts.filter((b: any) => (b.current_balance ?? 0) > 0); if (activeDebts.length === 0) { throw ValidationError( 'No debts with a balance found. Add a balance to at least one bill.', @@ -371,7 +370,7 @@ router.post('/plans', (req: Req, res: Res) => { // Money fields on `debts` are stored as integer cents; the snowball/APR // math and plan_snapshot are dollar-denominated, so convert before computing. - const debtsForMath = debts.map((b) => ({ + const debtsForMath = debts.map((b: any) => ({ ...b, current_balance: fromCents(b.current_balance), minimum_payment: fromCents(b.minimum_payment), @@ -393,8 +392,8 @@ router.post('/plans', (req: Req, res: Res) => { 100, ); - const debtSnaps = debtsForMath.map((b, i) => { - const proj = snowball.debts?.find((d) => d.id === b.id); + const debtSnaps = debtsForMath.map((b: any, i: number) => { + const proj = snowball.debts?.find((d: any) => d.id === b.id); return { bill_id: b.id, name: b.name, @@ -451,7 +450,7 @@ router.get('/plans', (req: Req, res: Res) => { `, ) .all(req.user.id); - res.json({ plans: plans.map((p) => enrichPlanWithProgress(db, p)) }); + res.json({ plans: plans.map((p: any) => enrichPlanWithProgress(db, p)) }); }); // GET /api/snowball/plans/active — return the active or paused plan (or null) @@ -495,7 +494,7 @@ router.patch('/plans/:id', (req: Req, res: Res) => { // Shared status-transition handler for pause / resume / complete / abandon. // `setSql` is a fixed constant per route (never user input). -function transitionPlan(req, res, { allowedFrom, setSql, past }) { +function transitionPlan(req: Req, res: Res, { allowedFrom, setSql, past }: any) { const db = getDb(); const id = parseInt(req.params.id, 10); if (!Number.isInteger(id) || id <= 0) { diff --git a/routes/summary.cts b/routes/summary.cts index 392f5de..e21887a 100644 --- a/routes/summary.cts +++ b/routes/summary.cts @@ -1,4 +1,3 @@ -// @ts-nocheck — route controller converted to .cts; handler req/res typed, full type-check deferred (thin glue over typed services). import type { Req, Res, Next } from '../types/http'; const express = require('express'); const { log } = require('../utils/logger.cts'); @@ -15,7 +14,7 @@ const DEFAULT_PENDING_DAYS = 3; // Build bank tracking summary when the user has enabled SimpleFIN bank tracking. // Returns null when disabled, the account isn't found, or bank sync isn't set up. -function buildBankTrackingSummary(db, userId, year, month) { +function buildBankTrackingSummary(db: any, userId: any, year: any, month: any) { try { const settings = getUserSettings(userId); if (settings.bank_tracking_enabled !== 'true') return null; @@ -92,8 +91,8 @@ function buildBankTrackingSummary(db, userId, year, month) { ) .all(year, month, start, end, userId); const unpaidTotalCents = unpaidCandidates - .filter((row) => resolveDueDate(row, year, month)) - .reduce((sum, row) => sum + (row.amount_cents || 0), 0); + .filter((row: any) => resolveDueDate(row, year, month)) + .reduce((sum: number, row: any) => sum + (row.amount_cents || 0), 0); const balanceDollars = money(account.balance / 100); const pendingDollars = fromCents(pendingRow.pending_total); @@ -116,13 +115,13 @@ function buildBankTrackingSummary(db, userId, year, month) { unpaid_this_month: unpaidDollars, remaining: money(effectiveDollars - unpaidDollars), }; - } catch (err) { + } catch (err: any) { log.error('[buildBankTrackingSummary] Error:', err.message); return null; } } -function parseYearMonth(source) { +function parseYearMonth(source: any) { const now = new Date(); const year = parseInt(source.year || now.getFullYear(), 10); const month = parseInt(source.month || now.getMonth() + 1, 10); @@ -137,12 +136,12 @@ function parseYearMonth(source) { return { year, month }; } -function money(value) { +function money(value: any) { const n = Number(value); return Number.isFinite(n) ? Math.round(n * 100) / 100 : 0; } -function getStartingAmounts(db, userId, year, month) { +function getStartingAmounts(db: any, userId: any, year: any, month: any) { const row = db .prepare( ` @@ -160,7 +159,7 @@ function getStartingAmounts(db, userId, year, month) { }; } -function calculatePaidDeductions(db, userId, year, month) { +function calculatePaidDeductions(db: any, userId: any, year: any, month: any) { const { start, end } = getCycleRange(year, month); // Paid from first bucket: bills with due_day 1-14 @@ -237,7 +236,7 @@ function calculatePaidDeductions(db, userId, year, month) { }; } -function buildStartingAmountsSummary(db, userId, year, month) { +function buildStartingAmountsSummary(db: any, userId: any, year: any, month: any) { const amounts = getStartingAmounts(db, userId, year, month); const paid = calculatePaidDeductions(db, userId, year, month); @@ -264,7 +263,7 @@ function buildStartingAmountsSummary(db, userId, year, month) { }; } -function getIncome(db, userId, year, month) { +function getIncome(db: any, userId: any, year: any, month: any) { const row = db .prepare( ` @@ -282,7 +281,7 @@ function getIncome(db, userId, year, month) { }; } -function buildSummary(db, userId, year, month) { +function buildSummary(db: any, userId: any, year: any, month: any) { const income = getIncome(db, userId, year, month); const { start, end } = getCycleRange(year, month); @@ -321,9 +320,9 @@ function buildSummary(db, userId, year, month) { // QA-B5-01: only bills that actually occur in this month (matches the Tracker's // resolveDueDate gating) — otherwise annual / off-month quarterly bills inflate // the monthly expense list and total, disagreeing with the Tracker. - .filter((row) => resolveDueDate(row, year, month)); + .filter((row: any) => resolveDueDate(row, year, month)); - const billIds = billRows.map((row) => row.bill_id); + const billIds = billRows.map((row: any) => row.bill_id); const paymentMap = new Map(); if (billIds.length > 0) { @@ -353,7 +352,7 @@ function buildSummary(db, userId, year, month) { } } - const expenses = billRows.map((row) => { + const expenses = billRows.map((row: any) => { const payment = paymentMap.get(row.bill_id) || { payment_count: 0, paid_amount: 0 }; const hasActual = row.actual_amount !== null && row.actual_amount !== undefined; const displayAmount = fromCents(hasActual ? row.actual_amount : row.expected_amount); @@ -378,13 +377,15 @@ function buildSummary(db, userId, year, month) { }; }); - const countedExpenses = expenses.filter((expense) => !expense.is_skipped); + const countedExpenses = expenses.filter((expense: any) => !expense.is_skipped); const incomeTotal = money(income.amount); const expenseTotal = money( - countedExpenses.reduce((sum, expense) => sum + expense.display_amount, 0), + countedExpenses.reduce((sum: number, expense: any) => sum + expense.display_amount, 0), ); - const paidTotal = money(countedExpenses.reduce((sum, expense) => sum + expense.paid_amount, 0)); - const paidExpenseCount = countedExpenses.filter((expense) => expense.is_paid).length; + const paidTotal = money( + countedExpenses.reduce((sum: number, expense: any) => sum + expense.paid_amount, 0), + ); + const paidExpenseCount = countedExpenses.filter((expense: any) => expense.is_paid).length; const starting_amounts = buildStartingAmountsSummary(db, userId, year, month); const bank_tracking = buildBankTrackingSummary(db, userId, year, month); // When bank tracking is on, drive the "plan base" from the effective bank balance