import { periodWindow } from '@shared/timezone'; import { famMeta } from './fam'; import { evaluateFam } from './bonuses'; export async function myChores(pb: any, famId: string, memberId: string) { const [templates, assigned, completions, rewards, bonusConfigs] = await Promise.all([ pb.collection('chore_templates').getFullList({ filter: `famId = '${famId}'` }), pb.collection('assigned_chores').getFullList({ filter: `famId = '${famId}' && memberId = '${memberId}'` }), pb.collection('completions').getFullList({ filter: `famId = '${famId}' && memberId = '${memberId}'` }), pb.collection('rewards').getFullList({ filter: `famId = '${famId}' && memberId = '${memberId}'` }), pb.collection('bonus_configs').getFullList({ filter: `famId = '${famId}' && status = 'active'` }) ]); const { payday, paydayTime, tz } = await famMeta(pb, famId); let settings: any = {}; try { const s = await pb .collection('settings') .getFullList({ filter: `famId = '${famId}'` }); settings = s?.[0] || {}; } catch {} return { templates, assigned, completions, rewards, bonusConfigs, payday, paydayTime, timezone: tz, simulateEow: !!settings.simulateEow }; } export async function toggle(pb: any, famId: string, memberId: string, body: { assignedChoreId: string; date: string }) { const { assignedChoreId, date } = body; if (!assignedChoreId || !date) throw new Error('assignedChoreId and date required'); const choreList = await pb .collection('assigned_chores') .getFullList({ filter: `famId = '${famId}' && id = '${assignedChoreId}'` }); const chore = choreList?.[0]; const isTodo = chore?.isTodo; let filter: string; if (isTodo) { filter = `assignedChoreId = '${assignedChoreId}' && memberId = '${memberId}'`; } else { const { payday, tz } = await famMeta(pb, famId); const { from, to } = periodWindow(chore?.frequency, payday, tz); filter = `assignedChoreId = '${assignedChoreId}' && memberId = '${memberId}' && date >= '${from}' && date < '${to}'`; } const existing = await pb .collection('completions') .getFullList({ filter }); if (existing?.length > 0) { await pb.collection('completions').delete(existing[0].id); // Un-completing a cash todo removes its one-time reward. if (isTodo && existing[0].rewardId) { await pb.collection('rewards').delete(existing[0].rewardId).catch(() => {}); } evaluateFam(pb, famId).catch(() => {}); return { completed: false }; } let rewardId: string | undefined; if (isTodo && chore?.type === 'money' && Number(chore.value) > 0) { const reward = await pb.collection('rewards').create({ famId, memberId, label: chore.customName || 'Cash todo', value: Number(chore.value), rewardType: 'cash', status: 'unclaimed', claimable: 'payday', date }); rewardId = reward.id; } const record = await pb.collection('completions').create({ famId, memberId, assignedChoreId, date, completedAt: new Date().toISOString(), ...(rewardId ? { rewardId } : {}) }); evaluateFam(pb, famId).catch(() => {}); return { completed: true, record }; } export async function revoke(pb: any, famId: string, completionId: string) { const completions = await pb .collection('completions') .getFullList({ filter: `famId = '${famId}' && id = '${completionId}'` }); if (!completions?.length) throw new Error('Completion not found'); await pb.collection('completions').delete(completionId); evaluateFam(pb, famId).catch(() => {}); return { revoked: true }; }