feat: unify income payment cards and drop period comparisons (WealthLedger/main#33) (#83)
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 13s
Backend verify / verify (push) Failing after 7m1s

feat: unify income payment cards and drop period comparisons (WealthLedger/main#33)
This commit was merged in pull request #83.
This commit is contained in:
2026-10-13 18:16:46 +03:00
parent 90486d4237
commit 31b0e15579
14 changed files with 230 additions and 274 deletions
@@ -14,45 +14,28 @@ const MONDAY_WEEKDAY_INDEX = 1;
const INCLUSIVE_DAY_OFFSET = 1;
const NEXT_DAY_OFFSET = 1;
const PREVIOUS_MONTH_LAST_DAY = 0;
const PREVIOUS_DAY_OFFSET = -1;
const PREVIOUS_YEAR_OFFSET = 1;
const DATE_COMPONENT_DIGITS = 2;
const DAILY_BUCKET_MAX_DAYS = 45;
const WEEKLY_BUCKET_MAX_DAYS = 180;
const DAYS_PER_WEEK = 7;
const ISO_MONTH_LENGTH = 7;
const ISO_YEAR_LENGTH = 4;
export type IncomeBucketGranularity = 'day' | 'month' | 'week';
export function planIncomePeriod(
from: string | undefined,
to: string | undefined,
openedDate?: Date,
): {
granularity: IncomeBucketGranularity;
previous: DateOnlyRange;
selected: DateOnlyRange;
yearAgo: DateOnlyRange | null;
} {
if (Boolean(from) !== Boolean(to)) throw invalidPeriod();
const today = moscowDate(new Date());
const selected =
from && to ? { from, to } : { from: firstOfMonth(today), to: today };
assertRange(selected, today);
const previous = isFullMonth(selected)
? previousMonth(selected.from)
: shiftDays(selected, -rangeDays(selected));
const candidate = yearAgo(selected);
const yearAgoRange =
candidate && (!openedDate || candidate.from >= moscowDate(openedDate))
? candidate
: null;
return {
selected,
previous,
yearAgo: yearAgoRange,
granularity:
rangeDays(selected) <= DAILY_BUCKET_MAX_DAYS
? 'day'
@@ -136,34 +119,6 @@ function endOfMonth(value: string): string {
return new Date(Date.UTC(year, month, PREVIOUS_MONTH_LAST_DAY)).toISOString().slice(ISO_DATE_START, ISO_DATE_LENGTH);
}
function isFullMonth(range: DateOnlyRange): boolean {
return (
range.from === firstOfMonth(range.from) && range.to === endOfMonth(range.to)
);
}
function previousMonth(from: string): DateOnlyRange {
const last = addDays(firstOfMonth(from), PREVIOUS_DAY_OFFSET);
return { from: firstOfMonth(last), to: last };
}
function shiftDays(range: DateOnlyRange, days: number): DateOnlyRange {
return { from: addDays(range.from, days), to: addDays(range.to, days) };
}
function yearAgo(range: DateOnlyRange): DateOnlyRange | null {
const shift = (value: string) => {
const [year, month, day] = dateParts(value);
const result = `${String(year - PREVIOUS_YEAR_OFFSET).padStart(ISO_YEAR_LENGTH, '0')}-${String(month).padStart(DATE_COMPONENT_DIGITS, '0')}-${String(day).padStart(DATE_COMPONENT_DIGITS, '0')}`;
return utcDate(result).toISOString().slice(ISO_DATE_START, ISO_DATE_LENGTH) === result
? result
: null;
};
const from = shift(range.from);
const to = shift(range.to);
return from && to ? { from, to } : null;
}
function invalidPeriod(): BadRequestException {
return new BadRequestException({ code: IncomeErrorCode.InvalidPeriod });
}