Fix receipts bugs: budget queries, undo/transfer desync, timer spam

This commit is contained in:
2026-08-09 23:19:02 +02:00
parent f9dd88b9b8
commit cd5b076d65
5 changed files with 59 additions and 63 deletions

View File

@@ -1,4 +1,5 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { HOUSEHOLD_IDS } = require('../../core/constants.js');
module.exports = { module.exports = {
category: 'utility', category: 'utility',
@@ -44,46 +45,42 @@ module.exports = {
.setDescription('User to view stats for')) .setDescription('User to view stats for'))
), ),
async execute(interaction) { async execute(interaction) {
console.log('budget command entrypoint');
let db = await interaction.client.localDB; let db = await interaction.client.localDB;
let targetUser = interaction.options.getUser('user') ?? interaction.user; let targetUser = interaction.options.getUser('user') ?? interaction.user;
let discordId = interaction.member.id; let discordId = interaction.member.id;
console.log(db, discordId); let allowed = [HOUSEHOLD_IDS.MIFFY, HOUSEHOLD_IDS.MINZ].includes(discordId);
let allowed = ['372115788498468864', '222457277708369928'].includes(discordId);
console.log(allowed);
if (!allowed) { if (!allowed) {
console.log('budget command denied');
await interaction.reply('This command may only be invoked by Miffy!'); await interaction.reply('This command may only be invoked by Miffy!');
return; return;
} }
console.log(`Switcching on sub ${interaction.options.getSubcommand()}`);
switch (interaction.options.getSubcommand()) { switch (interaction.options.getSubcommand()) {
case 'set': case 'set':
console.log('budget command set');
let override = interaction.options.getBoolean('override') ?? false; let override = interaction.options.getBoolean('override') ?? false;
let budget = interaction.options.get('amount').value; let budget = interaction.options.get('amount').value;
db.prepare(`INSERT OR REPLACE INTO bot_config (id, weekly_budget) VALUES (1, ?)`).run(budget); db.prepare(`INSERT OR REPLACE INTO bot_config (id, weekly_budget) VALUES (1, ?)`).run(budget);
let overrideApplied = true;
if (override) { if (override) {
db.prepare(` const result = db.prepare(`
UPDATE weekly_budgets UPDATE weekly_budgets
SET budget_amount = ? SET budget_amount = ?
WHERE id = ( WHERE id = (
SELECT id FROM weekly_budgets SELECT id FROM weekly_budgets
WHERE date('now', 'localtime') BETWEEN start_date AND end_date WHERE date('now', 'localtime') BETWEEN start_date AND end_date
ORDER BY id DESC ORDER BY id DESC
LIMIT 1 LIMIT 1
)`).run(budget); )`).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; break;
case 'view': case 'view':
const lastWeek = interaction.options.getBoolean('lastweek') ?? false; const lastWeek = interaction.options.getBoolean('lastweek') ?? false;
const currentBudget = db.prepare(` const currentBudget = db.prepare(`
SELECT id, budget_amount, exchange_rate, start_date, end_date SELECT id, budget_amount, exchange_rate, start_date, end_date
FROM weekly_budgets FROM weekly_budgets
WHERE ${lastWeek ? "start_date < date('now', 'localtime')" : "date('now', 'localtime') BETWEEN start_date AND end_date"} WHERE ${lastWeek ? "end_date < date('now', 'localtime')" : "date('now', 'localtime') BETWEEN start_date AND end_date"}
ORDER BY start_date DESC ORDER BY start_date DESC, created_at DESC
LIMIT 1 LIMIT 1
`).get(); `).get();
@@ -123,11 +120,11 @@ module.exports = {
break break
case 'stats': case 'stats':
const allBudgets = db.prepare(` const allBudgets = db.prepare(`
SELECT id, budget_amount, exchange_rate, start_date, end_date SELECT id, budget_amount, exchange_rate, start_date, end_date
FROM weekly_budgets FROM weekly_budgets
ORDER BY start_date ASC ORDER BY start_date DESC
LIMIT 4 LIMIT 4
`).all(); `).all().reverse();
let statsDescription = ""; let statsDescription = "";
let cumulativeSavingsEur = 0; let cumulativeSavingsEur = 0;

6
core/constants.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
HOUSEHOLD_IDS: {
MINZ: '222457277708369928',
MIFFY: '372115788498468864',
},
};

View File

@@ -1,4 +1,5 @@
const { Events, channelLink, discordSort } = require('discord.js'); const { Events, channelLink, discordSort } = require('discord.js');
const { HOUSEHOLD_IDS } = require('../core/constants.js');
module.exports = { module.exports = {
name: Events.MessageCreate, name: Events.MessageCreate,
@@ -8,8 +9,8 @@ module.exports = {
const receiptsChannelId = process.env.NODE_ENV === 'development' ? '1468186493251227658' : '1462060674766344370'; const receiptsChannelId = process.env.NODE_ENV === 'development' ? '1468186493251227658' : '1462060674766344370';
if(message.channel.id === receiptsChannelId) { if(message.channel.id === receiptsChannelId) {
const CURRENCY_MAP = { const CURRENCY_MAP = {
"222457277708369928": "EUR", // Minz [HOUSEHOLD_IDS.MINZ]: "EUR",
"372115788498468864": "SEK", // Miffy [HOUSEHOLD_IDS.MIFFY]: "SEK",
}; };
let reply = ""; let reply = "";
@@ -25,12 +26,17 @@ module.exports = {
LIMIT 1 LIMIT 1
`).get(); `).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") { if (message.content === "fuck") {
let lastSpend = db.prepare(` let lastSpend = db.prepare(`
SELECT id, amount, message_raw, created_at SELECT id, amount, message_raw, created_at
FROM grocery_spendings FROM grocery_spendings
WHERE discord_id = ? AND budget_id = ? WHERE discord_id = ? AND budget_id = ? AND is_transfer = 0
ORDER BY created_at DESC, id DESC ORDER BY created_at DESC, id DESC
LIMIT 1 LIMIT 1
`).get(authorId, currentBudget.id); `).get(authorId, currentBudget.id);
if (lastSpend) { if (lastSpend) {
@@ -45,7 +51,7 @@ module.exports = {
const transferRegex = /^transfer\s+(\d+(?:[.,]\d+)?)$/i; const transferRegex = /^transfer\s+(\d+(?:[.,]\d+)?)$/i;
const transferMatch = message.content.match(transferRegex); const transferMatch = message.content.match(transferRegex);
if (transferMatch) { 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."); await message.reply("Only Minz and Miffy can use the transfer command.");
return; return;
} }
@@ -53,11 +59,11 @@ module.exports = {
let amount = parseFloat(transferMatch[1].replace(',', '.')); 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(` const insertSpending = db.prepare(`
INSERT INTO grocery_spendings (discord_id, message_id, message_raw, amount, budget_id) INSERT INTO grocery_spendings (discord_id, message_id, message_raw, amount, budget_id, is_transfer)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, 1)
`); `);
const runTransfer = db.transaction(() => { const runTransfer = db.transaction(() => {
@@ -67,18 +73,17 @@ module.exports = {
runTransfer(); runTransfer();
const targetName = authorId === "222457277708369928" ? "Miffy" : "Minz"; const targetName = authorId === HOUSEHOLD_IDS.MINZ ? "Miffy" : "Minz";
await message.reply(`Transferred ${amount.toFixed(2)}€ to ${targetName}.`); await message.reply(`Transferred ${amount.toFixed(2)}€ to ${targetName}.`);
return; return;
} }
let matches = [];
let lines = message.content.split("\n"); let lines = message.content.split("\n");
let additionalSpent = 0.0; let additionalSpent = 0.0;
lines.forEach(line => {0 lines.forEach(line => {
if (line.startsWith('-')) { if (line.startsWith('-')) {
console.log(`Matching on line ${line}`); 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) let match = regex.exec(line)
console.log(match); console.log(match);
if (match) { if (match) {

View File

@@ -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;`);
}
};

View File

@@ -1,7 +1,9 @@
const { formatDate } = require('../core/utils.js'); const { formatDate } = require('../core/utils.js');
module.exports = { 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, immediate: true,
name: 'Receipt Day Announcements', name: 'Receipt Day Announcements',
data: { data: {
@@ -26,30 +28,24 @@ module.exports = {
let db = await client.localDB; let db = await client.localDB;
const channel = await client.channels.fetch(timer.data.channelId); 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); const existingBudget = db.prepare(`SELECT id FROM weekly_budgets WHERE start_date = ?`).get(startDate);
if (!existingBudget) { 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}.`); 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`); const response = await fetch(`https://api.frankfurter.dev/v1/latest?amount=1&from=EUR&to=SEK`);
if (!response.ok) {
if (!response.ok) await channel.send(`Failed to fetch exchange rate:\n${response.statusText}`); await channel.send(`Failed to fetch exchange rate:\n${response.statusText}`);
return;
}
const data = await response.json(); 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`); await channel.send(`Reset weekly budget to ${config.weekly_budget.toFixed(2)}€ / ${(config.weekly_budget * data.rates.SEK).toFixed(2)} kr`);
db.prepare(` db.prepare(`
INSERT INTO weekly_budgets (start_date, end_date, budget_amount, exchange_rate) INSERT INTO weekly_budgets (start_date, end_date, budget_amount, exchange_rate)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
`).run(startDate, endDate, config.weekly_budget, data.rates.SEK); `).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 { } else {
console.log(`budget found for week starting ${startDate}.`); console.log(`budget found for week starting ${startDate}.`);
} }
@@ -61,25 +57,7 @@ module.exports = {
) as was_sent_today ) as was_sent_today
`).get(); `).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) { 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', { const currentDate = new Date().toLocaleDateString('en-GB', {
weekday: 'long', weekday: 'long',
day: '2-digit', day: '2-digit',