feat(admin): Task 34 — donations, expenses, and financial reports
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import type { PageServerLoad, Actions } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { donations, pets } from '$lib/server/schema';
|
||||
import { error, fail, redirect } from '@sveltejs/kit';
|
||||
import { logAudit } from '$lib/server/audit';
|
||||
import { hasPermission } from '$lib/roles';
|
||||
import type { Role } from '$lib/roles';
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
if (!hasPermission(locals.user!.role as Role, 'donations')) error(403, 'Access denied');
|
||||
const petList = await db.select({ id: pets.id, name: pets.name }).from(pets).orderBy(pets.name);
|
||||
return { pets: petList };
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async ({ request, locals }) => {
|
||||
if (!hasPermission(locals.user!.role as Role, 'donations')) error(403, 'Access denied');
|
||||
const fd = await request.formData();
|
||||
|
||||
const source = fd.get('source')?.toString() ?? '';
|
||||
const amountStr = fd.get('amount')?.toString().trim() ?? '';
|
||||
const donorName = fd.get('donorName')?.toString().trim() || null;
|
||||
const donorEmail = fd.get('donorEmail')?.toString().trim() || null;
|
||||
const petId = fd.get('petId')?.toString() ? parseInt(fd.get('petId') as string, 10) : null;
|
||||
const isRecurring = fd.get('isRecurring') === 'on';
|
||||
const notes = fd.get('notes')?.toString().trim() || null;
|
||||
const purpose = fd.get('purpose')?.toString().trim() || null;
|
||||
|
||||
const validSources = ['stripe', 'paypal', 'venmo', 'givebutter', 'cash', 'check', 'zelle', 'google_pay', 'square', 'other'];
|
||||
if (!validSources.includes(source)) {
|
||||
return fail(400, { error: 'Please select a valid source', values: Object.fromEntries(fd) });
|
||||
}
|
||||
|
||||
const parsedAmount = parseFloat(amountStr);
|
||||
if (!amountStr || isNaN(parsedAmount) || parsedAmount <= 0) {
|
||||
return fail(400, { error: 'Amount must be a positive number', values: Object.fromEntries(fd) });
|
||||
}
|
||||
|
||||
const amountCents = Math.round(parsedAmount * 100);
|
||||
|
||||
const [result] = await db.insert(donations).values({
|
||||
source: source as typeof donations.source.enumValues[number],
|
||||
amount: amountCents,
|
||||
donorName,
|
||||
donorEmail,
|
||||
petId,
|
||||
isRecurring,
|
||||
notes,
|
||||
purpose
|
||||
});
|
||||
|
||||
await logAudit({
|
||||
userId: locals.user!.id,
|
||||
action: 'create_donation',
|
||||
entity: 'donation',
|
||||
entityId: result.insertId,
|
||||
diff: { source, amount: parsedAmount, donorName }
|
||||
});
|
||||
|
||||
redirect(303, '/admin/donations');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user