testing 101

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-06-30 19:31:45 +08:00
parent 89acdfc239
commit 1372f4e975
62 changed files with 6696 additions and 281 deletions
+2 -2
View File
@@ -3,8 +3,8 @@
/**
* Single archive — soft delete one record
*/
async function archiveOne(Model, options, deletedBy, transaction) {
const record = await Model.findOne(options);
async function archiveOne(Model, where, deletedBy, transaction) {
const record = await Model.findOne({ where, transaction });
if (!record) return null;
await record.update({ deletedBy: deletedBy ?? null }, { transaction });
await record.destroy({ transaction });
+171
View File
@@ -0,0 +1,171 @@
/***********************************************************************************************************************************************************************
* File Name: currency.util.js
* Type of Program: Utility
* Description: Currency formatting and resolution helpers for backend use (emails, receipts, notifications).
*
* All format functions accept an optional options object: { locale }
* locale — BCP 47 tag, defaults to 'en-US'
*
* USD is the platform's base/canonical currency. Plans may carry localized price
* overrides (plan_prices table). resolvePrice() applies the COALESCE logic:
* localized override wins → falls back to plan's base price + currency.
***********************************************************************************************************************************************************************/
'use strict';
// ─── Supported currencies ─────────────────────────────────────────────────────
const SUPPORTED_CURRENCIES = [
{ code: 'USD', name: 'US Dollar', symbol: '$' },
{ code: 'EUR', name: 'Euro', symbol: '€' },
{ code: 'GBP', name: 'British Pound', symbol: '£' },
{ code: 'CNY', name: 'Chinese Yuan', symbol: '¥' },
{ code: 'JPY', name: 'Japanese Yen', symbol: '¥' },
{ code: 'PHP', name: 'Philippine Peso', symbol: '₱' },
{ code: 'KRW', name: 'South Korean Won', symbol: '₩' },
{ code: 'AUD', name: 'Australian Dollar', symbol: 'A$' },
{ code: 'CAD', name: 'Canadian Dollar', symbol: 'C$' },
{ code: 'SGD', name: 'Singapore Dollar', symbol: 'S$' },
{ code: 'HKD', name: 'Hong Kong Dollar', symbol: 'HK$'},
{ code: 'INR', name: 'Indian Rupee', symbol: '₹' },
{ code: 'MYR', name: 'Malaysian Ringgit', symbol: 'RM' },
{ code: 'THB', name: 'Thai Baht', symbol: '฿' },
{ code: 'IDR', name: 'Indonesian Rupiah', symbol: 'Rp' },
{ code: 'TWD', name: 'Taiwan Dollar', symbol: 'NT$'},
{ code: 'VND', name: 'Vietnamese Dong', symbol: '₫' },
];
const SUPPORTED_CURRENCY_CODES = new Set(SUPPORTED_CURRENCIES.map((c) => c.code));
function isSupported(code) {
return SUPPORTED_CURRENCY_CODES.has(code?.toUpperCase());
}
// ─── Formatting ───────────────────────────────────────────────────────────────
/** "¥1,299.00" / "$9.99" */
function fmtCurrency(amount, currency = 'USD', { locale = 'en-US' } = {}) {
if (amount === null || amount === undefined) return '—';
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency ?? 'USD',
minimumFractionDigits: 2,
}).format(Number(amount));
}
// ─── Price resolution ─────────────────────────────────────────────────────────
/**
* Returns the effective { price, currency } for a plan given a user's preferred currency.
* plan.prices must be eager-loaded (as: 'prices') for the override to be considered.
* Falls back to plan.price + plan.currency when no override exists.
*/
function resolvePrice(plan, preferredCurrency) {
if (!preferredCurrency || preferredCurrency === plan.currency)
return { price: Number(plan.price), currency: plan.currency };
const override = (plan.prices ?? []).find((p) => p.currency === preferredCurrency);
if (override) return { price: Number(override.price), currency: override.currency };
return { price: Number(plan.price), currency: plan.currency };
}
// ─── Exchange rate fetching ───────────────────────────────────────────────────
// Uses frankfurter.app (ECB-backed, no API key, free).
// In-process cache with 1-hour TTL avoids hammering the API on every save.
const _rateCache = new Map();
async function fetchExchangeRate(from, to) {
if (from === to) return 1;
const key = `${from}:${to}`;
const now = Date.now();
const cached = _rateCache.get(key);
if (cached && cached.expiresAt > now) return cached.rate;
try {
const res = await fetch(
`https://api.frankfurter.app/latest?from=${from}&to=${to}`,
{ signal: AbortSignal.timeout(4000) },
);
if (!res.ok) return null;
const json = await res.json();
const rate = json?.rates?.[to];
if (!rate) return null;
_rateCache.set(key, { rate, expiresAt: now + 60 * 60 * 1000 }); // 1 h TTL
return rate;
} catch {
return null;
}
}
// ─── Localized price validation ───────────────────────────────────────────────
// Three zones relative to the market-rate conversion of the base price:
//
// pass → 85 % – 150 % of expected (green, saves normally)
// warn → 70 % – 85 % or 150 % – 300 % (saves with caution message)
// block → < 70 % or > 300 % (rejected — too far from market rate)
//
// If the exchange-rate API is unavailable the check is skipped (returns 'pass').
const PRICE_ZONES = {
LOWER_HARD: 0.70,
LOWER_WARN: 0.85,
UPPER_WARN: 1.50,
UPPER_HARD: 3.00,
};
async function validateLocalizedPrice(basePrice, baseCurrency, localizedPrice, targetCurrency) {
const rate = await fetchExchangeRate(baseCurrency, targetCurrency);
if (!rate) return { zone: 'pass', skipped: true };
const expected = Number(basePrice) * rate;
const entered = Number(localizedPrice);
const { LOWER_HARD, LOWER_WARN, UPPER_WARN, UPPER_HARD } = PRICE_ZONES;
const hardMin = expected * LOWER_HARD;
const hardMax = expected * UPPER_HARD;
const warnMin = expected * LOWER_WARN;
const warnMax = expected * UPPER_WARN;
const fmt = (n) => n.toFixed(2);
const rateStr = `1 ${baseCurrency} = ${rate} ${targetCurrency}`;
if (entered < hardMin || entered > hardMax) {
return {
zone: 'block',
expected: fmt(expected),
hardMin: fmt(hardMin), hardMax: fmt(hardMax),
warnMin: fmt(warnMin), warnMax: fmt(warnMax),
message: `${fmt(entered)} ${targetCurrency} is too far from the current market rate (${rateStr}). ` +
`Acceptable range: ${fmt(hardMin)} – ${fmt(hardMax)} ${targetCurrency}.`,
};
}
if (entered < warnMin || entered > warnMax) {
return {
zone: 'warn',
expected: fmt(expected),
hardMin: fmt(hardMin), hardMax: fmt(hardMax),
warnMin: fmt(warnMin), warnMax: fmt(warnMax),
message: `${fmt(entered)} ${targetCurrency} is outside the suggested range (${rateStr}). ` +
`Suggested: ${fmt(warnMin)} – ${fmt(warnMax)} ${targetCurrency}. Saved with caution.`,
};
}
return {
zone: 'pass',
expected: fmt(expected),
hardMin: fmt(hardMin), hardMax: fmt(hardMax),
warnMin: fmt(warnMin), warnMax: fmt(warnMax),
};
}
module.exports = {
SUPPORTED_CURRENCIES,
isSupported,
fmtCurrency,
resolvePrice,
fetchExchangeRate,
validateLocalizedPrice,
PRICE_ZONES,
};