update money tracker
All checks were successful
Test and deploy staging / staging (push) Successful in 30s
All checks were successful
Test and deploy staging / staging (push) Successful in 30s
This commit is contained in:
@@ -7,7 +7,8 @@ import {
|
||||
moneyUtilitySettings,
|
||||
moneyUtilityReadings,
|
||||
moneyWishlist,
|
||||
moneyInsurances
|
||||
moneyInsurances,
|
||||
moneyProjectionSettings
|
||||
} from '$lib/server/db/schema';
|
||||
import { requireAdmin } from '$lib/server/authorization';
|
||||
import {
|
||||
@@ -38,7 +39,8 @@ export const load: PageServerLoad = async (event) => {
|
||||
utilitySettings,
|
||||
utilityReadings,
|
||||
wishlist,
|
||||
insurances
|
||||
insurances,
|
||||
projectionSettings
|
||||
] = await Promise.all([
|
||||
db
|
||||
.select()
|
||||
@@ -75,7 +77,8 @@ export const load: PageServerLoad = async (event) => {
|
||||
.select()
|
||||
.from(moneyInsurances)
|
||||
.where(eq(moneyInsurances.userId, userId))
|
||||
.orderBy(asc(moneyInsurances.name))
|
||||
.orderBy(asc(moneyInsurances.name)),
|
||||
db.select().from(moneyProjectionSettings).where(eq(moneyProjectionSettings.userId, userId))
|
||||
]);
|
||||
return {
|
||||
accounts,
|
||||
@@ -85,7 +88,9 @@ export const load: PageServerLoad = async (event) => {
|
||||
utilitySettings,
|
||||
utilityReadings,
|
||||
wishlist,
|
||||
insurances
|
||||
insurances,
|
||||
projectionSettings: projectionSettings[0] ?? null,
|
||||
today: new Date().toISOString().slice(0, 10)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -111,18 +116,20 @@ export const actions: Actions = {
|
||||
const form = await event.request.formData();
|
||||
const name = textField(form, 'name', 80);
|
||||
const openingBalanceCents = cents(form.get('openingBalance'));
|
||||
if (!name || openingBalanceCents == null)
|
||||
const kind = String(form.get('kind') ?? 'spendable');
|
||||
if (!name || openingBalanceCents == null || !['spendable', 'investment'].includes(kind))
|
||||
return invalid('Enter an account name and a valid opening balance.');
|
||||
const id = recordId(form);
|
||||
try {
|
||||
if (id) {
|
||||
const [row] = await db
|
||||
.update(moneyAccounts)
|
||||
.set({ name, openingBalanceCents })
|
||||
.set({ name, openingBalanceCents, kind })
|
||||
.where(and(eq(moneyAccounts.id, id), eq(moneyAccounts.userId, user.id)))
|
||||
.returning({ id: moneyAccounts.id });
|
||||
if (!row) error(404, 'Account not found');
|
||||
} else await db.insert(moneyAccounts).values({ userId: user.id, name, openingBalanceCents });
|
||||
} else
|
||||
await db.insert(moneyAccounts).values({ userId: user.id, name, openingBalanceCents, kind });
|
||||
} catch (cause) {
|
||||
if (typeof cause === 'object' && cause && 'code' in cause && cause.code === '23505')
|
||||
return invalid('An account with that name already exists.');
|
||||
@@ -182,10 +189,21 @@ export const actions: Actions = {
|
||||
const name = textField(form, 'name', 200);
|
||||
const amountCents = cents(form.get('amount'));
|
||||
const period = cadence(form.get('cadence'));
|
||||
if (!name || amountCents == null || !period)
|
||||
const startsOnRaw = String(form.get('startsOn') ?? '').trim();
|
||||
const endsOnRaw = String(form.get('endsOn') ?? '').trim();
|
||||
const startsOn = startsOnRaw ? validDate(startsOnRaw) : null;
|
||||
const endsOn = endsOnRaw ? validDate(endsOnRaw) : null;
|
||||
if (
|
||||
!name ||
|
||||
amountCents == null ||
|
||||
!period ||
|
||||
(startsOnRaw && !startsOn) ||
|
||||
(endsOnRaw && !endsOn) ||
|
||||
(startsOn && endsOn && endsOn < startsOn)
|
||||
)
|
||||
return invalid('Enter a name, valid signed amount and frequency.');
|
||||
const accountId = await ownedAccount(user.id, form.get('accountId'), true);
|
||||
const values = { name, amountCents, cadence: period, accountId };
|
||||
const values = { name, amountCents, cadence: period, accountId, startsOn, endsOn };
|
||||
const id = recordId(form);
|
||||
if (id) {
|
||||
const [row] = await db
|
||||
@@ -236,6 +254,59 @@ export const actions: Actions = {
|
||||
.where(and(eq(moneyForecasts.id, id), eq(moneyForecasts.userId, user.id)));
|
||||
return { saved: true };
|
||||
},
|
||||
saveProjectionSettings: async (event) => {
|
||||
const user = requireAdmin(event);
|
||||
const form = await event.request.formData();
|
||||
const investmentAccountId = await ownedAccount(user.id, form.get('investmentAccountId'), true);
|
||||
if (investmentAccountId !== null) {
|
||||
const [account] = await db
|
||||
.select({ kind: moneyAccounts.kind })
|
||||
.from(moneyAccounts)
|
||||
.where(and(eq(moneyAccounts.id, investmentAccountId), eq(moneyAccounts.userId, user.id)));
|
||||
if (account?.kind !== 'investment') return invalid('Choose an investment account.');
|
||||
}
|
||||
const reserveCents = cents(form.get('reserve'));
|
||||
const fixedMonthlyCents = cents(form.get('fixedMonthly'));
|
||||
const annualReturnPercent = String(form.get('annualReturnPercent') ?? '')
|
||||
.trim()
|
||||
.replace(',', '.');
|
||||
const contributionMode = String(form.get('contributionMode') ?? 'excess');
|
||||
const endDateRaw = String(form.get('endDate') ?? '').trim();
|
||||
const endDate = endDateRaw ? validDate(endDateRaw) : null;
|
||||
const maxEndDate = new Date();
|
||||
maxEndDate.setUTCFullYear(maxEndDate.getUTCFullYear() + 20);
|
||||
if (
|
||||
reserveCents == null ||
|
||||
reserveCents < 0 ||
|
||||
fixedMonthlyCents == null ||
|
||||
fixedMonthlyCents < 0 ||
|
||||
!/^(?:\d{1,2}|100)(?:\.\d{1,2})?$/.test(annualReturnPercent) ||
|
||||
Number(annualReturnPercent) > 100 ||
|
||||
!['excess', 'fixed'].includes(contributionMode) ||
|
||||
(endDateRaw && !endDate) ||
|
||||
(endDate &&
|
||||
(endDate < new Date().toISOString().slice(0, 10) ||
|
||||
endDate > maxEndDate.toISOString().slice(0, 10)))
|
||||
)
|
||||
return invalid('Enter valid projection settings.');
|
||||
const values = {
|
||||
investmentAccountId,
|
||||
reserveCents,
|
||||
fixedMonthlyCents,
|
||||
annualReturnPercent,
|
||||
contributionMode,
|
||||
includeRecurring: form.get('includeRecurring') === 'on' ? 1 : 0,
|
||||
endDate
|
||||
};
|
||||
await db
|
||||
.insert(moneyProjectionSettings)
|
||||
.values({ userId: user.id, ...values })
|
||||
.onConflictDoUpdate({
|
||||
target: moneyProjectionSettings.userId,
|
||||
set: values
|
||||
});
|
||||
return { saved: true };
|
||||
},
|
||||
saveUtilitySettings: async (event) => {
|
||||
const user = requireAdmin(event);
|
||||
const form = await event.request.formData();
|
||||
@@ -300,14 +371,18 @@ export const actions: Actions = {
|
||||
const name = textField(form, 'name', 200);
|
||||
const estimatedCents = cents(form.get('estimated'), true);
|
||||
const notes = optionalText(form, 'notes');
|
||||
const priority = Number(form.get('priority') ?? 3);
|
||||
if (
|
||||
!name ||
|
||||
estimatedCents === undefined ||
|
||||
notes === undefined ||
|
||||
(estimatedCents !== null && estimatedCents < 0)
|
||||
(estimatedCents !== null && estimatedCents < 0) ||
|
||||
!Number.isInteger(priority) ||
|
||||
priority < 1 ||
|
||||
priority > 5
|
||||
)
|
||||
return invalid('Enter a name and a valid estimated cost.');
|
||||
const values = { name, estimatedCents, notes };
|
||||
const values = { name, estimatedCents, notes, priority };
|
||||
const id = recordId(form);
|
||||
if (id) {
|
||||
const [row] = await db
|
||||
|
||||
Reference in New Issue
Block a user