Files
workspace/code/app/app-rn/src/helpers/financeAuto.ts
T
2026-07-07 21:35:10 +08:00

153 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import dayjs from "dayjs";
import { FinanceAutoRepeatType } from "@/types/finance";
/** repeat_date 以 ! 开头表示"排除模式":指定的日期/星期几不执行 */
function isExcludeMode(repeatDate: string): boolean {
return repeatDate.startsWith("!");
}
/** 去掉排除前缀,返回干净的日期字符串 */
function cleanRepeatDate(repeatDate: string): string {
return isExcludeMode(repeatDate) ? repeatDate.slice(1) : repeatDate;
}
/**
* 粗粒度判断:从上次执行至今,是否"可能"有遗漏的触发日期
* 只做时间周期级别的判断,不做具体日期匹配(日期匹配交给 shouldRunOnDate / getMissedDates)
*/
export function shouldRun(repeatType: number, repeatDate: string, lastRun: string | null | undefined): boolean {
const today = dayjs();
if (!lastRun) return true; // 从未执行过,需要尝试
const last = dayjs(lastRun);
// 排除模式下每天都是潜在触发日,简单判断 lastRun 不是今天即可
if (isExcludeMode(repeatDate)) {
return !last.isSame(today, "day");
}
switch (repeatType) {
case FinanceAutoRepeatType.Daily:
return !last.isSame(today, "day");
case FinanceAutoRepeatType.Weekly: {
const days = (repeatDate || "").split(",").map(Number).filter((d) => !isNaN(d));
if (days.length === 0) return false;
let check = today;
for (let i = 0; i < 7; i++) {
if (days.includes(check.day())) {
return check.isAfter(last, "day");
}
check = check.subtract(1, "day");
}
return false;
}
case FinanceAutoRepeatType.Monthly:
return !last.isSame(today, "month");
case FinanceAutoRepeatType.Quarterly:
return last.quarter() !== today.quarter() || last.year() !== today.year();
case FinanceAutoRepeatType.Yearly:
// 支持同一年内多个触发日(如 3/15 和 6/1),不再仅按年份判断
return last.isBefore(today, "day");
default:
return false;
}
}
/**
* 获取应该补记账的日期列表(从上次执行到今天)
* 逐日遍历,通过 shouldRunOnDate 精确匹配触发日
*/
export function getMissedDates(
repeatType: number,
repeatDate: string,
lastRun: string | null | undefined,
): string[] {
const today = dayjs();
// 首次执行:只在今天恰好是触发日时才创建账单
if (!lastRun) {
if (shouldRunOnDate(repeatType, repeatDate, today)) {
return [today.format("YYYY-MM-DD")];
}
return [];
}
const last = dayjs(lastRun);
const dates: string[] = [];
let cursor = last.add(1, "day");
while (cursor.isBefore(today, "day") || cursor.isSame(today, "day")) {
const d = cursor.format("YYYY-MM-DD");
if (shouldRunOnDate(repeatType, repeatDate, cursor)) {
dates.push(d);
}
cursor = cursor.add(1, "day");
}
return dates;
}
function shouldRunOnDate(repeatType: number, repeatDate: string, date: dayjs.Dayjs): boolean {
const exclude = isExcludeMode(repeatDate);
const raw = cleanRepeatDate(repeatDate);
switch (repeatType) {
case FinanceAutoRepeatType.Daily: {
if (!exclude || !raw) return true;
// 每天 排除星期几(如 "!0" → 排除周日)
const excludedDays = raw.split(",").map(Number).filter((d) => !isNaN(d));
return !excludedDays.includes(date.day());
}
case FinanceAutoRepeatType.Weekly: {
const days = raw.split(",").map(Number).filter((d) => !isNaN(d));
if (days.length === 0) return false;
const match = days.includes(date.day());
return exclude ? !match : match;
}
case FinanceAutoRepeatType.Monthly:
case FinanceAutoRepeatType.Quarterly: {
const dates = raw.split(",").map(Number).filter((d) => !isNaN(d));
if (dates.length === 0) return false;
const match = dates.includes(date.date());
return exclude ? !match : match;
}
case FinanceAutoRepeatType.Yearly: {
// 月,日 成对存储,如 "3,15,6,1" → 3月15日 + 6月1日
const parts = raw.split(",").map(Number).filter((n) => !isNaN(n));
for (let i = 0; i < parts.length - 1; i += 2) {
if (parts[i] === date.month() + 1 && parts[i + 1] === date.date()) {
return exclude ? false : true;
}
}
return exclude; // 排除模式未命中 → 执行;正常模式未命中 → 不执行
}
default:
return false;
}
}
export const REPEAT_LABELS: Record<number, string> = {
[FinanceAutoRepeatType.Daily]: "每天",
[FinanceAutoRepeatType.Weekly]: "每周",
[FinanceAutoRepeatType.Monthly]: "每月",
};
export const WEEK_DAYS = [
{ value: 1, label: "一" },
{ value: 2, label: "二" },
{ value: 3, label: "三" },
{ value: 4, label: "四" },
{ value: 5, label: "五" },
{ value: 6, label: "六" },
{ value: 0, label: "日" },
];