115 lines
3.5 KiB
JavaScript
Executable File
115 lines
3.5 KiB
JavaScript
Executable File
const express = require('express');
|
|
const { getDb } = require('../db');
|
|
const {
|
|
createUserNameResolver,
|
|
normalizeAmount,
|
|
roundCurrency,
|
|
} = require('../utils/accounting');
|
|
|
|
const router = express.Router();
|
|
|
|
function getUsers(db) {
|
|
return db.all('SELECT name FROM users ORDER BY sort_order, id');
|
|
}
|
|
|
|
function buildBalancePayload(db) {
|
|
const totalIncome = db.get("SELECT COALESCE(SUM(amount), 0) AS total FROM records WHERE type = 'income'");
|
|
const totalExpense = db.get("SELECT COALESCE(SUM(amount), 0) AS total FROM records WHERE type = 'expense'");
|
|
const income = roundCurrency(totalIncome?.total);
|
|
const expense = roundCurrency(totalExpense?.total);
|
|
|
|
const userBalances = getUsers(db).map((user) => {
|
|
const balanceSetting = db.get(
|
|
'SELECT initial_balance FROM user_balance_settings WHERE user_name = ?',
|
|
user.name
|
|
);
|
|
const userIncome = db.get(
|
|
"SELECT COALESCE(SUM(amount), 0) AS total FROM records WHERE type = 'income' AND user = ?",
|
|
user.name
|
|
);
|
|
const userExpense = db.get(
|
|
"SELECT COALESCE(SUM(amount), 0) AS total FROM records WHERE type = 'expense' AND user = ?",
|
|
user.name
|
|
);
|
|
const initialBalance = roundCurrency(balanceSetting?.initial_balance);
|
|
const resolvedIncome = roundCurrency(userIncome?.total);
|
|
const resolvedExpense = roundCurrency(userExpense?.total);
|
|
|
|
return {
|
|
user: user.name,
|
|
initial_balance: initialBalance,
|
|
income: resolvedIncome,
|
|
expense: resolvedExpense,
|
|
balance: roundCurrency(initialBalance + resolvedIncome - resolvedExpense),
|
|
};
|
|
});
|
|
|
|
const totalInitialBalance = roundCurrency(
|
|
userBalances.reduce((sum, item) => sum + item.initial_balance, 0)
|
|
);
|
|
|
|
return {
|
|
initial_balance: totalInitialBalance,
|
|
total_income: income,
|
|
total_expense: expense,
|
|
current_balance: roundCurrency(totalInitialBalance + income - expense),
|
|
userBalances,
|
|
};
|
|
}
|
|
|
|
router.get('/', (req, res) => {
|
|
res.json(buildBalancePayload(getDb()));
|
|
});
|
|
|
|
router.put('/', (req, res) => {
|
|
const db = getDb();
|
|
const initialBalance = normalizeAmount(req.body.initial_balance);
|
|
|
|
if (initialBalance === null) {
|
|
return res.status(400).json({ error: '请提供有效的初始余额' });
|
|
}
|
|
|
|
db.run(
|
|
'UPDATE balance_settings SET initial_balance = ?, updated_at = CURRENT_TIMESTAMP WHERE id = 1',
|
|
initialBalance
|
|
);
|
|
|
|
res.json(buildBalancePayload(db));
|
|
});
|
|
|
|
router.put('/user/:userName', (req, res) => {
|
|
const db = getDb();
|
|
const users = getUsers(db);
|
|
const resolveUserName = createUserNameResolver(users);
|
|
const userName = resolveUserName(req.params.userName, '');
|
|
const initialBalance = normalizeAmount(req.body.initial_balance);
|
|
|
|
if (!userName || !users.some((user) => user.name === userName)) {
|
|
return res.status(404).json({ error: '用户不存在' });
|
|
}
|
|
|
|
if (initialBalance === null) {
|
|
return res.status(400).json({ error: '请提供有效的初始余额' });
|
|
}
|
|
|
|
const existing = db.get('SELECT * FROM user_balance_settings WHERE user_name = ?', userName);
|
|
if (existing) {
|
|
db.run(
|
|
'UPDATE user_balance_settings SET initial_balance = ?, updated_at = CURRENT_TIMESTAMP WHERE user_name = ?',
|
|
initialBalance,
|
|
userName
|
|
);
|
|
} else {
|
|
db.run(
|
|
'INSERT INTO user_balance_settings (user_name, initial_balance) VALUES (?, ?)',
|
|
userName,
|
|
initialBalance
|
|
);
|
|
}
|
|
|
|
const payload = buildBalancePayload(db).userBalances.find((item) => item.user === userName);
|
|
res.json(payload);
|
|
});
|
|
|
|
module.exports = router;
|