diff --git a/src/consts/datetime.js b/src/consts/datetime.js index a1958f90..c95015f1 100644 --- a/src/consts/datetime.js +++ b/src/consts/datetime.js @@ -252,6 +252,22 @@ const allDateRanges = { [allDateRangeScenes.TrendAnalysis]: true } }, + PreviousBillingCycle: { + type: 51, + name: 'Previous Billing Cycle', + isBillingCycle: true, + availableScenes: { + [allDateRangeScenes.Normal]: true + } + }, + CurrentBillingCycle: { + type: 52, + name: 'Current Billing Cycle', + isBillingCycle: true, + availableScenes: { + [allDateRangeScenes.Normal]: true + } + }, RecentTwelveMonths: { type: 101, name: 'Recent 12 months', @@ -316,7 +332,8 @@ const allDateRangesMap = { [allDateRanges.LastMonth.type]: allDateRanges.LastMonth, [allDateRanges.ThisYear.type]: allDateRanges.ThisYear, [allDateRanges.LastYear.type]: allDateRanges.LastYear, - [allDateRanges.RecentTwelveMonths.type]: allDateRanges.RecentTwelveMonths, + [allDateRanges.PreviousBillingCycle.type]: allDateRanges.PreviousBillingCycle, + [allDateRanges.CurrentBillingCycle.type]: allDateRanges.CurrentBillingCycle, [allDateRanges.RecentTwentyFourMonths.type]: allDateRanges.RecentTwentyFourMonths, [allDateRanges.RecentThirtySixMonths.type]: allDateRanges.RecentThirtySixMonths, [allDateRanges.RecentTwoYears.type]: allDateRanges.RecentTwoYears, @@ -325,6 +342,11 @@ const allDateRangesMap = { [allDateRanges.Custom.type]: allDateRanges.Custom }; +const allBillingCycleDateRangesMap = { + [allDateRanges.PreviousBillingCycle.type]: allDateRanges.PreviousBillingCycle, + [allDateRanges.CurrentBillingCycle.type]: allDateRanges.CurrentBillingCycle +}; + const defaultFirstDayOfWeek = allWeekDays.Sunday.type; const defaultLongDateFormat = allLongDateFormat.YYYYMMDD; const defaultShortDateFormat = allShortDateFormat.YYYYMMDD; @@ -349,6 +371,7 @@ export default { allDateRangeScenes: allDateRangeScenes, allDateRanges: allDateRanges, allDateRangesMap: allDateRangesMap, + allBillingCycleDateRangesMap: allBillingCycleDateRangesMap, defaultFirstDayOfWeek: defaultFirstDayOfWeek, defaultLongDateFormat: defaultLongDateFormat, defaultShortDateFormat: defaultShortDateFormat, diff --git a/src/lib/datetime.js b/src/lib/datetime.js index 43e9d4fa..1f43fb0d 100644 --- a/src/lib/datetime.js +++ b/src/lib/datetime.js @@ -136,6 +136,10 @@ export function getCurrentYear() { return moment().year(); } +export function getCurrentDay() { + return moment().date(); +} + export function parseDateFromUnixTime(unixTime, utcOffset, currentUtcOffset) { if (isNumber(utcOffset)) { if (!isNumber(currentUtcOffset)) { @@ -270,6 +274,14 @@ export function getThisMonthLastUnixTime() { return moment.unix(getThisMonthFirstUnixTime()).add(1, 'months').subtract(1, 'seconds').unix(); } +export function getThisMonthSpecifiedDayFirstUnixTime(date) { + return moment().set({ date: date, hour: 0, minute: 0, second: 0, millisecond: 0 }).unix(); +} + +export function getThisMonthSpecifiedDayLastUnixTime(date) { + return moment.unix(getThisMonthSpecifiedDayFirstUnixTime(date)).add(1, 'days').subtract(1, 'seconds').unix(); +} + export function getThisYearFirstUnixTime() { const today = moment.unix(getTodayFirstUnixTime()); return today.subtract(today.dayOfYear() - 1, 'days').unix(); @@ -476,6 +488,16 @@ export function getShiftedDateRangeAndDateType(minTime, maxTime, scale, firstDay }; } +export function getShiftedDateRangeAndDateTypeForBillingCycle(dateType, scale, firstDayOfWeek, statementDate) { + if (dateType === dateTimeConstants.allDateRanges.PreviousBillingCycle.type && scale === 1) { + return getDateRangeByBillingCycleDateType(dateTimeConstants.allDateRanges.CurrentBillingCycle.type, firstDayOfWeek, statementDate); + } else if (dateType === dateTimeConstants.allDateRanges.CurrentBillingCycle.type && scale === -1) { + return getDateRangeByBillingCycleDateType(dateTimeConstants.allDateRanges.PreviousBillingCycle.type, firstDayOfWeek, statementDate); + } + + return null; +} + export function getDateTypeByDateRange(minTime, maxTime, firstDayOfWeek, scene) { let newDateType = dateTimeConstants.allDateRanges.Custom.type; @@ -567,6 +589,49 @@ export function getDateRangeByDateType(dateType, firstDayOfWeek) { }; } +export function getDateRangeByBillingCycleDateType(dateType, firstDayOfWeek, statementDate) { + let maxTime = 0; + let minTime = 0; + + if (dateType === dateTimeConstants.allDateRanges.PreviousBillingCycle.type || dateType === dateTimeConstants.allDateRanges.CurrentBillingCycle.type) { // Previous Billing Cycle | Current Billing Cycle + if (statementDate) { + if (getCurrentDay() <= statementDate) { + maxTime = getThisMonthSpecifiedDayLastUnixTime(statementDate); + minTime = getUnixTimeBeforeUnixTime(getUnixTimeAfterUnixTime(getThisMonthSpecifiedDayFirstUnixTime(statementDate), 1, 'days'), 1, 'months'); + } else { + maxTime = getUnixTimeAfterUnixTime(getThisMonthSpecifiedDayLastUnixTime(statementDate), 1, 'months'); + minTime = getUnixTimeAfterUnixTime(getThisMonthSpecifiedDayFirstUnixTime(statementDate), 1, 'days'); + } + + if (dateType === dateTimeConstants.allDateRanges.PreviousBillingCycle.type) { + maxTime = getUnixTimeBeforeUnixTime(maxTime, 1, 'months'); + minTime = getUnixTimeBeforeUnixTime(minTime, 1, 'months'); + } + } else { + let fallbackDateRange = null; + + if (dateType === dateTimeConstants.allDateRanges.CurrentBillingCycle.type) { // same as This Month + fallbackDateRange = getDateRangeByDateType(dateTimeConstants.allDateRanges.ThisMonth.type, firstDayOfWeek); + } else if (dateType === dateTimeConstants.allDateRanges.PreviousBillingCycle.type) { // same as Last Month + fallbackDateRange = getDateRangeByDateType(dateTimeConstants.allDateRanges.LastMonth.type, firstDayOfWeek); + } + + if (fallbackDateRange) { + maxTime = fallbackDateRange.maxTime; + minTime = fallbackDateRange.minTime; + } + } + } else { + return null; + } + + return { + dateType: dateType, + maxTime: maxTime, + minTime: minTime + }; +} + export function getRecentMonthDateRanges(monthCount) { const recentDateRanges = []; const thisMonthFirstUnixTime = getThisMonthFirstUnixTime(); diff --git a/src/lib/i18n.js b/src/lib/i18n.js index 8ee8a297..3d4cbfcf 100644 --- a/src/lib/i18n.js +++ b/src/lib/i18n.js @@ -644,7 +644,7 @@ function getAllWeekDays(firstDayOfWeek, translateFn) { return allWeekDays; } -function getAllDateRanges(scene, includeCustom, translateFn) { +function getAllDateRanges(scene, includeCustom, includeBillingCycle, translateFn) { const allDateRanges = []; for (let dateRangeField in datetimeConstants.allDateRanges) { @@ -658,6 +658,18 @@ function getAllDateRanges(scene, includeCustom, translateFn) { continue; } + if (dateRangeType.isBillingCycle) { + if (includeBillingCycle) { + allDateRanges.push({ + type: dateRangeType.type, + displayName: translateFn(dateRangeType.name), + isBillingCycle: dateRangeType.isBillingCycle + }); + } + + continue; + } + if (includeCustom || dateRangeType.type !== datetimeConstants.allDateRanges.Custom.type) { allDateRanges.push({ type: dateRangeType.type, @@ -1746,7 +1758,7 @@ export function i18nFunctions(i18nGlobal) { getTimezoneDifferenceDisplayText: (utcOffset) => getTimezoneDifferenceDisplayText(utcOffset, i18nGlobal.t), getAllCurrencies: () => getAllCurrencies(i18nGlobal.t), getAllWeekDays: (firstDayOfWeek) => getAllWeekDays(firstDayOfWeek, i18nGlobal.t), - getAllDateRanges: (scene, includeCustom) => getAllDateRanges(scene, includeCustom, i18nGlobal.t), + getAllDateRanges: (scene, includeCustom, includeBillingCycle) => getAllDateRanges(scene, includeCustom, includeBillingCycle, i18nGlobal.t), getAllRecentMonthDateRanges: (userStore, includeAll, includeCustom) => getAllRecentMonthDateRanges(userStore, includeAll, includeCustom, i18nGlobal.t), getDateRangeDisplayName: (userStore, dateType, startTime, endTime) => getDateRangeDisplayName(userStore, dateType, startTime, endTime, i18nGlobal.t), getAllTimezoneTypesUsedForStatistics: (currentTimezone) => getAllTimezoneTypesUsedForStatistics(currentTimezone, i18nGlobal.t), diff --git a/src/locales/en.json b/src/locales/en.json index 275c7790..717ee51a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1283,6 +1283,8 @@ "Recent 2 years": "Recent 2 years", "Recent 3 years": "Recent 3 years", "Recent 5 years": "Recent 5 years", + "Previous Billing Cycle": "Previous Billing Cycle", + "Current Billing Cycle": "Current Billing Cycle", "Custom Date": "Custom Date", "Start Time": "Start Time", "End Time": "End Time", diff --git a/src/locales/vi.json b/src/locales/vi.json index f7447dd5..8dbb0127 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -1283,6 +1283,8 @@ "Recent 2 years": "2 năm gần đây", "Recent 3 years": "3 năm gần đây", "Recent 5 years": "5 năm gần đây", + "Previous Billing Cycle": "Previous Billing Cycle", + "Current Billing Cycle": "Current Billing Cycle", "Custom Date": "Ngày tùy chỉnh", "Start Time": "Thời gian bắt đầu", "End Time": "Thời gian kết thúc", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index 7b082670..95d137cd 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -1283,6 +1283,8 @@ "Recent 2 years": "最近2年", "Recent 3 years": "最近3年", "Recent 5 years": "最近5年", + "Previous Billing Cycle": "上个账单周期", + "Current Billing Cycle": "当前账单周期", "Custom Date": "自定义日期", "Start Time": "开始时间", "End Time": "结束时间", diff --git a/src/stores/account.js b/src/stores/account.js index e5736811..b0da200c 100644 --- a/src/stores/account.js +++ b/src/stores/account.js @@ -381,6 +381,47 @@ export const useAccountsStore = defineStore('accounts', { return ret; }, + getAccountStatementDate(accountId) { + if (!accountId) { + return null; + } + + const accountIds = accountId.split(','); + let mainAccount = null; + + for (let i = 0; i < accountIds.length; i++) { + const id = accountIds[i]; + let account = this.allAccountsMap[id]; + + if (!account) { + return null; + } + + if (account.parentId !== '0') { + account = this.allAccountsMap[account.parentId]; + } + + if (mainAccount !== null) { + if (mainAccount.id !== account.id) { + return null; + } else { + continue; + } + } + + mainAccount = account; + } + + if (!mainAccount) { + return null; + } + + if (mainAccount.category === accountConstants.creditCardCategoryType) { + return mainAccount.creditCardStatementDate; + } + + return null; + }, getNetAssets(showAccountBalance) { if (!showAccountBalance) { return '***'; diff --git a/src/stores/transaction.js b/src/stores/transaction.js index 0303dfb0..be217751 100644 --- a/src/stores/transaction.js +++ b/src/stores/transaction.js @@ -825,7 +825,7 @@ export const useTransactionsStore = defineStore('transactions', { querys.push('dateType=' + this.transactionsFilter.dateType); - if (this.transactionsFilter.dateType === datetimeConstants.allDateRanges.Custom.type) { + if (datetimeConstants.allBillingCycleDateRangesMap[this.transactionsFilter.dateType] || this.transactionsFilter.dateType === datetimeConstants.allDateRanges.Custom.type) { querys.push('maxTime=' + this.transactionsFilter.maxTime); querys.push('minTime=' + this.transactionsFilter.minTime); } diff --git a/src/views/desktop/common/cards/AccountFilterSettingsCard.vue b/src/views/desktop/common/cards/AccountFilterSettingsCard.vue index f5854655..3b7a8cc0 100644 --- a/src/views/desktop/common/cards/AccountFilterSettingsCard.vue +++ b/src/views/desktop/common/cards/AccountFilterSettingsCard.vue @@ -152,6 +152,7 @@ import { useAccountsStore } from '@/stores/account.js'; import { useTransactionsStore } from '@/stores/transaction.js'; import { useStatisticsStore } from '@/stores/statistics.js'; +import datetimeConstants from '@/consts/datetime.js'; import accountConstants from '@/consts/account.js'; import { copyObjectTo } from '@/lib/common.js'; import { @@ -322,9 +323,15 @@ export default { self.statisticsStore.updateTransactionStatisticsInvalidState(true); } } else if (this.type === 'transactionListCurrent') { - changed = self.transactionsStore.updateTransactionListFilter({ + const filter = { accountIds: isAllSelected ? '' : finalAccountIds - }); + }; + + if (datetimeConstants.allBillingCycleDateRangesMap[self.transactionsStore.transactionsFilter.dateType] && !self.accountsStore.getAccountStatementDate(filter.accountIds)) { + filter.dateType = datetimeConstants.allDateRanges.Custom.type; + } + + changed = self.transactionsStore.updateTransactionListFilter(filter); if (changed) { self.transactionsStore.updateTransactionListInvalidState(true); diff --git a/src/views/desktop/transactions/ListPage.vue b/src/views/desktop/transactions/ListPage.vue index 475c593a..0f983e7e 100644 --- a/src/views/desktop/transactions/ListPage.vue +++ b/src/views/desktop/transactions/ListPage.vue @@ -146,7 +146,7 @@ {{ dateRange.displayName }} -