Docs: backfill JSDoc, utility docs, and CLAUDE.md API/schema sections
- Add JSDoc to paychecks.js helpers: buildVirtualPaychecks, generatePaychecks, fetchPaychecksForMonth - Add JSDoc to financing.js helpers: remainingPeriods, calcPaymentAmount, enrichPlan - Add JSDoc to validateBillFields (bills.js) and getAllConfig (config.js) - Add JSDoc to ThemeProvider and useTheme in ThemeContext.jsx - Add Database Schema reference table to CLAUDE.md - Add complete API Endpoints reference section to CLAUDE.md covering all routes Nightshift-Task: docs-backfill Nightshift-Ref: https://github.com/marcus/nightshift
This commit is contained in:
@@ -2,6 +2,20 @@ const express = require('express');
|
||||
const router = express.Router();
|
||||
const { pool } = require('../db');
|
||||
|
||||
/**
|
||||
* Validate the request body for bill create/update operations.
|
||||
*
|
||||
* Checks that all required fields are present and within acceptable ranges.
|
||||
* Amount is optional when `variable_amount` is true (defaults to 0 on save).
|
||||
*
|
||||
* @param {object} body - Request body.
|
||||
* @param {string} body.name - Bill name (non-empty).
|
||||
* @param {number|string} [body.amount] - Bill amount; required when variable_amount is falsy.
|
||||
* @param {number|string} body.due_day - Day of month (1–31).
|
||||
* @param {number|string} body.assigned_paycheck - Which paycheck: 1 or 2.
|
||||
* @param {boolean} [body.variable_amount] - Whether the bill amount varies each month.
|
||||
* @returns {string|null} Validation error message, or null when valid.
|
||||
*/
|
||||
function validateBillFields(body) {
|
||||
const { name, amount, due_day, assigned_paycheck, variable_amount } = body;
|
||||
if (!name || name.toString().trim() === '') {
|
||||
|
||||
@@ -16,6 +16,22 @@ const DEFAULTS = {
|
||||
paycheck2_day: 15,
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch all application config values from the database.
|
||||
*
|
||||
* Reads all known `CONFIG_KEYS` from the `config` table and coerces values
|
||||
* to numbers. Keys missing from the DB fall back to hard-coded `DEFAULTS`
|
||||
* (paycheck1_day = 1, paycheck2_day = 15); keys with no default are null.
|
||||
*
|
||||
* @returns {Promise<{
|
||||
* paycheck1_day: number|null,
|
||||
* paycheck2_day: number|null,
|
||||
* paycheck1_gross: number|null,
|
||||
* paycheck1_net: number|null,
|
||||
* paycheck2_gross: number|null,
|
||||
* paycheck2_net: number|null
|
||||
* }>}
|
||||
*/
|
||||
async function getAllConfig() {
|
||||
const result = await pool.query(
|
||||
'SELECT key, value FROM config WHERE key = ANY($1)',
|
||||
|
||||
@@ -4,9 +4,20 @@ const { pool } = require('../db');
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
// Count how many payment periods remain for a plan starting from (year, month),
|
||||
// including that month. Each month contributes 1 or 2 periods depending on
|
||||
// whether the plan is split across both paychecks (assigned_paycheck = null).
|
||||
/**
|
||||
* Count the number of payment periods remaining for a plan, starting from
|
||||
* (and including) the given year/month.
|
||||
*
|
||||
* Each calendar month contributes 1 period for single-paycheck plans
|
||||
* (`assigned_paycheck` = 1 or 2) or 2 periods for split plans
|
||||
* (`assigned_paycheck` = null). Always returns at least 1 to prevent
|
||||
* division-by-zero in {@link calcPaymentAmount}.
|
||||
*
|
||||
* @param {{ due_date: string, assigned_paycheck: number|null }} plan
|
||||
* @param {number} year - Current year.
|
||||
* @param {number} month - Current month (1–12).
|
||||
* @returns {number} Number of remaining payment periods (≥ 1).
|
||||
*/
|
||||
function remainingPeriods(plan, year, month) {
|
||||
const due = new Date(plan.due_date);
|
||||
const dueYear = due.getFullYear();
|
||||
@@ -19,7 +30,18 @@ function remainingPeriods(plan, year, month) {
|
||||
return monthsLeft * perMonth;
|
||||
}
|
||||
|
||||
// Calculate the payment amount for one period.
|
||||
/**
|
||||
* Calculate the payment amount due for a single period.
|
||||
*
|
||||
* Formula: `(total_amount - paid_so_far) / remainingPeriods(plan, year, month)`.
|
||||
* Returns 0 when the plan is already fully paid.
|
||||
*
|
||||
* @param {import('pg').PoolClient} client - Active DB client (read-only query).
|
||||
* @param {{ id: number, total_amount: number|string, due_date: string, assigned_paycheck: number|null }} plan
|
||||
* @param {number} year - Current year for period calculation.
|
||||
* @param {number} month - Current month (1–12) for period calculation.
|
||||
* @returns {Promise<number>} Payment amount rounded to 2 decimal places, or 0.
|
||||
*/
|
||||
async function calcPaymentAmount(client, plan, year, month) {
|
||||
const { rows } = await client.query(
|
||||
`SELECT COALESCE(SUM(fp.amount), 0) AS paid_total
|
||||
@@ -35,7 +57,22 @@ async function calcPaymentAmount(client, plan, year, month) {
|
||||
return parseFloat((remaining / periods).toFixed(2));
|
||||
}
|
||||
|
||||
// Enrich a plan row with computed progress fields.
|
||||
/**
|
||||
* Enrich a raw `financing_plans` row with computed progress fields.
|
||||
*
|
||||
* Aggregates payment records to derive `paid_total`, `remaining`,
|
||||
* `paid_count`, `total_count`, and `overdue`. Used by every route that
|
||||
* returns a plan to the client.
|
||||
*
|
||||
* @param {import('pg').Pool} pool - DB pool (runs a single SELECT).
|
||||
* @param {object} plan - Raw row from the `financing_plans` table.
|
||||
* @returns {Promise<object>} Plan object extended with:
|
||||
* - `paid_total` {number} — sum of paid payment amounts
|
||||
* - `remaining` {number} — total_amount minus paid_total (≥ 0)
|
||||
* - `paid_count` {number} — number of paid financing_payments rows
|
||||
* - `total_count` {number} — total financing_payments rows for the plan
|
||||
* - `overdue` {boolean} — true when active, remaining > 0, and due_date is in the past
|
||||
*/
|
||||
async function enrichPlan(pool, plan) {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT
|
||||
|
||||
@@ -43,9 +43,29 @@ function pad2(n) {
|
||||
return String(n).padStart(2, '0');
|
||||
}
|
||||
|
||||
// Build virtual (unsaved) paycheck data from config + active bills.
|
||||
// Returns the same shape as fetchPaychecksForMonth but with id: null
|
||||
// and paycheck_bill_id: null — nothing is written to the DB.
|
||||
/**
|
||||
* Build virtual (unsaved) paycheck data from config + active bills.
|
||||
*
|
||||
* Returns the same shape as {@link fetchPaychecksForMonth} but with
|
||||
* `id: null` and `paycheck_bill_id: null` — nothing is written to the DB.
|
||||
* Financing payment previews are included with `financing_payment_id: null`.
|
||||
* Plans whose `start_date` is after the requested period are excluded.
|
||||
*
|
||||
* @param {number} year - Full four-digit year (e.g. 2025).
|
||||
* @param {number} month - Month number 1–12.
|
||||
* @returns {Promise<Array<{
|
||||
* id: null,
|
||||
* period_year: number,
|
||||
* period_month: number,
|
||||
* paycheck_number: 1|2,
|
||||
* pay_date: string,
|
||||
* gross: number,
|
||||
* net: number,
|
||||
* bills: Array<object>,
|
||||
* one_time_expenses: [],
|
||||
* financing: Array<object>
|
||||
* }>>}
|
||||
*/
|
||||
async function buildVirtualPaychecks(year, month) {
|
||||
const config = await getConfig();
|
||||
const paychecks = [];
|
||||
@@ -137,8 +157,19 @@ async function buildVirtualPaychecks(year, month) {
|
||||
return paychecks;
|
||||
}
|
||||
|
||||
// Generate (upsert) paycheck records for the given year/month.
|
||||
// Returns the two paycheck IDs.
|
||||
/**
|
||||
* Generate (upsert) paycheck records for the given year/month.
|
||||
*
|
||||
* Inserts or updates both `paychecks` rows, syncs `paycheck_bills` for all
|
||||
* active bills, and inserts `financing_payments` for active plans that have
|
||||
* started by this period. All writes run inside a single transaction.
|
||||
* Split financing plans (assigned_paycheck = null) get half the per-period
|
||||
* amount on each paycheck.
|
||||
*
|
||||
* @param {number} year - Full four-digit year.
|
||||
* @param {number} month - Month number 1–12.
|
||||
* @returns {Promise<number[]>} Two-element array of paycheck IDs `[id1, id2]`.
|
||||
*/
|
||||
async function generatePaychecks(year, month) {
|
||||
const config = await getConfig();
|
||||
|
||||
@@ -221,7 +252,25 @@ async function generatePaychecks(year, month) {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch both paycheck records for a month with full bill and one_time_expense data.
|
||||
/**
|
||||
* Fetch both persisted paycheck records for a month with full bill,
|
||||
* one-time-expense, and financing-payment data joined in.
|
||||
*
|
||||
* @param {number} year - Full four-digit year.
|
||||
* @param {number} month - Month number 1–12.
|
||||
* @returns {Promise<Array<{
|
||||
* id: number,
|
||||
* period_year: number,
|
||||
* period_month: number,
|
||||
* paycheck_number: 1|2,
|
||||
* pay_date: string,
|
||||
* gross: number,
|
||||
* net: number,
|
||||
* bills: Array<object>,
|
||||
* one_time_expenses: Array<object>,
|
||||
* financing: Array<object>
|
||||
* }>>} Empty array when no DB records exist for the given month.
|
||||
*/
|
||||
async function fetchPaychecksForMonth(year, month) {
|
||||
const pcResult = await pool.query(
|
||||
`SELECT id, period_year, period_month, paycheck_number, pay_date, gross, net
|
||||
|
||||
Reference in New Issue
Block a user