Compare commits
3 Commits
fix/json-i
...
feature/an
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dc0e48348 | |||
| 027fbedb8c | |||
| a1e93a7c1f |
@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [Frontend 0.11.2 / Backend 0.10.5 / Shared 0.5.1] - 2026-08-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Show cashback separately in the cash-flow summary alongside interest income.
|
||||||
|
|
||||||
## [Backend 0.10.3] - 2026-08-24
|
## [Backend 0.10.3] - 2026-08-24
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@family-budget/backend",
|
"name": "@family-budget/backend",
|
||||||
"version": "0.10.4",
|
"version": "0.10.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/app.ts",
|
"dev": "tsx watch src/app.ts",
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ async function testQueries(): Promise<void> {
|
|||||||
"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)",
|
"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],
|
[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(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 insert(accountId, '2026-08-01T12:00:00+03:00', -7_000, categoryId.expense, 'analytics-test-7');
|
||||||
await client.query(
|
await client.query(
|
||||||
@@ -35,7 +39,7 @@ async function testQueries(): Promise<void> {
|
|||||||
|
|
||||||
const params = { from: '2026-07-01', to: '2026-07-31', accountId, onlyConfirmed: true };
|
const params = { from: '2026-07-01', to: '2026-07-31', accountId, onlyConfirmed: true };
|
||||||
const summary = await getSummary(params, client);
|
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);
|
const categorySummary = await getSummary({ ...params, categoryId: categoryId.expense }, client);
|
||||||
assert.deepEqual({ expense: categorySummary.totalExpense, income: categorySummary.totalIncome }, { expense: 6_000, income: 0 });
|
assert.deepEqual({ expense: categorySummary.totalExpense, income: categorySummary.totalIncome }, { expense: 6_000, income: 0 });
|
||||||
const uncategorized = await getSummary({ ...params, categoryId: 0, onlyConfirmed: false }, client);
|
const uncategorized = await getSummary({ ...params, categoryId: 0, onlyConfirmed: false }, client);
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ function analyticsTransactions(where: string): string {
|
|||||||
CASE WHEN a.account_type = 'savings'
|
CASE WHEN a.account_type = 'savings'
|
||||||
AND ${effectiveAmount} > 0
|
AND ${effectiveAmount} > 0
|
||||||
AND (t.description ILIKE '%процент%' OR t.description ILIKE '%выплата %' OR t.description LIKE '%\%%' ESCAPE '\\')
|
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
|
FROM transactions t
|
||||||
LEFT JOIN categories c ON c.id = t.category_id
|
LEFT JOIN categories c ON c.id = t.category_id
|
||||||
LEFT JOIN accounts a ON a.id = t.account_id
|
LEFT JOIN accounts a ON a.id = t.account_id
|
||||||
@@ -87,7 +91,8 @@ export async function getSummary(
|
|||||||
`${analyticsTransactions(where)},
|
`${analyticsTransactions(where)},
|
||||||
category_net AS (
|
category_net AS (
|
||||||
SELECT category_id, category_name, analytic_type, SUM(effective_amount)::bigint AS amount,
|
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
|
FROM analytics_transactions
|
||||||
GROUP BY category_id, category_name, analytic_type
|
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), 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_inflow,
|
||||||
COALESCE((SELECT SUM(GREATEST(-effective_amount, 0)) FROM analytics_transactions WHERE analytic_type = 'transfer'), 0)::bigint AS transfer_outflow,
|
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`,
|
FROM category_net`,
|
||||||
values,
|
values,
|
||||||
);
|
);
|
||||||
@@ -110,6 +116,7 @@ export async function getSummary(
|
|||||||
const transferInflow = Number(totalsResult.rows[0].transfer_inflow);
|
const transferInflow = Number(totalsResult.rows[0].transfer_inflow);
|
||||||
const transferOutflow = Number(totalsResult.rows[0].transfer_outflow);
|
const transferOutflow = Number(totalsResult.rows[0].transfer_outflow);
|
||||||
const interestIncome = Number(totalsResult.rows[0].interest_income);
|
const interestIncome = Number(totalsResult.rows[0].interest_income);
|
||||||
|
const cashbackIncome = Number(totalsResult.rows[0].cashback_income);
|
||||||
|
|
||||||
const topResult = await db.query(
|
const topResult = await db.query(
|
||||||
`${analyticsTransactions(where)}
|
`${analyticsTransactions(where)}
|
||||||
@@ -140,6 +147,7 @@ export async function getSummary(
|
|||||||
transferOutflow,
|
transferOutflow,
|
||||||
cashNet: cashInflow - cashOutflow,
|
cashNet: cashInflow - cashOutflow,
|
||||||
interestIncome,
|
interestIncome,
|
||||||
|
cashbackIncome,
|
||||||
topCategories,
|
topCategories,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@family-budget/frontend",
|
"name": "@family-budget/frontend",
|
||||||
"version": "0.11.1",
|
"version": "0.11.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export function SummaryCards({ summary }: Props) {
|
|||||||
<div className="summary__subvalue">Поступило: {formatAmount(summary.cashInflow)}</div>
|
<div className="summary__subvalue">Поступило: {formatAmount(summary.cashInflow)}</div>
|
||||||
<div className="summary__subvalue">Списано: {formatAmount(summary.cashOutflow)}</div>
|
<div className="summary__subvalue">Списано: {formatAmount(summary.cashOutflow)}</div>
|
||||||
<div className="summary__subvalue">Доход от процентов: {formatAmount(summary.interestIncome)}</div>
|
<div className="summary__subvalue">Доход от процентов: {formatAmount(summary.interestIncome)}</div>
|
||||||
|
<div className="summary__subvalue">Кэшбек: {formatAmount(summary.cashbackIncome)}</div>
|
||||||
{(summary.transferInflow > 0 || summary.transferOutflow > 0) && (
|
{(summary.transferInflow > 0 || summary.transferOutflow > 0) && (
|
||||||
<div className="summary__subvalue">
|
<div className="summary__subvalue">
|
||||||
Переводы: {formatAmount(summary.transferInflow)} / {formatAmount(summary.transferOutflow)}
|
Переводы: {formatAmount(summary.transferInflow)} / {formatAmount(summary.transferOutflow)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@family-budget/shared",
|
"name": "@family-budget/shared",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export interface AnalyticsSummaryResponse {
|
|||||||
transferOutflow: number;
|
transferOutflow: number;
|
||||||
cashNet: number;
|
cashNet: number;
|
||||||
interestIncome: number;
|
interestIncome: number;
|
||||||
|
cashbackIncome: number;
|
||||||
topCategories: TopCategory[];
|
topCategories: TopCategory[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user