86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
import { billingCycleForSchedule, scheduleValue } from './billingSchedule';
|
|
|
|
interface Category {
|
|
id: string | number;
|
|
name?: string | null;
|
|
}
|
|
|
|
interface Template {
|
|
name?: string | null;
|
|
categoryKeywords?: string[];
|
|
}
|
|
|
|
// The source bill a draft is seeded from. The specific fields the draft reads
|
|
// are declared; the index signature carries every other field through the spread.
|
|
export interface SourceBill {
|
|
id?: string | number | null;
|
|
name?: string | null;
|
|
category_id?: string | number | null;
|
|
due_day?: number | null;
|
|
expected_amount?: number | null;
|
|
cycle_type?: string | null;
|
|
billing_cycle?: string | null;
|
|
cycle_day?: string | number | null;
|
|
autopay_enabled?: unknown;
|
|
autodraft_status?: string | null;
|
|
auto_mark_paid?: unknown;
|
|
has_2fa?: unknown;
|
|
snowball_include?: unknown;
|
|
snowball_exempt?: unknown;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
interface MakeBillDraftOptions {
|
|
copy?: boolean;
|
|
template?: Template | null;
|
|
categories?: Category[];
|
|
}
|
|
|
|
function categoryForTemplate(template: Template | null | undefined, categories: Category[] = []): string | number | null {
|
|
const keywords = template?.categoryKeywords || [];
|
|
const match = categories.find(category => {
|
|
const name = String(category.name || '').toLowerCase();
|
|
return keywords.some(keyword => name.includes(keyword));
|
|
});
|
|
return match?.id ?? null;
|
|
}
|
|
|
|
function categoryIdOrFallback(
|
|
categoryId: string | number | null | undefined,
|
|
template: Template | null,
|
|
categories: Category[] = [],
|
|
): string | number | null {
|
|
if (categoryId && categories.some(category => String(category.id) === String(categoryId))) {
|
|
return categoryId;
|
|
}
|
|
return template ? categoryForTemplate(template, categories) : null;
|
|
}
|
|
|
|
export function makeBillDraft(source: SourceBill | null | undefined, { copy = false, template = null, categories = [] }: MakeBillDraftOptions = {}): SourceBill {
|
|
const data: SourceBill = source || {};
|
|
return {
|
|
...data,
|
|
id: undefined,
|
|
source_bill_id: copy && data.id != null ? data.id : undefined,
|
|
active: 1,
|
|
name: copy
|
|
? `${data.name || 'Bill'} (Copy)`
|
|
: (data.name || template?.name || ''),
|
|
category_id: categoryIdOrFallback(data.category_id, template, categories),
|
|
due_day: data.due_day || 1,
|
|
expected_amount: data.expected_amount ?? 0,
|
|
billing_cycle: billingCycleForSchedule(scheduleValue(data)),
|
|
cycle_type: scheduleValue(data),
|
|
cycle_day: String(data.cycle_day || '1'),
|
|
autopay_enabled: !!data.autopay_enabled,
|
|
autodraft_status: data.autodraft_status || (data.autopay_enabled ? 'assumed_paid' : 'none'),
|
|
auto_mark_paid: !!data.auto_mark_paid,
|
|
has_2fa: !!data.has_2fa,
|
|
snowball_include: !!data.snowball_include,
|
|
snowball_exempt: !!data.snowball_exempt,
|
|
};
|
|
}
|
|
|
|
/** The pre-filled draft object produced by makeBillDraft (fed to BillModal's `initialBill`). */
|
|
export type BillDraft = ReturnType<typeof makeBillDraft>;
|