feat: add payment management improvements and reminder emails

- Add option to approve/reject payments without sending notification emails
  (checkbox in review popup, default enabled)
- Add payment reminder email template and send functionality
- Track when reminder emails are sent (reminderSentAt field)
- Display reminder sent timestamp in payment review popup
- Make payment review popup scrollable for better UX
- Add payment-reminder template to email system (available in admin emails)
This commit is contained in:
Michilis
2026-02-05 04:13:42 +00:00
parent 0c142884c7
commit 23d0325d8d
8 changed files with 357 additions and 23 deletions

View File

@@ -209,6 +209,9 @@ async function migrate() {
try {
await (db as any).run(sql`ALTER TABLE payments ADD COLUMN payer_name TEXT`);
} catch (e) { /* column may already exist */ }
try {
await (db as any).run(sql`ALTER TABLE payments ADD COLUMN reminder_sent_at TEXT`);
} catch (e) { /* column may already exist */ }
// Invoices table
await (db as any).run(sql`
@@ -577,6 +580,9 @@ async function migrate() {
try {
await (db as any).execute(sql`ALTER TABLE payments ADD COLUMN payer_name VARCHAR(255)`);
} catch (e) { /* column may already exist */ }
try {
await (db as any).execute(sql`ALTER TABLE payments ADD COLUMN reminder_sent_at TIMESTAMP`);
} catch (e) { /* column may already exist */ }
// Invoices table
await (db as any).execute(sql`

View File

@@ -115,6 +115,7 @@ export const sqlitePayments = sqliteTable('payments', {
paidAt: text('paid_at'),
paidByAdminId: text('paid_by_admin_id'),
adminNote: text('admin_note'), // Internal admin notes
reminderSentAt: text('reminder_sent_at'), // When payment reminder email was sent
createdAt: text('created_at').notNull(),
updatedAt: text('updated_at').notNull(),
});
@@ -405,6 +406,7 @@ export const pgPayments = pgTable('payments', {
paidAt: timestamp('paid_at'),
paidByAdminId: uuid('paid_by_admin_id'),
adminNote: pgText('admin_note'),
reminderSentAt: timestamp('reminder_sent_at'), // When payment reminder email was sent
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at').notNull(),
});