129 lines
4.1 KiB
JavaScript
129 lines
4.1 KiB
JavaScript
'use strict';
|
|
|
|
const { toCents, fromCents } = require('../utils/money');
|
|
|
|
function isPositiveIntegerString(value) {
|
|
return /^\d+$/.test(String(value).trim());
|
|
}
|
|
|
|
function validateIsoDate(value, field = 'paid_date') {
|
|
if (typeof value !== 'string') {
|
|
return { error: `${field} must be a valid date in YYYY-MM-DD format` };
|
|
}
|
|
|
|
const trimmed = value.trim();
|
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(trimmed);
|
|
if (!match) {
|
|
return { error: `${field} must be a valid date in YYYY-MM-DD format` };
|
|
}
|
|
|
|
const year = Number(match[1]);
|
|
const month = Number(match[2]);
|
|
const day = Number(match[3]);
|
|
const date = new Date(Date.UTC(year, month - 1, day));
|
|
|
|
if (
|
|
date.getUTCFullYear() !== year ||
|
|
date.getUTCMonth() !== month - 1 ||
|
|
date.getUTCDate() !== day
|
|
) {
|
|
return { error: `${field} must be a real calendar date in YYYY-MM-DD format` };
|
|
}
|
|
|
|
return { value: trimmed };
|
|
}
|
|
|
|
/**
|
|
* Validates a positive dollar amount and converts it to integer cents
|
|
* (the unit `payments.amount` and related money columns are stored in).
|
|
*/
|
|
function validatePositiveAmount(value, field = 'amount') {
|
|
const cents = toCents(value);
|
|
if (!Number.isInteger(cents) || cents <= 0) {
|
|
return { error: `${field} must be a positive number` };
|
|
}
|
|
return { value: cents };
|
|
}
|
|
|
|
/**
|
|
* Converts a payment row's cent columns (amount, balance_delta, interest_delta)
|
|
* to dollars for API responses.
|
|
*/
|
|
function serializePayment(payment) {
|
|
if (!payment) return payment;
|
|
const out = { ...payment };
|
|
if (out.amount != null) out.amount = fromCents(out.amount);
|
|
if (out.balance_delta != null) out.balance_delta = fromCents(out.balance_delta);
|
|
if (out.interest_delta != null) out.interest_delta = fromCents(out.interest_delta);
|
|
return out;
|
|
}
|
|
|
|
const PAYMENT_SOURCES = ['manual', 'file_import', 'provider_sync'];
|
|
|
|
function validatePaymentSource(value, field = 'payment_source') {
|
|
if (typeof value !== 'string') {
|
|
return { error: `${field} must be one of: ${PAYMENT_SOURCES.join(', ')}` };
|
|
}
|
|
|
|
const source = value.trim();
|
|
if (!PAYMENT_SOURCES.includes(source)) {
|
|
return { error: `${field} must be one of: ${PAYMENT_SOURCES.join(', ')}` };
|
|
}
|
|
return { value: source };
|
|
}
|
|
|
|
function validatePaymentInput(data, options = {}) {
|
|
const {
|
|
requireBillId = true,
|
|
requireAmount = true,
|
|
requirePaidDate = true,
|
|
fieldPrefix = '',
|
|
} = options;
|
|
const normalized = {};
|
|
|
|
if (requireBillId || data.bill_id !== undefined) {
|
|
if (data.bill_id === undefined || data.bill_id === null || data.bill_id === '') {
|
|
return { error: 'bill_id is required', field: `${fieldPrefix}bill_id` };
|
|
}
|
|
if (!isPositiveIntegerString(data.bill_id)) {
|
|
return { error: 'bill_id must be a positive integer', field: `${fieldPrefix}bill_id` };
|
|
}
|
|
normalized.bill_id = Number(String(data.bill_id).trim());
|
|
}
|
|
|
|
if (requireAmount || data.amount !== undefined) {
|
|
if (data.amount === undefined || data.amount === null || data.amount === '') {
|
|
return { error: 'amount is required', field: `${fieldPrefix}amount` };
|
|
}
|
|
const amount = validatePositiveAmount(data.amount, `${fieldPrefix}amount`);
|
|
if (amount.error) return { error: amount.error, field: `${fieldPrefix}amount` };
|
|
normalized.amount = amount.value;
|
|
}
|
|
|
|
if (requirePaidDate || data.paid_date !== undefined) {
|
|
if (data.paid_date === undefined || data.paid_date === null || data.paid_date === '') {
|
|
return { error: 'paid_date is required', field: `${fieldPrefix}paid_date` };
|
|
}
|
|
const paidDate = validateIsoDate(data.paid_date, `${fieldPrefix}paid_date`);
|
|
if (paidDate.error) return { error: paidDate.error, field: `${fieldPrefix}paid_date` };
|
|
normalized.paid_date = paidDate.value;
|
|
}
|
|
|
|
if (data.payment_source !== undefined) {
|
|
const source = validatePaymentSource(data.payment_source, `${fieldPrefix}payment_source`);
|
|
if (source.error) return { error: source.error, field: `${fieldPrefix}payment_source` };
|
|
normalized.payment_source = source.value;
|
|
}
|
|
|
|
return { normalized };
|
|
}
|
|
|
|
module.exports = {
|
|
PAYMENT_SOURCES,
|
|
serializePayment,
|
|
validateIsoDate,
|
|
validatePaymentInput,
|
|
validatePaymentSource,
|
|
validatePositiveAmount,
|
|
};
|