mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-18 08:44:25 +08:00
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>
This commit is contained in:
committed by
mayswind
parent
70eea8ff33
commit
b94dc8eb83
@@ -19,6 +19,7 @@
|
||||
"default": {
|
||||
"currency": "USD",
|
||||
"firstDayOfWeek": "Sunday",
|
||||
"fiscalYearFormat": "EndYYYY",
|
||||
"longDateFormat": "MMDDYYYY",
|
||||
"shortDateFormat": "MMDDYYYY",
|
||||
"longTimeFormat": "HHMMSSA",
|
||||
@@ -85,6 +86,13 @@
|
||||
"q3": "{year}Q3",
|
||||
"q4": "{year}Q4"
|
||||
},
|
||||
"fiscalYear": {
|
||||
"StartYYYY_EndYYYY": "FY {StartYYYY}-{EndYYYY}",
|
||||
"StartYYYY_EndYY": "FY {StartYYYY}-{EndYY}",
|
||||
"StartYY_EndYY": "FY {StartYY}-{EndYY}",
|
||||
"EndYYYY": "FY {EndYYYY}",
|
||||
"EndYY": "FY {EndYY}"
|
||||
},
|
||||
"misc": {
|
||||
"multiTextJoinSeparator": ", ",
|
||||
"hoursBehindDefaultTimezone": "{hours} hour(s) behind default timezone",
|
||||
@@ -1196,6 +1204,7 @@
|
||||
"oldPassword": "Current Password",
|
||||
"defaultCurrency": "Default Currency",
|
||||
"firstDayOfWeek": "First Day of Week",
|
||||
"fiscalYearStart": "Fiscal Year Start Date",
|
||||
"transactionEditScope": "Editable Transaction Range",
|
||||
"name": "Name",
|
||||
"category": "Category",
|
||||
@@ -1366,6 +1375,8 @@
|
||||
"Last month": "Last month",
|
||||
"This year": "This year",
|
||||
"Last year": "Last year",
|
||||
"This fiscal year": "This fiscal year",
|
||||
"Last fiscal year": "Last fiscal year",
|
||||
"Recent 12 months": "Recent 12 months",
|
||||
"Recent 24 months": "Recent 24 months",
|
||||
"Recent 36 months": "Recent 36 months",
|
||||
@@ -1447,10 +1458,12 @@
|
||||
"Default Currency": "Default Currency",
|
||||
"Default Account": "Default Account",
|
||||
"First Day of Week": "First Day of Week",
|
||||
"Fiscal Year Start Date": "Fiscal Year Start Date",
|
||||
"Long Date Format": "Long Date Format",
|
||||
"Short Date Format": "Short Date Format",
|
||||
"Long Time Format": "Long Time Format",
|
||||
"Short Time Format": "Short Time Format",
|
||||
"Fiscal Year Format": "Fiscal Year Format",
|
||||
"Decimal Separator": "Decimal Separator",
|
||||
"Digit Grouping Symbol": "Digit Grouping Symbol",
|
||||
"Digit Grouping": "Digit Grouping",
|
||||
@@ -1807,6 +1820,7 @@
|
||||
"Aggregate by Month": "Aggregate by Month",
|
||||
"Aggregate by Quarter": "Aggregate by Quarter",
|
||||
"Aggregate by Year": "Aggregate by Year",
|
||||
"Aggregate by Fiscal Year": "Aggregate by Fiscal Year",
|
||||
"Filter Accounts": "Filter Accounts",
|
||||
"Filter Transaction Categories": "Filter Transaction Categories",
|
||||
"Filter Transaction Tags": "Filter Transaction Tags",
|
||||
|
||||
+138
-11
@@ -12,6 +12,7 @@ import {
|
||||
type LocalizedDateTimeFormat,
|
||||
type LocalizedDateRange,
|
||||
type LocalizedRecentMonthDateRange,
|
||||
type UnixTimeRange,
|
||||
Month,
|
||||
WeekDay,
|
||||
MeridiemIndicator,
|
||||
@@ -46,6 +47,13 @@ import {
|
||||
CurrencySortingType
|
||||
} from '@/core/currency.ts';
|
||||
|
||||
import {
|
||||
FiscalYearStart,
|
||||
FiscalYearFormat,
|
||||
FiscalYearUnixTime,
|
||||
LANGUAGE_DEFAULT_FISCAL_YEAR_FORMAT_VALUE,
|
||||
} from '@/core/fiscalyear.ts';
|
||||
|
||||
import {
|
||||
CoordinateDisplayType
|
||||
} from '@/core/coordinate.ts';
|
||||
@@ -120,21 +128,25 @@ import {
|
||||
} from '@/lib/common.ts';
|
||||
|
||||
import {
|
||||
isPM,
|
||||
formatUnixTime,
|
||||
formatCurrentTime,
|
||||
formatDate,
|
||||
parseDateFromUnixTime,
|
||||
getYear,
|
||||
getTimezoneOffset,
|
||||
getTimezoneOffsetMinutes,
|
||||
formatMonthDay,
|
||||
formatUnixTime,
|
||||
getBrowserTimezoneOffset,
|
||||
getBrowserTimezoneOffsetMinutes,
|
||||
getTimeDifferenceHoursAndMinutes,
|
||||
getCurrentUnixTime,
|
||||
getDateTimeFormatType,
|
||||
getFiscalYearTimeRangeFromUnixTime,
|
||||
getFiscalYearTimeRangeFromYear,
|
||||
getRecentMonthDateRanges,
|
||||
getTimeDifferenceHoursAndMinutes,
|
||||
getTimezoneOffset,
|
||||
getTimezoneOffsetMinutes,
|
||||
getYear,
|
||||
isDateRangeMatchFullMonths,
|
||||
isDateRangeMatchFullYears,
|
||||
isDateRangeMatchFullMonths
|
||||
isPM,
|
||||
parseDateFromUnixTime,
|
||||
} from '@/lib/datetime.ts';
|
||||
|
||||
import {
|
||||
@@ -629,6 +641,14 @@ export function useI18n() {
|
||||
return t('default.firstDayOfWeek');
|
||||
}
|
||||
|
||||
function getDefaultFiscalYearStart(): string {
|
||||
return t('default.fiscalYearStart');
|
||||
}
|
||||
|
||||
function getDefaultFiscalYearFormat(): string {
|
||||
return t('default.fiscalYearFormat');
|
||||
}
|
||||
|
||||
function getAllLanguageOptions(includeSystemDefault: boolean): LanguageOption[] {
|
||||
const ret: LanguageOption[] = [];
|
||||
|
||||
@@ -766,7 +786,7 @@ export function useI18n() {
|
||||
function getLocalizedDateTimeFormats<T extends DateFormat | TimeFormat>(type: string, allFormatMap: Record<string, T>, allFormatArray: T[], languageDefaultTypeNameKey: string, systemDefaultFormatType: T): LocalizedDateTimeFormat[] {
|
||||
const defaultFormat = getLocalizedDateTimeFormat<T>(type, allFormatMap, allFormatArray, LANGUAGE_DEFAULT_DATE_TIME_FORMAT_VALUE, languageDefaultTypeNameKey, systemDefaultFormatType);
|
||||
const ret: LocalizedDateTimeFormat[] = [];
|
||||
|
||||
|
||||
ret.push({
|
||||
type: LANGUAGE_DEFAULT_DATE_TIME_FORMAT_VALUE,
|
||||
format: defaultFormat,
|
||||
@@ -786,7 +806,7 @@ export function useI18n() {
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
function getAllDateRanges(scene: DateRangeScene, includeCustom?: boolean, includeBillingCycle?: boolean): LocalizedDateRange[] {
|
||||
const ret: LocalizedDateRange[] = [];
|
||||
const allDateRanges = DateRange.values();
|
||||
@@ -919,6 +939,37 @@ export function useI18n() {
|
||||
];
|
||||
}
|
||||
|
||||
function getAllFiscalYearFormats(): FiscalYearFormat[] {
|
||||
const now = getCurrentUnixTime();
|
||||
let fiscalYearStart = userStore.currentUserFiscalYearStart;
|
||||
if (!fiscalYearStart) {
|
||||
fiscalYearStart = FiscalYearStart.Default.value;
|
||||
}
|
||||
let nowFiscalYearRange = getFiscalYearTimeRangeFromUnixTime(now, userStore.currentUserFiscalYearStart);
|
||||
|
||||
const ret: FiscalYearFormat[] = [];
|
||||
|
||||
let defaultFiscalYearFormatType = FiscalYearFormat.parse(t('default.fiscalYearFormat'));
|
||||
if (!defaultFiscalYearFormatType) {
|
||||
defaultFiscalYearFormatType = FiscalYearFormat.Default;
|
||||
}
|
||||
ret.push({
|
||||
type: LANGUAGE_DEFAULT_FISCAL_YEAR_FORMAT_VALUE,
|
||||
displayName: `${t('Language Default')} (${formatTimeRangeToFiscalYearFormat(defaultFiscalYearFormatType, nowFiscalYearRange)})`
|
||||
});
|
||||
|
||||
const allFiscalYearFormats = FiscalYearFormat.values();
|
||||
for (let i = 0; i < allFiscalYearFormats.length; i++) {
|
||||
const type = allFiscalYearFormats[i];
|
||||
ret.push({
|
||||
type: type.type,
|
||||
displayName: formatTimeRangeToFiscalYearFormat(type, nowFiscalYearRange),
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getAllDigitGroupingTypes(): LocalizedDigitGroupingType[] {
|
||||
const defaultDigitGroupingTypeName = t('default.digitGrouping');
|
||||
let defaultDigitGroupingType = DigitGroupingType.parse(defaultDigitGroupingTypeName);
|
||||
@@ -1315,6 +1366,21 @@ export function useI18n() {
|
||||
return digitGroupingType.type;
|
||||
}
|
||||
|
||||
function getCurrentFiscalYearFormatType(): number {
|
||||
let fiscalYearFormat = FiscalYearFormat.valueOf(userStore.currentUserFiscalYearFormat);
|
||||
|
||||
if (!fiscalYearFormat) {
|
||||
const defaultFiscalYearFormatTypeName = t('default.fiscalYearFormat');
|
||||
fiscalYearFormat = FiscalYearFormat.parse(defaultFiscalYearFormatTypeName);
|
||||
|
||||
if (!fiscalYearFormat) {
|
||||
fiscalYearFormat = FiscalYearFormat.Default;
|
||||
}
|
||||
}
|
||||
|
||||
return fiscalYearFormat.type;
|
||||
}
|
||||
|
||||
function getCurrencyName(currencyCode: string): string {
|
||||
return t(`currency.name.${currencyCode}`);
|
||||
}
|
||||
@@ -1347,6 +1413,10 @@ export function useI18n() {
|
||||
return formatDate(date, getLocalizedLongDateFormat());
|
||||
}
|
||||
|
||||
function formatMonthDayToLongDay(monthDay: string): string {
|
||||
return formatMonthDay(monthDay, getLocalizedLongMonthDayFormat());
|
||||
}
|
||||
|
||||
function formatYearQuarter(year: number, quarter: number): string {
|
||||
if (1 <= quarter && quarter <= 4) {
|
||||
return t('format.yearQuarter.q' + quarter, {
|
||||
@@ -1357,7 +1427,7 @@ export function useI18n() {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function formatDateRange(dateType: number, startTime: number, endTime: number): string {
|
||||
if (dateType === DateRange.All.type) {
|
||||
return t(DateRange.All.name);
|
||||
@@ -1406,6 +1476,53 @@ export function useI18n() {
|
||||
return `${displayStartTime} ~ ${displayEndTime}`;
|
||||
}
|
||||
|
||||
function formatTimeRangeToFiscalYearFormat(format: FiscalYearFormat, timeRange: FiscalYearUnixTime | UnixTimeRange): string {
|
||||
if (!format) {
|
||||
format = FiscalYearFormat.Default;
|
||||
}
|
||||
|
||||
return t('format.fiscalYear.' + format.displayName, {
|
||||
StartYYYY: formatUnixTime(timeRange.minUnixTime, 'YYYY'),
|
||||
StartYY: formatUnixTime(timeRange.minUnixTime, 'YY'),
|
||||
EndYYYY: formatUnixTime(timeRange.maxUnixTime, 'YYYY'),
|
||||
EndYY: formatUnixTime(timeRange.maxUnixTime, 'YY'),
|
||||
});
|
||||
}
|
||||
|
||||
function formatUnixTimeToFiscalYear(unixTime: number): string {
|
||||
let fiscalYearFormat = FiscalYearFormat.valueOf(getCurrentFiscalYearFormatType());
|
||||
|
||||
if (!fiscalYearFormat) {
|
||||
fiscalYearFormat = FiscalYearFormat.Default;
|
||||
}
|
||||
|
||||
let timeRange = getFiscalYearTimeRangeFromUnixTime(unixTime, userStore.currentUserFiscalYearStart);
|
||||
|
||||
return formatTimeRangeToFiscalYearFormat(fiscalYearFormat, timeRange);
|
||||
}
|
||||
|
||||
function formatYearToFiscalYear(year: number) {
|
||||
let fiscalYearFormat = FiscalYearFormat.valueOf(getCurrentFiscalYearFormatType());
|
||||
|
||||
if (!fiscalYearFormat) {
|
||||
fiscalYearFormat = FiscalYearFormat.Default;
|
||||
}
|
||||
|
||||
let timeRange = getFiscalYearTimeRangeFromYear(year, userStore.currentUserFiscalYearStart);
|
||||
|
||||
return formatTimeRangeToFiscalYearFormat(fiscalYearFormat, timeRange);
|
||||
}
|
||||
|
||||
function formatFiscalYearStart(fiscalYearStart: number) {
|
||||
let fy = FiscalYearStart.fromNumber(fiscalYearStart);
|
||||
|
||||
if ( !fy ) {
|
||||
fy = FiscalYearStart.Default;
|
||||
}
|
||||
|
||||
return formatMonthDayToLongDay(fy.toMonthDashDayString());
|
||||
}
|
||||
|
||||
function getTimezoneDifferenceDisplayText(utcOffset: number): string {
|
||||
const defaultTimezoneOffset = getTimezoneOffsetMinutes();
|
||||
const offsetTime = getTimeDifferenceHoursAndMinutes(utcOffset - defaultTimezoneOffset);
|
||||
@@ -1699,6 +1816,8 @@ export function useI18n() {
|
||||
// get localization default type
|
||||
getDefaultCurrency,
|
||||
getDefaultFirstDayOfWeek,
|
||||
getDefaultFiscalYearStart,
|
||||
getDefaultFiscalYearFormat,
|
||||
// get all localized info of specified type
|
||||
getAllLanguageOptions,
|
||||
getAllEnableDisableOptions,
|
||||
@@ -1714,6 +1833,7 @@ export function useI18n() {
|
||||
getAllShortDateFormats: () => getLocalizedDateTimeFormats<ShortDateFormat>('shortDate', ShortDateFormat.all(), ShortDateFormat.values(), 'shortDateFormat', ShortDateFormat.Default),
|
||||
getAllLongTimeFormats: () => getLocalizedDateTimeFormats<LongTimeFormat>('longTime', LongTimeFormat.all(), LongTimeFormat.values(), 'longTimeFormat', LongTimeFormat.Default),
|
||||
getAllShortTimeFormats: () => getLocalizedDateTimeFormats<ShortTimeFormat>('shortTime', ShortTimeFormat.all(), ShortTimeFormat.values(), 'shortTimeFormat', ShortTimeFormat.Default),
|
||||
getAllFiscalYearFormats,
|
||||
getAllDateRanges,
|
||||
getAllRecentMonthDateRanges,
|
||||
getAllTimezones,
|
||||
@@ -1750,6 +1870,8 @@ export function useI18n() {
|
||||
getWeekdayLongName,
|
||||
getMultiMonthdayShortNames,
|
||||
getMultiWeekdayLongNames,
|
||||
getCurrentFiscalYearStartFormatted: () => formatMonthDayToLongDay(FiscalYearStart.strictFromNumber(userStore.currentUserFiscalYearStart).toMonthDashDayString()),
|
||||
getCurrentFiscalYearFormatType,
|
||||
getCurrentDecimalSeparator,
|
||||
getCurrentDigitGroupingSymbol,
|
||||
getCurrentDigitGroupingType,
|
||||
@@ -1776,8 +1898,13 @@ export function useI18n() {
|
||||
formatUnixTimeToLongTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongTimeFormat(), utcOffset, currentUtcOffset),
|
||||
formatUnixTimeToShortTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortTimeFormat(), utcOffset, currentUtcOffset),
|
||||
formatDateToLongDate,
|
||||
formatMonthDayToLongDay,
|
||||
formatYearQuarter,
|
||||
formatDateRange,
|
||||
formatFiscalYearStart,
|
||||
formatTimeRangeToFiscalYearFormat,
|
||||
formatUnixTimeToFiscalYear,
|
||||
formatYearToFiscalYear,
|
||||
getTimezoneDifferenceDisplayText,
|
||||
appendDigitGroupingSymbol: getNumberWithDigitGroupingSymbol,
|
||||
parseAmount: getParsedAmountNumber,
|
||||
|
||||
Reference in New Issue
Block a user