Compare commits
2 commits
main
...
recurring-
Author | SHA1 | Date | |
---|---|---|---|
5d0be6cd84 | |||
c3cee37add |
2 changed files with 218 additions and 0 deletions
197
src/app/recurringtransactions/recurringtransaction.ts
Normal file
197
src/app/recurringtransactions/recurringtransaction.ts
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
export class RecurringTransaction {
|
||||||
|
id: string = '';
|
||||||
|
title: string;
|
||||||
|
description: string = null;
|
||||||
|
frequency: Frequency;
|
||||||
|
start: Date = new Date();
|
||||||
|
end?: Date;
|
||||||
|
amount: number;
|
||||||
|
expense = true;
|
||||||
|
categoryId: string;
|
||||||
|
budgetId: string;
|
||||||
|
createdBy: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Frequency {
|
||||||
|
unit: FrequencyUnit;
|
||||||
|
count: number;
|
||||||
|
time: Time;
|
||||||
|
amount?: (void | Set<DayOfWeek> | DayOfMonth | DayOfYear);
|
||||||
|
|
||||||
|
private constructor(unit: FrequencyUnit, count: number, time: Time, amount?: (void | Set<DayOfWeek> | DayOfMonth | DayOfYear) = null) {
|
||||||
|
this.unit = unit;
|
||||||
|
this.count = count;
|
||||||
|
this.time = time;
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Daily(count: number, time: Time): Frequency {
|
||||||
|
return new Frequency(FrequencyUnit.DAILY, count, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Weekly(count: number, time: Time, daysOfWeek: Set<DayOfWeek>): Frequency {
|
||||||
|
return new Frequency(FrequencyUnit.WEEKLY, count, time, daysOfWeek)
|
||||||
|
}
|
||||||
|
|
||||||
|
static Monthly(count: number, time: Time, dayOfMonth: DayOfMonth): Frequency {
|
||||||
|
return new Frequency(FrequencyUnit.MONTHLY, count, time, dayOfMonth)
|
||||||
|
}
|
||||||
|
|
||||||
|
static Yearly(count: number, time: Time, dayOfYear: DayOfYear): Frequency {
|
||||||
|
return new Frequency(FrequencyUnit.YEARLY, count, time, dayOfYear)
|
||||||
|
}
|
||||||
|
|
||||||
|
static parse(s: string): Frequency {
|
||||||
|
const parts = s.split(';');
|
||||||
|
let count: number, time: Time;
|
||||||
|
switch (parts[0]) {
|
||||||
|
case 'D':
|
||||||
|
count = Number.parseInt(parts[1]);
|
||||||
|
time = Time.parse(parts[2]);
|
||||||
|
return this.Daily(count, time);
|
||||||
|
case 'W':
|
||||||
|
count = Number.parseInt(parts[1]);
|
||||||
|
time = Time.parse(parts[3]);
|
||||||
|
const daysOfWeek = new Set(parts[2].split(',').map(day => DayOfWeek[day]));
|
||||||
|
return this.Weekly(count, time, daysOfWeek);
|
||||||
|
case 'M':
|
||||||
|
count = Number.parseInt(parts[1]);
|
||||||
|
time = Time.parse(parts[3]);
|
||||||
|
const dayOfMonth = DayOfMonth.parse(parts[2]);
|
||||||
|
return this.Monthly(count, time, dayOfMonth);
|
||||||
|
case 'Y':
|
||||||
|
count = Number.parseInt(parts[1]);
|
||||||
|
time = Time.parse(parts[3]);
|
||||||
|
const dayOfYear = DayOfYear.parse(parts[2]);
|
||||||
|
return this.Yearly(count, time, dayOfYear);
|
||||||
|
default:
|
||||||
|
throw new Error(`Invalid Frequency format: ${s}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FrequencyUnit {
|
||||||
|
DAILY = 'D',
|
||||||
|
WEEKLY = 'W',
|
||||||
|
MONTHLY = 'M',
|
||||||
|
YEARLY = 'Y',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Time {
|
||||||
|
hours: number;
|
||||||
|
minutes: number;
|
||||||
|
seconds: number;
|
||||||
|
|
||||||
|
constructor(hours: number, minutes: number, seconds: number) {
|
||||||
|
this.hours = hours;
|
||||||
|
this.minutes = minutes;
|
||||||
|
this.seconds = seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return [
|
||||||
|
String(this.hours).padStart(2, '0'),
|
||||||
|
String(this.minutes).padStart(2, '0'),
|
||||||
|
String(this.seconds).padStart(2, '0'),
|
||||||
|
].join(':')
|
||||||
|
}
|
||||||
|
|
||||||
|
static parse(s: string): Time {
|
||||||
|
if (!s.match(/[0-9]{2}:[0-9]{2}:[0-9]{2}/) {
|
||||||
|
throw new Error('Invalid time format. Time must be formatted as HH:mm:ss');
|
||||||
|
}
|
||||||
|
const parts = s.split(':').map(part => Number.parseInt(part));
|
||||||
|
return new Time(parts[0], parts[1], parts[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Position {
|
||||||
|
DAY = 'DAY',
|
||||||
|
FIRST = 'FIRST',
|
||||||
|
SECOND = 'SECOND',
|
||||||
|
THIRD = 'THIRD',
|
||||||
|
FOURTH = 'FOURTH',
|
||||||
|
LAST = 'LAST',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DayOfWeek {
|
||||||
|
MONDAY = 'MONDAY',
|
||||||
|
TUESDAY = 'TUESDAY',
|
||||||
|
WEDNESDAY = 'WEDNESDAY',
|
||||||
|
THURSDAY = 'THURSDAY',
|
||||||
|
FRIDAY = 'FRIDAY',
|
||||||
|
SATURDAY = 'SATURDAY',
|
||||||
|
SUNDAY = 'SUNDAY',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DayOfMonth {
|
||||||
|
position: Position;
|
||||||
|
day: (number | DayOfWeek);
|
||||||
|
|
||||||
|
private constructor(position: Position, day: (number | DayOfWeek)) {
|
||||||
|
this.position = position;
|
||||||
|
this.day = day;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Each(day: number): DayOfMonth {
|
||||||
|
if (day < 1 || day > 31) {
|
||||||
|
throw new Error('Day must be between 1 and 31');
|
||||||
|
}
|
||||||
|
return new DayOfMonth(Position.DAY, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PositionalDayOfWeek(position: Position, day: DayOfWeek): DayOfMonth {
|
||||||
|
if (position === Position.DAY) {
|
||||||
|
throw new Error('Use DayOfMonth.Each() to create a monthly recurring transaction on the same calendar day');
|
||||||
|
}
|
||||||
|
return new DayOfMonth(position, day)
|
||||||
|
}
|
||||||
|
|
||||||
|
static parse(s: string): DayOfMonth {
|
||||||
|
const parts = s.split('-');
|
||||||
|
const position = Position[parts[0]];
|
||||||
|
if (position === Position.DAY) {
|
||||||
|
return DayOfMonth.Each(Number.parseInt(parts[1]));
|
||||||
|
} else {
|
||||||
|
return DayOfMonth.PositionalDayOfWeek(position, DayOfWeek[parts[1]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DayOfYear {
|
||||||
|
month: number;
|
||||||
|
day: number;
|
||||||
|
|
||||||
|
constructor(month: number, day: number) {
|
||||||
|
this.month = month;
|
||||||
|
this.day = day;
|
||||||
|
}
|
||||||
|
|
||||||
|
static parse(s: string): DayOfYear {
|
||||||
|
if (!s.match(/[0-9]{2}-[0-9]{2}/)) {
|
||||||
|
throw new Error(`Invalid format for DayOfYear: ${s}`)
|
||||||
|
}
|
||||||
|
const parts = s.split('-').map(part => Number.parseInt(part));
|
||||||
|
if (parts[0] < 1 || parts[0] > 12) {
|
||||||
|
throw new Error(`Invalid month for DayOfYear: ${parts[0]}`);
|
||||||
|
}
|
||||||
|
let maxDay: number;
|
||||||
|
switch (parts[0]) {
|
||||||
|
case 2:
|
||||||
|
maxDay = 29;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
case 6:
|
||||||
|
case 9:
|
||||||
|
case 11:
|
||||||
|
maxDay = 30;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
maxDay = 31;
|
||||||
|
}
|
||||||
|
if (parts[1] < 1 || parts[1] > maxDay) {
|
||||||
|
throw new Error(`Invalid day for DayOfYear: ${parts[0]}`);
|
||||||
|
}
|
||||||
|
return new DayOfYear(parts[0], parts[1]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import { Observable } from 'rxjs';
|
||||||
import { User, UserPermission } from '../users/user';
|
import { User, UserPermission } from '../users/user';
|
||||||
import { Budget } from '../budgets/budget';
|
import { Budget } from '../budgets/budget';
|
||||||
import { Category } from '../categories/category';
|
import { Category } from '../categories/category';
|
||||||
|
import { RecurringTransaction, Frequency } from '../recurringtransactions/recurringtransaction';
|
||||||
import { Transaction } from '../transactions/transaction';
|
import { Transaction } from '../transactions/transaction';
|
||||||
|
|
||||||
export interface TwigsService {
|
export interface TwigsService {
|
||||||
|
@ -54,6 +55,26 @@ export interface TwigsService {
|
||||||
updateTransaction(id: string, changes: object): Observable<Transaction>;
|
updateTransaction(id: string, changes: object): Observable<Transaction>;
|
||||||
deleteTransaction(id: string): Observable<void>;
|
deleteTransaction(id: string): Observable<void>;
|
||||||
|
|
||||||
|
// Recurring Transactions
|
||||||
|
getRecurringTransactions(
|
||||||
|
budgetId: string,
|
||||||
|
): Observable<RecurringTransaction[]>;
|
||||||
|
getRecurringTransaction(id: string): Observable<RecurringTransaction>;
|
||||||
|
createRecurringTransaction(
|
||||||
|
id: string,
|
||||||
|
budgetId: string,
|
||||||
|
name: string,
|
||||||
|
description: string,
|
||||||
|
amount: number,
|
||||||
|
frequency: Frequency,
|
||||||
|
start: Date,
|
||||||
|
expense: boolean,
|
||||||
|
category: string,
|
||||||
|
end?: Date,
|
||||||
|
): Observable<RecurringTransaction>;
|
||||||
|
updateRecurringTransaction(id: string, changes: object): Observable<RecurringTransaction>;
|
||||||
|
deleteRecurringTransaction(id: string): Observable<void>;
|
||||||
|
|
||||||
getProfile(id: string): Observable<User>;
|
getProfile(id: string): Observable<User>;
|
||||||
getUsersByUsername(username: string): Observable<User[]>;
|
getUsersByUsername(username: string): Observable<User[]>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue