|
|
|
|
|
|
|
async function loadMembersForAccounting() {
|
|
try {
|
|
const res = await fetch('/api/members');
|
|
if (!res.ok) throw new Error('Failed to fetch members');
|
|
|
|
const members = await res.json();
|
|
const select = document.getElementById('memberSelect');
|
|
select.innerHTML = '<option value="">Select Member</option>';
|
|
|
|
members.forEach(m => {
|
|
const opt = document.createElement('option');
|
|
opt.value = m.id;
|
|
opt.textContent = `${m.name} (#${m.id})`;
|
|
select.appendChild(opt);
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('payerType').addEventListener('change', (e) => {
|
|
const isNonMember = e.target.value === 'non-member';
|
|
document.getElementById('payerName').style.display = isNonMember ? 'inline-block' : 'none';
|
|
});
|
|
|
|
|
|
|
|
|
|
async function loadTransactions() {
|
|
try {
|
|
const res = await fetch('/api/accounting');
|
|
if (!res.ok) throw new Error('Failed to fetch transactions');
|
|
|
|
let transactions = await res.json();
|
|
|
|
|
|
if (filters?.start || filters?.end) {
|
|
transactions = transactions.filter(tx => {
|
|
const txDate = new Date(tx.month + "-01");
|
|
const start = filters.start ? new Date(filters.start + "-01") : null;
|
|
const end = filters.end ? new Date(filters.end + "-01") : null;
|
|
return (!start || txDate >= start) && (!end || txDate <= end);
|
|
});
|
|
}
|
|
|
|
const tbody = document.getElementById('accountingBody');
|
|
tbody.innerHTML = transactions.map((t, i) => `
|
|
<tr class="${t.category === 'expense' ? 'expense' : 'income'}">
|
|
<td>${i + 1}</td>
|
|
<td>${t.payerType === 'member' ? t.memberId : t.payerName}</td>
|
|
<td>${t.payerType}</td>
|
|
<td>${t.month}</td>
|
|
<td>${t.amount}</td>
|
|
<td>${t.category}</td>
|
|
<td>${t.description || ''}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-primary" onclick="openActionModal(${t.id})">Actions</button>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
|
|
updateAccountingSummary(transactions);
|
|
renderMonthlyChart(transactions);
|
|
renderIncomeExpenseChart(transactions);
|
|
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
async function updateAccountingSummary(transactionsParam = null) {
|
|
try {
|
|
const transactions = transactionsParam || await (await fetch('/api/accounting')).json();
|
|
|
|
const summary = {
|
|
contribution: 0,
|
|
donation: 0,
|
|
sale: 0,
|
|
expense: 0,
|
|
memberIncome: 0,
|
|
nonMemberIncome: 0
|
|
};
|
|
|
|
transactions.forEach(tx => {
|
|
if (tx.category === 'contribution') summary.contribution += tx.amount;
|
|
else if (tx.category === 'donation') summary.donation += tx.amount;
|
|
else if (tx.category === 'sale') summary.sale += tx.amount;
|
|
else if (tx.category === 'expense') summary.expense += tx.amount;
|
|
|
|
if (tx.payerType === 'member' && tx.category !== 'expense') summary.memberIncome += tx.amount;
|
|
if (tx.payerType === 'non-member' && tx.category !== 'expense') summary.nonMemberIncome += tx.amount;
|
|
});
|
|
|
|
const tbody = document.getElementById('summaryBody');
|
|
tbody.innerHTML = `
|
|
<tr class="income-summary"><td>Contribution (Income)</td><td>${summary.contribution}</td></tr>
|
|
<tr class="income-summary"><td>Donation (Income)</td><td>${summary.donation}</td></tr>
|
|
<tr class="income-summary"><td>Sales (Income)</td><td>${summary.sale}</td></tr>
|
|
<tr class="income-summary"><td>Total Income from Members</td><td>${summary.memberIncome}</td></tr>
|
|
<tr class="income-summary"><td>Total Income from Non-Members</td><td>${summary.nonMemberIncome}</td></tr>
|
|
<tr class="expense-summary"><td>Total Expenses</td><td>${summary.expense}</td></tr>
|
|
<tr class="income-summary"><td>Net Total</td><td>${(summary.contribution + summary.donation + summary.sale) - summary.expense}</td></tr>
|
|
`;
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById('transactionForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const category = document.getElementById('transactionCategory').value;
|
|
|
|
const transaction = {
|
|
payerType: document.getElementById('payerType').value,
|
|
memberId: document.getElementById('memberSelect').value || null,
|
|
payerName: document.getElementById('payerName').value || null,
|
|
month: document.getElementById('transactionMonth').value,
|
|
amount: parseFloat(document.getElementById('transactionAmount').value),
|
|
category,
|
|
type: category === 'expense' ? 'expense' : 'income',
|
|
description: document.getElementById('transactionDesc').value.trim()
|
|
};
|
|
|
|
try {
|
|
const res = await fetch('/api/accounting', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(transaction)
|
|
});
|
|
|
|
if (!res.ok) throw new Error('Failed to add transaction');
|
|
|
|
document.getElementById('transactionForm').reset();
|
|
loadTransactions();
|
|
updateAccountingSummary();
|
|
renderMonthlyChart();
|
|
|
|
} catch (error) {
|
|
console.error(error);
|
|
alert('Failed to save transaction');
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
document.getElementById('exportAccounting').addEventListener('click', () => {
|
|
window.location.href = '/api/accounting/export';
|
|
});
|
|
|
|
|
|
|
|
|
|
loadMembersForAccounting();
|
|
loadTransactions();
|
|
updateAccountingSummary();
|
|
|
|
|
|
|
|
let selectedTransaction = null;
|
|
|
|
|
|
window.openActionModal = async function (id) {
|
|
const res = await fetch('/api/accounting');
|
|
const transactions = await res.json();
|
|
selectedTransaction = transactions.find(t => t.id === id);
|
|
if (!selectedTransaction) return alert('Transaction not found');
|
|
|
|
|
|
document.getElementById('editTransactionId').value = selectedTransaction.id;
|
|
document.getElementById('editTransactionMonth').value = selectedTransaction.month;
|
|
document.getElementById('editTransactionAmount').value = selectedTransaction.amount;
|
|
document.getElementById('editTransactionCategory').value = selectedTransaction.category;
|
|
document.getElementById('editTransactionDesc').value = selectedTransaction.description || '';
|
|
|
|
|
|
document.getElementById('transactionModal').classList.remove('hidden');
|
|
};
|
|
|
|
window.closeModal = function () {
|
|
document.getElementById('transactionModal').classList.add('hidden');
|
|
selectedTransaction = null;
|
|
};
|
|
|
|
|
|
document.getElementById('editTransactionForm').addEventListener('submit', async function (e) {
|
|
e.preventDefault();
|
|
const id = document.getElementById('editTransactionId').value;
|
|
|
|
const updatedTx = {
|
|
...selectedTransaction,
|
|
month: document.getElementById('editTransactionMonth').value,
|
|
amount: parseFloat(document.getElementById('editTransactionAmount').value),
|
|
category: document.getElementById('editTransactionCategory').value,
|
|
description: document.getElementById('editTransactionDesc').value
|
|
};
|
|
|
|
const res = await fetch(`/api/accounting/${id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(updatedTx)
|
|
});
|
|
|
|
if (!res.ok) {
|
|
alert('Failed to update transaction');
|
|
return;
|
|
}
|
|
|
|
closeModal();
|
|
loadTransactions();
|
|
});
|
|
|
|
|
|
window.deleteTransaction = async function () {
|
|
const id = document.getElementById('editTransactionId').value;
|
|
|
|
if (!confirm('Are you sure you want to delete this transaction?')) return;
|
|
|
|
const res = await fetch(`/api/accounting/${id}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (!res.ok) {
|
|
alert('Failed to delete transaction');
|
|
return;
|
|
}
|
|
|
|
closeModal();
|
|
loadTransactions();
|
|
};
|
|
|