diff --git a/commands/receipts/receiptCommands.js b/commands/receipts/receiptCommands.js index 980ecf2..5522077 100644 --- a/commands/receipts/receiptCommands.js +++ b/commands/receipts/receiptCommands.js @@ -1,4 +1,5 @@ const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); +const { HOUSEHOLD_IDS } = require('../../core/constants.js'); module.exports = { category: 'utility', @@ -44,46 +45,42 @@ module.exports = { .setDescription('User to view stats for')) ), async execute(interaction) { - console.log('budget command entrypoint'); let db = await interaction.client.localDB; let targetUser = interaction.options.getUser('user') ?? interaction.user; let discordId = interaction.member.id; - console.log(db, discordId); - let allowed = ['372115788498468864', '222457277708369928'].includes(discordId); - console.log(allowed); + let allowed = [HOUSEHOLD_IDS.MIFFY, HOUSEHOLD_IDS.MINZ].includes(discordId); if (!allowed) { - console.log('budget command denied'); await interaction.reply('This command may only be invoked by Miffy!'); return; } - console.log(`Switcching on sub ${interaction.options.getSubcommand()}`); switch (interaction.options.getSubcommand()) { case 'set': - console.log('budget command set'); let override = interaction.options.getBoolean('override') ?? false; let budget = interaction.options.get('amount').value; db.prepare(`INSERT OR REPLACE INTO bot_config (id, weekly_budget) VALUES (1, ?)`).run(budget); + let overrideApplied = true; if (override) { - db.prepare(` - UPDATE weekly_budgets + const result = db.prepare(` + UPDATE weekly_budgets SET budget_amount = ? WHERE id = ( - SELECT id FROM weekly_budgets + SELECT id FROM weekly_budgets WHERE date('now', 'localtime') BETWEEN start_date AND end_date ORDER BY id DESC LIMIT 1 )`).run(budget); - + overrideApplied = result.changes > 0; } - await interaction.reply(`Budget set for ${targetUser.username} to ${Number(budget).toFixed(2)}.`); + const overrideNote = override && !overrideApplied ? " (no active budget period to override this week)" : ""; + await interaction.reply(`Budget set for ${targetUser.username} to ${Number(budget).toFixed(2)}.${overrideNote}`); break; case 'view': const lastWeek = interaction.options.getBoolean('lastweek') ?? false; const currentBudget = db.prepare(` SELECT id, budget_amount, exchange_rate, start_date, end_date FROM weekly_budgets - WHERE ${lastWeek ? "start_date < date('now', 'localtime')" : "date('now', 'localtime') BETWEEN start_date AND end_date"} - ORDER BY start_date DESC + WHERE ${lastWeek ? "end_date < date('now', 'localtime')" : "date('now', 'localtime') BETWEEN start_date AND end_date"} + ORDER BY start_date DESC, created_at DESC LIMIT 1 `).get(); @@ -123,11 +120,11 @@ module.exports = { break case 'stats': const allBudgets = db.prepare(` - SELECT id, budget_amount, exchange_rate, start_date, end_date - FROM weekly_budgets - ORDER BY start_date ASC + SELECT id, budget_amount, exchange_rate, start_date, end_date + FROM weekly_budgets + ORDER BY start_date DESC LIMIT 4 - `).all(); + `).all().reverse(); let statsDescription = ""; let cumulativeSavingsEur = 0; diff --git a/core/constants.js b/core/constants.js new file mode 100644 index 0000000..2ff2b55 --- /dev/null +++ b/core/constants.js @@ -0,0 +1,6 @@ +module.exports = { + HOUSEHOLD_IDS: { + MINZ: '222457277708369928', + MIFFY: '372115788498468864', + }, +}; diff --git a/events/receiptsMessageCreate.js b/events/receiptsMessageCreate.js index 5b9622e..6f9dfab 100644 --- a/events/receiptsMessageCreate.js +++ b/events/receiptsMessageCreate.js @@ -1,4 +1,5 @@ const { Events, channelLink, discordSort } = require('discord.js'); +const { HOUSEHOLD_IDS } = require('../core/constants.js'); module.exports = { name: Events.MessageCreate, @@ -8,8 +9,8 @@ module.exports = { const receiptsChannelId = process.env.NODE_ENV === 'development' ? '1468186493251227658' : '1462060674766344370'; if(message.channel.id === receiptsChannelId) { const CURRENCY_MAP = { - "222457277708369928": "EUR", // Minz - "372115788498468864": "SEK", // Miffy + [HOUSEHOLD_IDS.MINZ]: "EUR", + [HOUSEHOLD_IDS.MIFFY]: "SEK", }; let reply = ""; @@ -25,12 +26,17 @@ module.exports = { LIMIT 1 `).get(); + if (!currentBudget) { + await message.reply("No active budget period found for this week yet - try again in a moment."); + return; + } + if (message.content === "fuck") { let lastSpend = db.prepare(` - SELECT id, amount, message_raw, created_at - FROM grocery_spendings - WHERE discord_id = ? AND budget_id = ? - ORDER BY created_at DESC, id DESC + SELECT id, amount, message_raw, created_at + FROM grocery_spendings + WHERE discord_id = ? AND budget_id = ? AND is_transfer = 0 + ORDER BY created_at DESC, id DESC LIMIT 1 `).get(authorId, currentBudget.id); if (lastSpend) { @@ -45,7 +51,7 @@ module.exports = { const transferRegex = /^transfer\s+(\d+(?:[.,]\d+)?)$/i; const transferMatch = message.content.match(transferRegex); if (transferMatch) { - if (authorId !== "222457277708369928" && authorId !== "372115788498468864") { + if (authorId !== HOUSEHOLD_IDS.MINZ && authorId !== HOUSEHOLD_IDS.MIFFY) { await message.reply("Only Minz and Miffy can use the transfer command."); return; } @@ -53,11 +59,11 @@ module.exports = { let amount = parseFloat(transferMatch[1].replace(',', '.')); - const targetId = authorId === "222457277708369928" ? "372115788498468864" : "222457277708369928"; + const targetId = authorId === HOUSEHOLD_IDS.MINZ ? HOUSEHOLD_IDS.MIFFY : HOUSEHOLD_IDS.MINZ; const insertSpending = db.prepare(` - INSERT INTO grocery_spendings (discord_id, message_id, message_raw, amount, budget_id) - VALUES (?, ?, ?, ?, ?) + INSERT INTO grocery_spendings (discord_id, message_id, message_raw, amount, budget_id, is_transfer) + VALUES (?, ?, ?, ?, ?, 1) `); const runTransfer = db.transaction(() => { @@ -67,18 +73,17 @@ module.exports = { runTransfer(); - const targetName = authorId === "222457277708369928" ? "Miffy" : "Minz"; + const targetName = authorId === HOUSEHOLD_IDS.MINZ ? "Miffy" : "Minz"; await message.reply(`Transferred ${amount.toFixed(2)}€ to ${targetName}.`); return; } - let matches = []; let lines = message.content.split("\n"); let additionalSpent = 0.0; - lines.forEach(line => {0 + lines.forEach(line => { if (line.startsWith('-')) { console.log(`Matching on line ${line}`); - const regex = /-(?=[0-9])([0-9]*[.,]?[0-9]*)/m; + const regex = /-\s*([0-9]+(?:[.,][0-9]+)?)/; let match = regex.exec(line) console.log(match); if (match) { diff --git a/migrations/02_receipts_fixes.js b/migrations/02_receipts_fixes.js new file mode 100644 index 0000000..bd96ef6 --- /dev/null +++ b/migrations/02_receipts_fixes.js @@ -0,0 +1,10 @@ +module.exports = { + up: async ({ context: db }) => { + db.exec(`ALTER TABLE grocery_spendings ADD COLUMN is_transfer INTEGER NOT NULL DEFAULT 0;`); + db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_weekly_budgets_unique_start ON weekly_budgets (start_date);`); + }, + down: async ({ context: db }) => { + db.exec(`DROP INDEX IF EXISTS idx_weekly_budgets_unique_start;`); + db.exec(`ALTER TABLE grocery_spendings DROP COLUMN is_transfer;`); + } +}; diff --git a/timers/receiptTimer.js b/timers/receiptTimer.js index 3ffc967..1e17ab8 100644 --- a/timers/receiptTimer.js +++ b/timers/receiptTimer.js @@ -1,7 +1,9 @@ const { formatDate } = require('../core/utils.js'); module.exports = { - timeout: 10000, + // scheduleNext() below drives the real daily schedule via setTimeout; + // this is just a safety-net interval in case that chain ever breaks. + timeout: 24 * 60 * 60 * 1000, immediate: true, name: 'Receipt Day Announcements', data: { @@ -26,30 +28,24 @@ module.exports = { let db = await client.localDB; const channel = await client.channels.fetch(timer.data.channelId); - const config = await (await db.prepare(`SELECT weekly_budget, last_date_msg_receipts FROM bot_config`).get()); + const config = db.prepare(`SELECT weekly_budget, last_date_msg_receipts FROM bot_config`).get(); const existingBudget = db.prepare(`SELECT id FROM weekly_budgets WHERE start_date = ?`).get(startDate); - + if (!existingBudget) { - await channel.send(`Format date last monday ${lastMonday} as startDate Format date next sunday ${nextSunday} as endDate`); - await channel.send(`\`\`\`SELECT id FROM weekly_budgets WHERE start_date = ${startDate}\`\`\` - ${JSON.stringify(existingBudget)}`); console.log(`No budget found for week starting ${startDate}.`); const response = await fetch(`https://api.frankfurter.dev/v1/latest?amount=1&from=EUR&to=SEK`); - - if (!response.ok) await channel.send(`Failed to fetch exchange rate:\n${response.statusText}`); + if (!response.ok) { + await channel.send(`Failed to fetch exchange rate:\n${response.statusText}`); + return; + } const data = await response.json(); - //await channel.send(`Fetched new exchange rate:\n${JSON.stringify(data)}`); await channel.send(`Reset weekly budget to ${config.weekly_budget.toFixed(2)}€ / ${(config.weekly_budget * data.rates.SEK).toFixed(2)} kr`); - db.prepare(` INSERT INTO weekly_budgets (start_date, end_date, budget_amount, exchange_rate) VALUES (?, ?, ?, ?) `).run(startDate, endDate, config.weekly_budget, data.rates.SEK); - - await channel.send(`\`\`\`INSERT INTO weekly_budgets (start_date, end_date, budget_amount, exchange_rate) - VALUES (${startDate}, ${endDate}, ${config.weekly_budget}, ${data.rates.SEK})\`\`\``); } else { console.log(`budget found for week starting ${startDate}.`); } @@ -61,25 +57,7 @@ module.exports = { ) as was_sent_today `).get(); - const debug = db.prepare(` - SELECT - date(last_date_msg_receipts, 'localtime') AS last_date, - datetime('now', 'localtime') AS current_time_full, - (date(last_date_msg_receipts, 'localtime') = date('now', 'localtime')) AS was_sent_today - FROM bot_config - LIMIT 1; - `).get(); - - if (!notification.was_sent_today) { - await channel.send(`\`\`\`SELECT - date(last_date_msg_receipts, 'localtime') AS last_date, - datetime('now', 'localtime') AS current_time_full, - (date(last_date_msg_receipts, 'localtime') = date('now', 'localtime')) AS was_sent_today - FROM bot_config - LIMIT 1;\`\`\` - - ${JSON.stringify(debug)}`); const currentDate = new Date().toLocaleDateString('en-GB', { weekday: 'long', day: '2-digit',