Files
homepage/src/models/RecurringPayment.ts
Alexander Bocken 90ea22497f feat: add multi-currency support to cospend payments
- Add ExchangeRate model for currency conversion tracking
- Implement currency utility functions for formatting and conversion
- Add exchange rates API endpoint with caching and fallback rates
- Update Payment and RecurringPayment models to support multiple currencies
- Enhanced payment forms with currency selection and conversion display
- Update split method selector with better currency handling
- Add currency-aware payment display and balance calculations
- Support for EUR, USD, GBP, and CHF with automatic exchange rate fetching

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-14 19:54:31 +02:00

141 lines
3.3 KiB
TypeScript

import mongoose from 'mongoose';
export interface IRecurringPayment {
_id?: string;
title: string;
description?: string;
amount: number; // Amount in the original currency
currency: string; // Original currency code
paidBy: string; // username/nickname of the person who paid
category: 'groceries' | 'shopping' | 'travel' | 'restaurant' | 'utilities' | 'fun' | 'settlement';
splitMethod: 'equal' | 'full' | 'proportional' | 'personal_equal';
splits: Array<{
username: string;
amount?: number; // Amount in original currency
proportion?: number;
personalAmount?: number; // Amount in original currency
}>;
frequency: 'daily' | 'weekly' | 'monthly' | 'custom';
cronExpression?: string; // For custom frequencies using cron syntax
isActive: boolean;
nextExecutionDate: Date;
lastExecutionDate?: Date;
startDate: Date;
endDate?: Date; // Optional end date for the recurring payments
createdBy: string;
createdAt?: Date;
updatedAt?: Date;
}
const RecurringPaymentSchema = new mongoose.Schema(
{
title: {
type: String,
required: true,
trim: true
},
description: {
type: String,
trim: true
},
amount: {
type: Number,
required: true,
min: 0
},
currency: {
type: String,
required: true,
default: 'CHF',
uppercase: true
},
paidBy: {
type: String,
required: true,
trim: true
},
category: {
type: String,
required: true,
enum: ['groceries', 'shopping', 'travel', 'restaurant', 'utilities', 'fun', 'settlement'],
default: 'groceries'
},
splitMethod: {
type: String,
required: true,
enum: ['equal', 'full', 'proportional', 'personal_equal'],
default: 'equal'
},
splits: [{
username: {
type: String,
required: true,
trim: true
},
amount: {
type: Number
},
proportion: {
type: Number,
min: 0,
max: 1
},
personalAmount: {
type: Number,
min: 0
}
}],
frequency: {
type: String,
required: true,
enum: ['daily', 'weekly', 'monthly', 'custom']
},
cronExpression: {
type: String,
validate: {
validator: function(value: string) {
// Only validate if frequency is custom
if (this.frequency === 'custom') {
return value != null && value.trim().length > 0;
}
return true;
},
message: 'Cron expression is required when frequency is custom'
}
},
isActive: {
type: Boolean,
default: true
},
nextExecutionDate: {
type: Date,
required: true
},
lastExecutionDate: {
type: Date
},
startDate: {
type: Date,
required: true,
default: Date.now
},
endDate: {
type: Date
},
createdBy: {
type: String,
required: true,
trim: true
}
},
{
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
}
);
// Index for efficiently finding payments that need to be executed
RecurringPaymentSchema.index({ nextExecutionDate: 1, isActive: 1 });
export const RecurringPayment = mongoose.model<IRecurringPayment>("RecurringPayment", RecurringPaymentSchema);