mirror of
https://github.com/Sea-Haven-Industries/payments-dashboard.git
synced 2026-09-30 06:33:11 +00:00
fix: null-guard cash-position tile, add error logging, drop UTC date skew and serial GetItems
- Null-guard current_ledger on the authoritative cash-position tile so a missing ledger renders 'Not available' instead of the false '/bin/bash.00' - Log GetCommand failures with console.error + logSafe instead of silent catch, matching the rest of the codebase - Derive dates from local midnight instead of toISOString UTC, so the current-day lookup doesn't miss from 8pm ET to midnight - Fetch all 14 balance keys in parallel with Promise.allSettled instead of 14 serial GetCommands; start previous-day walk at i=1 since today's previous-day snapshot can't exist until tomorrow - Use Item.as_of_date directly instead of a synthetic _asOfDate field - Remove _asOfDate from test fixtures; add test for the null-ledger guard
This commit is contained in:
parent
c7de44cc64
commit
6af1a6ab90
2 changed files with 65 additions and 31 deletions
|
|
@ -3,7 +3,7 @@ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
||||||
import { DynamoDBDocumentClient, GetCommand, ScanCommand } from "@aws-sdk/lib-dynamodb";
|
import { DynamoDBDocumentClient, GetCommand, ScanCommand } from "@aws-sdk/lib-dynamodb";
|
||||||
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
|
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
|
||||||
import { parseISOLocal, parseMDYLocal } from "./dates.js";
|
import { parseISOLocal, parseMDYLocal } from "./dates.js";
|
||||||
import { isCancelStatus } from "./boaRecon.js";
|
import { isCancelStatus, logSafe } from "./boaRecon.js";
|
||||||
|
|
||||||
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient());
|
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient());
|
||||||
const secrets = new SecretsManagerClient();
|
const secrets = new SecretsManagerClient();
|
||||||
|
|
@ -405,39 +405,62 @@ async function scanPayments() {
|
||||||
// --- Read the latest previous-day balance snapshot ---
|
// --- Read the latest previous-day balance snapshot ---
|
||||||
|
|
||||||
async function fetchCashPosition() {
|
async function fetchCashPosition() {
|
||||||
const today = new Date();
|
const now = new Date();
|
||||||
const todayStr = today.toISOString().split("T")[0];
|
// Local-midnight today so date arithmetic stays in local time: toISOString()
|
||||||
|
// gives UTC, which from 8pm ET is tomorrow's date (#80-review).
|
||||||
|
const localToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||||
|
|
||||||
// Walk backward from today looking for a previous-day balance snapshot.
|
const toDateStr = (d) =>
|
||||||
// Weekends and holidays may leave gaps; scan up to 14 days back.
|
d.getFullYear() + "-" +
|
||||||
let previousDay = null;
|
String(d.getMonth() + 1).padStart(2, "0") + "-" +
|
||||||
for (let i = 0; i < 14; i++) {
|
String(d.getDate()).padStart(2, "0");
|
||||||
const d = new Date(today);
|
|
||||||
|
const todayStr = toDateStr(localToday);
|
||||||
|
|
||||||
|
// Intraday (today, current-day) + up to 13 prior previous-day snapshots,
|
||||||
|
// all fetched in parallel. A previous-day snapshot dated today cannot exist
|
||||||
|
// until tomorrow, so the walk starts at i = 1 (#80-review).
|
||||||
|
const keys = [{ key: `boa_balance#${todayStr}#current-day` }];
|
||||||
|
for (let i = 1; i < 14; i++) {
|
||||||
|
const d = new Date(localToday);
|
||||||
d.setDate(d.getDate() - i);
|
d.setDate(d.getDate() - i);
|
||||||
const dateStr = d.toISOString().split("T")[0];
|
const ds = toDateStr(d);
|
||||||
const key = `boa_balance#${dateStr}#previous-day`;
|
keys.push({ key: `boa_balance#${ds}#previous-day` });
|
||||||
try {
|
|
||||||
const { Item } = await ddb.send(
|
|
||||||
new GetCommand({ TableName: TABLE_NAME, Key: { pk: key } })
|
|
||||||
);
|
|
||||||
if (Item) {
|
|
||||||
previousDay = { ...Item, _asOfDate: dateStr };
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// GetCommand failure is non-fatal; move to the prior day.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try today's intraday snapshot (current-day) for optional provisional display.
|
const results = await Promise.allSettled(
|
||||||
|
keys.map(({ key }) =>
|
||||||
|
ddb.send(new GetCommand({ TableName: TABLE_NAME, Key: { pk: key } }))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
let intraday = null;
|
let intraday = null;
|
||||||
try {
|
if (results[0].status === "fulfilled" && results[0].value.Item) {
|
||||||
const { Item } = await ddb.send(
|
intraday = results[0].value.Item;
|
||||||
new GetCommand({ TableName: TABLE_NAME, Key: { pk: `boa_balance#${todayStr}#current-day` } })
|
} else if (results[0].status === "rejected") {
|
||||||
|
console.error(
|
||||||
|
"Cash-position intraday GetItem failed:",
|
||||||
|
logSafe(results[0].reason?.name),
|
||||||
|
logSafe(results[0].reason?.message)
|
||||||
);
|
);
|
||||||
if (Item) intraday = { ...Item, _asOfDate: todayStr };
|
}
|
||||||
} catch {
|
|
||||||
// Non-fatal.
|
// Walk previous-day results (indices 1..) newest-first; pick the first hit.
|
||||||
|
let previousDay = null;
|
||||||
|
for (let i = 1; i < results.length; i++) {
|
||||||
|
const res = results[i];
|
||||||
|
if (res.status === "rejected") {
|
||||||
|
console.error(
|
||||||
|
"Cash-position previous-day GetItem failed:",
|
||||||
|
logSafe(res.reason?.name),
|
||||||
|
logSafe(res.reason?.message)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (res.value.Item) {
|
||||||
|
previousDay = res.value.Item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { previousDay, intraday };
|
return { previousDay, intraday };
|
||||||
|
|
@ -728,8 +751,8 @@ export function buildHomeView(payments, metadata, boaTransactions = [], cashPosi
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
type: "mrkdwn",
|
type: "mrkdwn",
|
||||||
text: balance
|
text: balance && balance.current_ledger != null
|
||||||
? `:moneybag: *Cash Position* — ${balance._asOfDate}\nLedger ${formatCurrency(balance.current_ledger)} · Available ${formatCurrency(balance.current_available)}`
|
? `:moneybag: *Cash Position* — ${balance.as_of_date}\nLedger ${formatCurrency(balance.current_ledger)} · Available ${formatCurrency(balance.current_available)}`
|
||||||
: `:moneybag: *Cash Position*\nNot available`,
|
: `:moneybag: *Cash Position*\nNot available`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,6 @@ const balanceItem = (overrides = {}) => ({
|
||||||
total_debits_count: 5,
|
total_debits_count: 5,
|
||||||
total_ach_debits: 20650.65,
|
total_ach_debits: 20650.65,
|
||||||
other: {},
|
other: {},
|
||||||
_asOfDate: "2026-07-22",
|
|
||||||
...overrides,
|
...overrides,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -173,6 +172,18 @@ test("home view: cash position shows 'Not available' when balance is missing", (
|
||||||
assert.ok(json.includes("Not available"));
|
assert.ok(json.includes("Not available"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("home view: cash position shows 'Not available' when previousDay exists but current_ledger is null", () => {
|
||||||
|
const view = buildHomeView([payment()], metadata, [], {
|
||||||
|
previousDay: balanceItem({ current_ledger: undefined, current_available: undefined }),
|
||||||
|
intraday: null,
|
||||||
|
});
|
||||||
|
const json = JSON.stringify(view);
|
||||||
|
// The cash-position tile falls back to "Not available" instead of "Ledger $0.00"
|
||||||
|
assert.ok(json.includes("Not available"));
|
||||||
|
// Confirm the tile does NOT contain the false "Ledger" line
|
||||||
|
assert.ok(!json.includes("Ledger "));
|
||||||
|
});
|
||||||
|
|
||||||
test("home view: intraday provisional tile renders when current-day exists", () => {
|
test("home view: intraday provisional tile renders when current-day exists", () => {
|
||||||
const view = buildHomeView([payment()], metadata, [], {
|
const view = buildHomeView([payment()], metadata, [], {
|
||||||
previousDay: balanceItem(),
|
previousDay: balanceItem(),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue