diff --git a/backend/package.json b/backend/package.json index b401d79..29d7e43 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "@family-budget/backend", - "version": "0.10.4", + "version": "0.10.5", "private": true, "scripts": { "dev": "tsx watch src/app.ts", diff --git a/backend/src/services/analytics.integration.test.ts b/backend/src/services/analytics.integration.test.ts index 7c4e3aa..704eb8f 100644 --- a/backend/src/services/analytics.integration.test.ts +++ b/backend/src/services/analytics.integration.test.ts @@ -26,6 +26,10 @@ async function testQueries(): Promise { "INSERT INTO transactions (account_id, operation_at, amount_signed, commission, description, direction, fingerprint, category_id, is_category_confirmed) VALUES ($1, '2026-07-06T12:00:00+03:00', 1_500, 0, 'Начисление процентов', 'transfer', 'analytics-test-interest', $2, TRUE)", [accountId, categoryId.transfer], ); + await client.query( + "INSERT INTO transactions (account_id, operation_at, amount_signed, commission, description, direction, fingerprint, category_id, is_category_confirmed) VALUES ($1, '2026-07-06T12:00:00+03:00', 0, 200, 'Зачисление кэшбека', 'income', 'analytics-test-cashback', $2, TRUE)", + [accountId, categoryId.income], + ); await insert(otherAccountId, '2026-07-01T12:00:00+03:00', -99_000, categoryId.expense, 'analytics-test-6'); await insert(accountId, '2026-08-01T12:00:00+03:00', -7_000, categoryId.expense, 'analytics-test-7'); await client.query( @@ -35,7 +39,7 @@ async function testQueries(): Promise { const params = { from: '2026-07-01', to: '2026-07-31', accountId, onlyConfirmed: true }; const summary = await getSummary(params, client); - assert.deepEqual({ expense: summary.totalExpense, income: summary.totalIncome, net: summary.net, transferOut: summary.transferOutflow, interest: summary.interestIncome }, { expense: 6_000, income: 15_000, net: 9_000, transferOut: 3_000, interest: 1_500 }); + assert.deepEqual({ expense: summary.totalExpense, income: summary.totalIncome, net: summary.net, transferOut: summary.transferOutflow, interest: summary.interestIncome, cashback: summary.cashbackIncome }, { expense: 6_000, income: 15_200, net: 9_200, transferOut: 3_000, interest: 1_500, cashback: 200 }); const categorySummary = await getSummary({ ...params, categoryId: categoryId.expense }, client); assert.deepEqual({ expense: categorySummary.totalExpense, income: categorySummary.totalIncome }, { expense: 6_000, income: 0 }); const uncategorized = await getSummary({ ...params, categoryId: 0, onlyConfirmed: false }, client); diff --git a/backend/src/services/analytics.ts b/backend/src/services/analytics.ts index a34dc75..ad7eefe 100644 --- a/backend/src/services/analytics.ts +++ b/backend/src/services/analytics.ts @@ -33,7 +33,11 @@ function analyticsTransactions(where: string): string { CASE WHEN a.account_type = 'savings' AND ${effectiveAmount} > 0 AND (t.description ILIKE '%процент%' OR t.description ILIKE '%выплата %' OR t.description LIKE '%\%%' ESCAPE '\\') - THEN ${effectiveAmount} ELSE 0 END AS interest_income + THEN ${effectiveAmount} ELSE 0 END AS interest_income, + CASE WHEN t.amount_signed = 0 + AND t.commission > 0 + AND t.description ILIKE '%зачисление%' + THEN t.commission ELSE 0 END AS cashback_income FROM transactions t LEFT JOIN categories c ON c.id = t.category_id LEFT JOIN accounts a ON a.id = t.account_id @@ -87,7 +91,8 @@ export async function getSummary( `${analyticsTransactions(where)}, category_net AS ( SELECT category_id, category_name, analytic_type, SUM(effective_amount)::bigint AS amount, - SUM(interest_income)::bigint AS interest_income + SUM(interest_income)::bigint AS interest_income, + SUM(cashback_income)::bigint AS cashback_income FROM analytics_transactions GROUP BY category_id, category_name, analytic_type ) @@ -98,7 +103,8 @@ export async function getSummary( COALESCE((SELECT SUM(GREATEST(-effective_amount, 0)) FROM analytics_transactions), 0)::bigint AS cash_outflow, COALESCE((SELECT SUM(GREATEST(effective_amount, 0)) FROM analytics_transactions WHERE analytic_type = 'transfer'), 0)::bigint AS transfer_inflow, COALESCE((SELECT SUM(GREATEST(-effective_amount, 0)) FROM analytics_transactions WHERE analytic_type = 'transfer'), 0)::bigint AS transfer_outflow, - COALESCE(SUM(interest_income), 0)::bigint AS interest_income + COALESCE(SUM(interest_income), 0)::bigint AS interest_income, + COALESCE(SUM(cashback_income), 0)::bigint AS cashback_income FROM category_net`, values, ); @@ -110,6 +116,7 @@ export async function getSummary( const transferInflow = Number(totalsResult.rows[0].transfer_inflow); const transferOutflow = Number(totalsResult.rows[0].transfer_outflow); const interestIncome = Number(totalsResult.rows[0].interest_income); + const cashbackIncome = Number(totalsResult.rows[0].cashback_income); const topResult = await db.query( `${analyticsTransactions(where)} @@ -140,6 +147,7 @@ export async function getSummary( transferOutflow, cashNet: cashInflow - cashOutflow, interestIncome, + cashbackIncome, topCategories, }; } diff --git a/frontend/package.json b/frontend/package.json index 5bc25f4..148723d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@family-budget/frontend", - "version": "0.11.1", + "version": "0.11.2", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/components/SummaryCards.tsx b/frontend/src/components/SummaryCards.tsx index 8bb9b2f..4cf98af 100644 --- a/frontend/src/components/SummaryCards.tsx +++ b/frontend/src/components/SummaryCards.tsx @@ -41,6 +41,7 @@ export function SummaryCards({ summary }: Props) {
Поступило: {formatAmount(summary.cashInflow)}
Списано: {formatAmount(summary.cashOutflow)}
Доход от процентов: {formatAmount(summary.interestIncome)}
+
Кэшбек: {formatAmount(summary.cashbackIncome)}
{(summary.transferInflow > 0 || summary.transferOutflow > 0) && (
Переводы: {formatAmount(summary.transferInflow)} / {formatAmount(summary.transferOutflow)} diff --git a/shared/package.json b/shared/package.json index d3fd683..a280663 100644 --- a/shared/package.json +++ b/shared/package.json @@ -1,6 +1,6 @@ { "name": "@family-budget/shared", - "version": "0.5.0", + "version": "0.5.1", "private": true, "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/shared/src/types/analytics.ts b/shared/src/types/analytics.ts index 8f35548..1329080 100644 --- a/shared/src/types/analytics.ts +++ b/shared/src/types/analytics.ts @@ -25,6 +25,7 @@ export interface AnalyticsSummaryResponse { transferOutflow: number; cashNet: number; interestIncome: number; + cashbackIncome: number; topCategories: TopCategory[]; }