From 8bfdcc7d529fe4d21932392b209b3ddd0f6cf7ba Mon Sep 17 00:00:00 2001 From: NotARoomba Date: Wed, 4 Mar 2026 13:53:35 -0500 Subject: [PATCH] aaa --- backend/dist/index.js | 24 ++++++++++++++++++------ backend/src/lib/airtable-sync.ts | 14 +++++++++++++- backend/src/lib/scraps.ts | 18 +++++++++++------- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/backend/dist/index.js b/backend/dist/index.js index 66f3ca5..cf17002 100644 --- a/backend/dist/index.js +++ b/backend/dist/index.js @@ -32628,17 +32628,17 @@ function calculateScrapsFromHours(hours, tier = 1) { } async function getUserScrapsBalance(userId, txOrDb = db) { const earnedResult = await txOrDb.select({ - total: sql`COALESCE(SUM(${projectsTable.scrapsPaidAmount}), 0)` - }).from(projectsTable).where(sql`${projectsTable.userId} = ${userId} AND ${projectsTable.scrapsPaidAmount} > 0`); + total: sql`COALESCE(SUM(CASE WHEN ${projectsTable.scrapsPaidAmount} > 0 THEN ${projectsTable.scrapsPaidAmount} ELSE ${projectsTable.scrapsAwarded} END), 0)` + }).from(projectsTable).where(sql`${projectsTable.userId} = ${userId} AND ${projectsTable.scrapsPaidAt} IS NOT NULL AND ${projectsTable.scrapsAwarded} > 0`); const pendingResult = await txOrDb.select({ - total: sql`COALESCE(SUM(${projectsTable.scrapsAwarded} - ${projectsTable.scrapsPaidAmount}), 0)` - }).from(projectsTable).where(sql`${projectsTable.userId} = ${userId} AND ${projectsTable.status} = 'shipped' AND (${projectsTable.deleted} = 0 OR ${projectsTable.deleted} IS NULL) AND ${projectsTable.scrapsPaidAt} IS NULL AND ${projectsTable.scrapsAwarded} > ${projectsTable.scrapsPaidAmount}`); + total: sql`COALESCE(SUM(${projectsTable.scrapsAwarded} - CASE WHEN ${projectsTable.scrapsPaidAmount} > 0 THEN ${projectsTable.scrapsPaidAmount} ELSE 0 END), 0)` + }).from(projectsTable).where(sql`${projectsTable.userId} = ${userId} AND ${projectsTable.status} = 'shipped' AND (${projectsTable.deleted} = 0 OR ${projectsTable.deleted} IS NULL) AND ${projectsTable.scrapsPaidAt} IS NULL AND ${projectsTable.scrapsAwarded} > 0`); const bonusResult = await txOrDb.select({ total: sql`COALESCE(SUM(${userBonusesTable.amount}), 0)` }).from(userBonusesTable).where(eq(userBonusesTable.userId, userId)); const spentResult = await txOrDb.select({ total: sql`COALESCE(SUM(${shopOrdersTable.totalPrice}), 0)` - }).from(shopOrdersTable).where(eq(shopOrdersTable.userId, userId)); + }).from(shopOrdersTable).where(sql`${shopOrdersTable.userId} = ${userId} AND ${shopOrdersTable.status} NOT IN ('cancelled', 'deleted')`); const upgradeSpentResult = await txOrDb.select({ total: sql`COALESCE(SUM(${refinerySpendingHistoryTable.cost}), 0)` }).from(refinerySpendingHistoryTable).where(eq(refinerySpendingHistoryTable.userId, userId)); @@ -34339,7 +34339,19 @@ function formatHoursMinutes(hours) { } function buildJustification(project, reviews, effectiveHours) { const lines = []; - lines.push(`The user logged ${formatHoursMinutes(effectiveHours)} on hackatime.`); + const rawHours = project.hours ?? 0; + if (project.hoursOverride !== null && project.hoursOverride !== rawHours) { + lines.push(`The user logged ${formatHoursMinutes(rawHours)} on hackatime.`); + lines.push(`Hours were overridden to ${formatHoursMinutes(project.hoursOverride)} by a reviewer.`); + if (effectiveHours !== project.hoursOverride) { + lines.push(`After deducting overlapping projects, effective hours: ${formatHoursMinutes(effectiveHours)}.`); + } + } else if (effectiveHours !== rawHours) { + lines.push(`The user logged ${formatHoursMinutes(rawHours)} on hackatime.`); + lines.push(`After deducting overlapping projects, effective hours: ${formatHoursMinutes(effectiveHours)}.`); + } else { + lines.push(`The user logged ${formatHoursMinutes(effectiveHours)} on hackatime.`); + } lines.push(""); lines.push(`The scraps project can be found at ${config.frontendUrl}/projects/${project.id}`); if (reviews.length > 0) { diff --git a/backend/src/lib/airtable-sync.ts b/backend/src/lib/airtable-sync.ts index 74f5ecc..d7850c1 100644 --- a/backend/src/lib/airtable-sync.ts +++ b/backend/src/lib/airtable-sync.ts @@ -44,8 +44,20 @@ function buildJustification(project: { createdAt: Date }[], effectiveHours: number): string { const lines: string[] = [] + const rawHours = project.hours ?? 0 - lines.push(`The user logged ${formatHoursMinutes(effectiveHours)} on hackatime.`) + if (project.hoursOverride !== null && project.hoursOverride !== rawHours) { + lines.push(`The user logged ${formatHoursMinutes(rawHours)} on hackatime.`) + lines.push(`Hours were overridden to ${formatHoursMinutes(project.hoursOverride)} by a reviewer.`) + if (effectiveHours !== project.hoursOverride) { + lines.push(`After deducting overlapping projects, effective hours: ${formatHoursMinutes(effectiveHours)}.`) + } + } else if (effectiveHours !== rawHours) { + lines.push(`The user logged ${formatHoursMinutes(rawHours)} on hackatime.`) + lines.push(`After deducting overlapping projects, effective hours: ${formatHoursMinutes(effectiveHours)}.`) + } else { + lines.push(`The user logged ${formatHoursMinutes(effectiveHours)} on hackatime.`) + } lines.push('') lines.push(`The scraps project can be found at ${config.frontendUrl}/projects/${project.id}`) diff --git a/backend/src/lib/scraps.ts b/backend/src/lib/scraps.ts index 6a07a55..9f2fcab 100644 --- a/backend/src/lib/scraps.ts +++ b/backend/src/lib/scraps.ts @@ -154,24 +154,26 @@ export async function getUserScrapsBalance( spent: number; balance: number; }> { - // Earned scraps: sum of scrapsPaidAmount (what has actually been paid out per project) + // Earned scraps: sum of paid amounts for projects that have been paid out + // Use scrapsPaidAmount if set, otherwise fall back to scrapsAwarded for legacy projects + // that were paid before scrapsPaidAmount was introduced const earnedResult = await txOrDb .select({ - total: sql`COALESCE(SUM(${projectsTable.scrapsPaidAmount}), 0)`, + total: sql`COALESCE(SUM(CASE WHEN ${projectsTable.scrapsPaidAmount} > 0 THEN ${projectsTable.scrapsPaidAmount} ELSE ${projectsTable.scrapsAwarded} END), 0)`, }) .from(projectsTable) .where( - sql`${projectsTable.userId} = ${userId} AND ${projectsTable.scrapsPaidAmount} > 0`, + sql`${projectsTable.userId} = ${userId} AND ${projectsTable.scrapsPaidAt} IS NOT NULL AND ${projectsTable.scrapsAwarded} > 0`, ); - // Pending scraps: the delta between scrapsAwarded and scrapsPaidAmount for shipped unpaid projects + // Pending scraps: scrapsAwarded for shipped unpaid projects const pendingResult = await txOrDb .select({ - total: sql`COALESCE(SUM(${projectsTable.scrapsAwarded} - ${projectsTable.scrapsPaidAmount}), 0)`, + total: sql`COALESCE(SUM(${projectsTable.scrapsAwarded} - CASE WHEN ${projectsTable.scrapsPaidAmount} > 0 THEN ${projectsTable.scrapsPaidAmount} ELSE 0 END), 0)`, }) .from(projectsTable) .where( - sql`${projectsTable.userId} = ${userId} AND ${projectsTable.status} = 'shipped' AND (${projectsTable.deleted} = 0 OR ${projectsTable.deleted} IS NULL) AND ${projectsTable.scrapsPaidAt} IS NULL AND ${projectsTable.scrapsAwarded} > ${projectsTable.scrapsPaidAmount}`, + sql`${projectsTable.userId} = ${userId} AND ${projectsTable.status} = 'shipped' AND (${projectsTable.deleted} = 0 OR ${projectsTable.deleted} IS NULL) AND ${projectsTable.scrapsPaidAt} IS NULL AND ${projectsTable.scrapsAwarded} > 0`, ); const bonusResult = await txOrDb @@ -186,7 +188,9 @@ export async function getUserScrapsBalance( total: sql`COALESCE(SUM(${shopOrdersTable.totalPrice}), 0)`, }) .from(shopOrdersTable) - .where(eq(shopOrdersTable.userId, userId)); + .where( + sql`${shopOrdersTable.userId} = ${userId} AND ${shopOrdersTable.status} NOT IN ('cancelled', 'deleted')`, + ); // Calculate scraps spent on refinery upgrades (permanent history, only deleted on undo) const upgradeSpentResult = await txOrDb