Files
ezbookkeeping/src/models/user.ts
T
Sebastian Reategui b94dc8eb83 Feature - Add support for a fiscal year period defined in user settings.
* Add "This fiscal year", "Last fiscal year" as date range options in Transaction Details to filter transactions to those periods
* Add fiscal year ranges to Statistics & Trend Analysis
* Add "fiscal year start date" to user profile settings, allowing the user to select any date of the calendar year as the start of the fiscal year
* Add "fiscal year format" to user profile settings, allowing the user to specify how financial year date labels should appear

Implementation notes:
* The default fiscal year start is January 1 and the default fiscal year display format is "FY 2025"
* Fiscal year start date (month number & day number) are stored together in db as a uint16, high byte & low byte respectively
* February 29 is disallowed as a fiscal year start date, since it is never used as a convention in any country
* Jest is added to the project as a dev dependency, for unit tests in frontend

Signed-off-by: Sebastian Reategui <seb.reategui@gmail.com>
2025-06-07 22:04:47 +08:00

246 lines
9.4 KiB
TypeScript

import { LongDateFormat, ShortDateFormat, LongTimeFormat, ShortTimeFormat } from '@/core/datetime.ts';
import { DecimalSeparator, DigitGroupingSymbol, DigitGroupingType } from '@/core/numeral.ts';
import { CurrencyDisplayType } from '@/core/currency.ts';
import { CoordinateDisplayType } from '@/core/coordinate.ts';
import { PresetAmountColor } from '@/core/color.ts';
import type { LocalizedPresetCategory } from '@/core/category.ts';
import { TransactionEditScopeType } from '@/core/transaction.ts';
import { FiscalYearFormat, FiscalYearStart } from '@/core/fiscalyear';
export class User {
public username: string = '';
public password: string = '';
public confirmPassword: string = '';
public email: string = '';
public nickname: string = '';
public language: string;
public defaultCurrency: string;
public firstDayOfWeek: number;
public defaultAccountId: string = '';
public transactionEditScope: number = 1;
public fiscalYearStart: number = 0;
public fiscalYearFormat: number = 0;
public longDateFormat: number = 0;
public shortDateFormat: number = 0;
public longTimeFormat: number = 0;
public shortTimeFormat: number = 0;
public decimalSeparator: number = 0;
public digitGroupingSymbol: number = 0;
public digitGrouping: number = 0;
public currencyDisplayType: number = 0;
public coordinateDisplayType: number = 0;
public expenseAmountColor: number = 0;
public incomeAmountColor: number = 0;
private constructor(language: string, defaultCurrency: string, firstDayOfWeek: number) {
this.language = language;
this.defaultCurrency = defaultCurrency;
this.firstDayOfWeek = firstDayOfWeek;
}
public fillFrom(user: User | UserBasicInfo | UserProfileResponse): void {
this.username = user.username;
this.email = user.email;
this.nickname = user.nickname;
this.language = user.language;
this.defaultCurrency = user.defaultCurrency;
this.firstDayOfWeek = user.firstDayOfWeek;
this.defaultAccountId = user.defaultAccountId;
this.transactionEditScope = user.transactionEditScope;
this.fiscalYearStart = user.fiscalYearStart;
this.longDateFormat = user.longDateFormat;
this.shortDateFormat = user.shortDateFormat;
this.longTimeFormat = user.longTimeFormat;
this.shortTimeFormat = user.shortTimeFormat;
this.fiscalYearFormat = user.fiscalYearFormat;
this.decimalSeparator = user.decimalSeparator;
this.digitGroupingSymbol = user.digitGroupingSymbol;
this.digitGrouping = user.digitGrouping;
this.currencyDisplayType = user.currencyDisplayType;
this.coordinateDisplayType = user.coordinateDisplayType;
this.expenseAmountColor = user.expenseAmountColor;
this.incomeAmountColor = user.incomeAmountColor;
}
public toRegisterRequest(categories?: LocalizedPresetCategory[]): UserRegisterRequest {
return {
username: this.username,
email: this.email,
nickname: this.nickname,
password: this.password,
language: this.language,
defaultCurrency: this.defaultCurrency,
firstDayOfWeek: this.firstDayOfWeek,
categories: categories
};
}
public toProfileUpdateRequest(currentPassword?: string): UserProfileUpdateRequest {
return {
email: this.email,
nickname: this.nickname,
password: this.password,
oldPassword: currentPassword,
defaultAccountId: this.defaultAccountId,
transactionEditScope: this.transactionEditScope,
language: this.language,
defaultCurrency: this.defaultCurrency,
firstDayOfWeek: this.firstDayOfWeek,
fiscalYearStart: this.fiscalYearStart,
longDateFormat: this.longDateFormat,
shortDateFormat: this.shortDateFormat,
longTimeFormat: this.longTimeFormat,
shortTimeFormat: this.shortTimeFormat,
fiscalYearFormat: this.fiscalYearFormat,
decimalSeparator: this.decimalSeparator,
digitGroupingSymbol: this.digitGroupingSymbol,
digitGrouping: this.digitGrouping,
currencyDisplayType: this.currencyDisplayType,
coordinateDisplayType: this.coordinateDisplayType,
expenseAmountColor: this.expenseAmountColor,
incomeAmountColor: this.incomeAmountColor
};
}
public static of(userInfo: UserBasicInfo): User {
const user = new User(userInfo.language, userInfo.defaultCurrency, userInfo.firstDayOfWeek);
user.defaultAccountId = userInfo.defaultAccountId;
user.transactionEditScope = userInfo.transactionEditScope;
user.fiscalYearStart = userInfo.fiscalYearStart;
user.longDateFormat = userInfo.longDateFormat;
user.shortDateFormat = userInfo.shortDateFormat;
user.longTimeFormat = userInfo.longTimeFormat;
user.shortTimeFormat = userInfo.shortTimeFormat;
user.fiscalYearFormat = userInfo.fiscalYearFormat;
user.decimalSeparator = userInfo.decimalSeparator;
user.digitGroupingSymbol = userInfo.digitGroupingSymbol;
user.digitGrouping = userInfo.digitGrouping;
user.currencyDisplayType = userInfo.currencyDisplayType;
user.coordinateDisplayType = userInfo.coordinateDisplayType;
user.expenseAmountColor = userInfo.expenseAmountColor;
user.incomeAmountColor = userInfo.incomeAmountColor;
return user;
}
public static createNewUser(language: string, defaultCurrency: string, firstDayOfWeek: number): User {
return new User(language, defaultCurrency, firstDayOfWeek);
}
}
export interface UserBasicInfo {
readonly username: string;
readonly email: string;
readonly nickname: string;
readonly avatar: string;
readonly avatarProvider?: string;
readonly defaultAccountId: string;
readonly transactionEditScope: number;
readonly language: string;
readonly defaultCurrency: string;
readonly fiscalYearStart: number;
readonly firstDayOfWeek: number;
readonly longDateFormat: number;
readonly shortDateFormat: number;
readonly longTimeFormat: number;
readonly shortTimeFormat: number;
readonly fiscalYearFormat: number;
readonly decimalSeparator: number;
readonly digitGroupingSymbol: number;
readonly digitGrouping: number;
readonly currencyDisplayType: number;
readonly coordinateDisplayType: number;
readonly expenseAmountColor: number;
readonly incomeAmountColor: number;
readonly emailVerified: boolean;
}
export interface UserLoginRequest {
readonly loginName: string;
readonly password: string;
}
export interface UserRegisterRequest {
readonly username: string;
readonly email: string;
readonly nickname: string;
readonly password: string;
readonly language: string;
readonly defaultCurrency: string;
readonly firstDayOfWeek: number;
readonly categories?: LocalizedPresetCategory[];
}
export interface UserVerifyEmailResponse {
readonly newToken?: string;
readonly user: UserBasicInfo;
readonly notificationContent?: string;
}
export interface UserResendVerifyEmailRequest {
readonly email: string;
readonly password: string;
}
export interface UserProfileUpdateRequest {
readonly email?: string;
readonly nickname?: string;
readonly password?: string;
readonly oldPassword?: string;
readonly defaultAccountId?: string;
readonly transactionEditScope?: number;
readonly language?: string;
readonly defaultCurrency?: string;
readonly firstDayOfWeek?: number;
readonly fiscalYearStart?: number;
readonly longDateFormat?: number;
readonly shortDateFormat?: number;
readonly longTimeFormat?: number;
readonly shortTimeFormat?: number;
readonly fiscalYearFormat?: number;
readonly decimalSeparator?: number;
readonly digitGroupingSymbol?: number;
readonly digitGrouping?: number;
readonly currencyDisplayType?: number;
readonly coordinateDisplayType?: number;
readonly expenseAmountColor?: number;
readonly incomeAmountColor?: number;
}
export interface UserProfileUpdateResponse {
readonly user: UserBasicInfo;
readonly newToken?: string;
}
export interface UserProfileResponse extends UserBasicInfo {
readonly lastLoginAt: number;
}
export const EMPTY_USER_BASIC_INFO: UserBasicInfo = {
username: '',
email: '',
nickname: '',
avatar: '',
avatarProvider: undefined,
defaultAccountId: '',
transactionEditScope: TransactionEditScopeType.All.type,
language: '',
defaultCurrency: '',
firstDayOfWeek: -1,
fiscalYearStart: FiscalYearStart.Default.value,
longDateFormat: LongDateFormat.Default.type,
shortDateFormat: ShortDateFormat.Default.type,
longTimeFormat: LongTimeFormat.Default.type,
shortTimeFormat: ShortTimeFormat.Default.type,
fiscalYearFormat: FiscalYearFormat.Default.type,
decimalSeparator: DecimalSeparator.LanguageDefaultType,
digitGroupingSymbol: DigitGroupingSymbol.LanguageDefaultType,
digitGrouping: DigitGroupingType.LanguageDefaultType,
currencyDisplayType: CurrencyDisplayType.Default.type,
coordinateDisplayType: CoordinateDisplayType.Default.type,
expenseAmountColor: PresetAmountColor.DefaultExpenseColor.type,
incomeAmountColor: PresetAmountColor.DefaultIncomeColor.type,
emailVerified: false
}