diff --git a/api/categories/controllers.py b/api/categories/controllers.py index 16d50a9..693fdd6 100644 --- a/api/categories/controllers.py +++ b/api/categories/controllers.py @@ -193,3 +193,69 @@ def post(self): return make_response(jsonify({ 'message': f'Error processing CSV: {str(e)}' }), 500) + + +@g.api.route('/categories/') +class CategoriesDetail(Resource): + def get(self, id): + """Get a single category by ID""" + category = CategoriesModel.query.get(id) + if not category: + return make_response(jsonify({'message': 'Category not found'}), 404) + + return make_response(jsonify({'category': category.to_dict()}), 200) + + @g.api.expect(categories_model) + def put(self, id): + """Update a category""" + category = CategoriesModel.query.get(id) + if not category: + return make_response(jsonify({'message': 'Category not found'}), 404) + + data = request.json + + # Validate foreign keys if provided + if 'categories_group_id' in data: + group = CategoriesGroupModel.query.get(data['categories_group_id']) + if not group: + return make_response(jsonify({ + 'message': 'Invalid categories_group_id' + }), 400) + + if 'categories_type_id' in data: + cat_type = CategoriesTypeModel.query.get(data['categories_type_id']) + if not cat_type: + return make_response(jsonify({ + 'message': 'Invalid categories_type_id' + }), 400) + + # Update fields if provided + if 'name' in data: + category.name = data['name'] + if 'categories_group_id' in data: + category.categories_group_id = data['categories_group_id'] + if 'categories_type_id' in data: + category.categories_type_id = data['categories_type_id'] + + category.save() + + return make_response(jsonify({'message': 'Category updated successfully', 'category': category.to_dict()}), 200) + + def delete(self, id): + """Delete a category""" + from api.transaction.models import TransactionModel + + category = CategoriesModel.query.get(id) + if not category: + return make_response(jsonify({'message': 'Category not found'}), 404) + + # Check if there are any transactions linked to this category + linked_transactions = TransactionModel.query.filter_by(categories_id=id).count() + if linked_transactions > 0: + return make_response(jsonify({ + 'message': f'Cannot delete category. There are {linked_transactions} transaction(s) linked to it. Please delete or reassign the transactions first.' + }), 400) + + category.delete() + + return make_response(jsonify({'message': 'Category deleted successfully'}), 200) diff --git a/api/institution/controllers.py b/api/institution/controllers.py index 989938e..2e6486e 100644 --- a/api/institution/controllers.py +++ b/api/institution/controllers.py @@ -39,3 +39,53 @@ def get(self): institutions = InstitutionModel.query.all() _isntitutions = [institution.to_dict() for institution in institutions] return make_response(jsonify({'institutions': _isntitutions}), 200) + +@g.api.route('/institution/') +class InstitutionDetail(Resource): + def get(self, id): + """Get a single institution by ID""" + institution = InstitutionModel.query.get(id) + if not institution: + return make_response(jsonify({'message': 'Institution not found'}), 404) + + return make_response(jsonify({'institution': institution.to_dict()}), 200) + + @g.api.expect(institution_model) + def put(self, id): + """Update an institution""" + institution = InstitutionModel.query.get(id) + if not institution: + return make_response(jsonify({'message': 'Institution not found'}), 404) + + data = request.json + + # Update fields if provided + if 'name' in data: + institution.name = data['name'] + if 'location' in data: + institution.location = data['location'] + if 'description' in data: + institution.description = data['description'] + + institution.save() + + return make_response(jsonify({'message': 'Institution updated successfully', 'institution': institution.to_dict()}), 200) + + def delete(self, id): + """Delete an institution""" + from api.institution_account.models import InstitutionAccountModel + + institution = InstitutionModel.query.get(id) + if not institution: + return make_response(jsonify({'message': 'Institution not found'}), 404) + + # Check if there are any accounts linked to this institution + linked_accounts = InstitutionAccountModel.query.filter_by(institution_id=id).count() + if linked_accounts > 0: + return make_response(jsonify({ + 'message': f'Cannot delete institution. There are {linked_accounts} account(s) linked to it. Please delete or reassign the accounts first.' + }), 400) + + institution.delete() + + return make_response(jsonify({'message': 'Institution deleted successfully'}), 200) diff --git a/api/institution_account/controllers.py b/api/institution_account/controllers.py index 3adf997..1377f27 100644 --- a/api/institution_account/controllers.py +++ b/api/institution_account/controllers.py @@ -87,4 +87,85 @@ def get(self): new_account_balance = starting_balance + total # we need to offset the transactions print(f'Updating account {account.name} balance from {account.balance} to {new_account_balance}') - \ No newline at end of file + account.balance = new_account_balance + account.save() + __accounts.append(account.to_dict()) + return make_response(jsonify({'accounts': __accounts}), 200) + +@g.api.route('/institution/account/') +class InstitutionAccountDetail(Resource): + def get(self, id): + """Get a single account by ID""" + account = InstitutionAccountModel.query.get(id) + if not account: + return make_response(jsonify({'message': 'Account not found'}), 404) + + return make_response(jsonify({'account': account.to_dict()}), 200) + + @g.api.expect(institution_account_model) + def put(self, id): + """Update an account""" + account = InstitutionAccountModel.query.get(id) + if not account: + return make_response(jsonify({'message': 'Account not found'}), 404) + + data = request.json + + # Validate enum fields if provided + valid_statuses = ['active', 'inactive'] + valid_types = ['checking', 'savings', 'credit', 'loan', 'investment', 'other'] + valid_classes = ['asset', 'liability'] + + if 'status' in data and data['status'] and data['status'] not in valid_statuses: + return make_response(jsonify({ + 'message': f'Invalid status. Must be one of: {valid_statuses}' + }), 400) + + if 'account_type' in data and data['account_type'] and data['account_type'] not in valid_types: + return make_response(jsonify({ + 'message': f'Invalid account type. Must be one of: {valid_types}' + }), 400) + + if 'account_class' in data and data['account_class'] and data['account_class'] not in valid_classes: + return make_response(jsonify({ + 'message': f'Invalid account class. Must be one of: {valid_classes}' + }), 400) + + # Update fields if provided + if 'name' in data: + account.name = data['name'] + if 'number' in data: + account.number = data['number'] + if 'status' in data: + account.status = data['status'] + if 'balance' in data: + account.balance = data['balance'] + if 'starting_balance' in data: + account.starting_balance = data['starting_balance'] + if 'account_type' in data: + account.account_type = data['account_type'] + if 'account_class' in data: + account.account_class = data['account_class'] + if 'institution_id' in data: + account.institution_id = data['institution_id'] + + account.save() + + return make_response(jsonify({'message': 'Account updated successfully', 'account': account.to_dict()}), 200) + + def delete(self, id): + """Delete an account""" + account = InstitutionAccountModel.query.get(id) + if not account: + return make_response(jsonify({'message': 'Account not found'}), 404) + + # Check if there are any transactions linked to this account + linked_transactions = TransactionModel.query.filter_by(account_id=id).count() + if linked_transactions > 0: + return make_response(jsonify({ + 'message': f'Cannot delete account. There are {linked_transactions} transaction(s) linked to it. Please delete the transactions first.' + }), 400) + + account.delete() + + return make_response(jsonify({'message': 'Account deleted successfully'}), 200) \ No newline at end of file diff --git a/api/transaction/controllers.py b/api/transaction/controllers.py index 095630c..1fb736f 100644 --- a/api/transaction/controllers.py +++ b/api/transaction/controllers.py @@ -362,3 +362,77 @@ def create_transaction(self,user_id, categories_id, account_id, amount, transact db.session.add(transaction) db.session.commit() print(f"Transaction {external_id} created successfully.") + + +@g.api.route('/transaction/') +class TransactionDetail(Resource): + def get(self, id): + """Get a single transaction by ID""" + transaction = TransactionModel.query.get(id) + if not transaction: + return make_response(jsonify({'message': 'Transaction not found'}), 404) + + return make_response(jsonify({'transaction': transaction.to_dict()}), 200) + + @g.api.expect(transaction_model) + def put(self, id): + """Update a transaction""" + transaction = TransactionModel.query.get(id) + if not transaction: + return make_response(jsonify({'message': 'Transaction not found'}), 404) + + data = request.json + + # Validate amount if provided + if 'amount' in data: + try: + amount = float(data['amount']) + except (ValueError, TypeError): + return make_response(jsonify({ + 'message': 'Amount must be a valid number' + }), 400) + + # Validate foreign keys if provided + if 'categories_id' in data: + category = CategoriesModel.query.get(data['categories_id']) + if not category: + return make_response(jsonify({ + 'message': 'Invalid categories_id' + }), 400) + + if 'account_id' in data: + account = InstitutionAccountModel.query.get(data['account_id']) + if not account: + return make_response(jsonify({ + 'message': 'Invalid account_id' + }), 400) + + # Update fields if provided + if 'description' in data: + transaction.description = data['description'] + if 'amount' in data: + transaction.amount = float(data['amount']) + if 'categories_id' in data: + transaction.categories_id = data['categories_id'] + if 'account_id' in data: + transaction.account_id = data['account_id'] + if 'transaction_type' in data: + transaction.transaction_type = data['transaction_type'] + if 'external_date' in data: + transaction.external_date = data['external_date'] + if 'external_id' in data: + transaction.external_id = data['external_id'] + + transaction.save() + + return make_response(jsonify({'message': 'Transaction updated successfully', 'transaction': transaction.to_dict()}), 200) + + def delete(self, id): + """Delete a transaction""" + transaction = TransactionModel.query.get(id) + if not transaction: + return make_response(jsonify({'message': 'Transaction not found'}), 404) + + transaction.delete() + + return make_response(jsonify({'message': 'Transaction deleted successfully'}), 200) diff --git a/app/static/js/categories/categories.js b/app/static/js/categories/categories.js index 4d249df..ea248da 100644 --- a/app/static/js/categories/categories.js +++ b/app/static/js/categories/categories.js @@ -31,4 +31,88 @@ function categoriesFormSubmit(event) { .catch((error) => { console.error('Error:', error); }); +} + +async function editCategory(categoryId) { + // Fetch the category data + try { + const response = await fetch(`/api/categories/${categoryId}`); + const data = await response.json(); + + if (response.ok) { + // Populate the edit form + document.getElementById('edit_category_id').value = categoryId; + document.getElementById('edit_categoriesName').value = data.category.name; + document.getElementById('edit_categoriesGroupId').value = data.category.categories_group_id || ''; + document.getElementById('edit_categoriesTypeId').value = data.category.categories_type_id || ''; + + // Show the modal + const editModal = new bootstrap.Modal(document.getElementById('EditCategoryModal')); + editModal.show(); + } else { + alert('Error loading category: ' + data.message); + } + } catch (error) { + console.error('Error:', error); + alert('Error loading category'); + } +} + +function updateCategory(event) { + event.preventDefault(); + + const categoryId = document.getElementById('edit_category_id').value; + const categoryName = document.getElementById('edit_categoriesName').value; + const categoriesGroupId = document.getElementById('edit_categoriesGroupId').value; + const categoriesTypeId = document.getElementById('edit_categoriesTypeId').value; + const user_id = document.getElementById('edit_category_user_id').value; + + const data = { + name: categoryName, + categories_group_id: categoriesGroupId, + categories_type_id: categoriesTypeId, + user_id: user_id + }; + + fetch(`/api/categories/${categoryId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(data => { + console.log('Success:', data); + // redirect to the categories page + window.location.href = '/categories'; + }) + .catch((error) => { + console.error('Error:', error); + alert('Error updating category'); + }); +} + +async function deleteCategory(categoryId) { + if (!confirm('Are you sure you want to delete this category?')) { + return; + } + + try { + const response = await fetch(`/api/categories/${categoryId}`, { + method: 'DELETE', + }); + + const data = await response.json(); + + if (response.ok) { + alert('Category deleted successfully'); + window.location.href = '/categories'; + } else { + alert('Error deleting category: ' + data.message); + } + } catch (error) { + console.error('Error:', error); + alert('Error deleting category'); + } } \ No newline at end of file diff --git a/app/static/js/institution/institution.js b/app/static/js/institution/institution.js index 2b8a4b2..1ed06fd 100644 --- a/app/static/js/institution/institution.js +++ b/app/static/js/institution/institution.js @@ -29,4 +29,88 @@ function instituionFormSubmit(event) { .catch((error) => { console.error('Error:', error); }); +} + +async function editInstitution(institutionId) { + // Fetch the institution data + try { + const response = await fetch(`/api/institution/${institutionId}`); + const data = await response.json(); + + if (response.ok) { + // Populate the edit form + document.getElementById('edit_institution_id').value = institutionId; + document.getElementById('edit_institutionName').value = data.institution.name; + document.getElementById('edit_institutionLocation').value = data.institution.location || ''; + document.getElementById('edit_institutionDescription').value = data.institution.description || ''; + + // Show the modal + const editModal = new bootstrap.Modal(document.getElementById('EditInstitutionModal')); + editModal.show(); + } else { + alert('Error loading institution: ' + data.message); + } + } catch (error) { + console.error('Error:', error); + alert('Error loading institution'); + } +} + +function updateInstitution(event) { + event.preventDefault(); + + const institutionId = document.getElementById('edit_institution_id').value; + const institutionName = document.getElementById('edit_institutionName').value; + const institutionLocation = document.getElementById('edit_institutionLocation').value; + const institutionDescription = document.getElementById('edit_institutionDescription').value; + const user_id = document.getElementById('edit_user_id').value; + + const data = { + name: institutionName, + location: institutionLocation, + description: institutionDescription, + user_id: user_id + }; + + fetch(`/api/institution/${institutionId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(data => { + console.log('Success:', data); + // redirect to the institutions page + window.location.href = '/institution'; + }) + .catch((error) => { + console.error('Error:', error); + alert('Error updating institution'); + }); +} + +async function deleteInstitution(institutionId) { + if (!confirm('Are you sure you want to delete this institution?')) { + return; + } + + try { + const response = await fetch(`/api/institution/${institutionId}`, { + method: 'DELETE', + }); + + const data = await response.json(); + + if (response.ok) { + alert('Institution deleted successfully'); + window.location.href = '/institution'; + } else { + alert('Error deleting institution: ' + data.message); + } + } catch (error) { + console.error('Error:', error); + alert('Error deleting institution'); + } } \ No newline at end of file diff --git a/app/static/js/institution_account/institution_account.js b/app/static/js/institution_account/institution_account.js index ad38e7b..00f970b 100644 --- a/app/static/js/institution_account/institution_account.js +++ b/app/static/js/institution_account/institution_account.js @@ -36,4 +36,95 @@ function instituionAccountFormSubmit(event) { console.error('Error:', error); }); +} + +async function editAccount(accountId) { + // Fetch the account data + try { + const response = await fetch(`/api/institution/account/${accountId}`); + const data = await response.json(); + + if (response.ok) { + // Populate the edit form + document.getElementById('edit_account_id').value = accountId; + document.getElementById('edit_accountName').value = data.account.name; + document.getElementById('edit_accountNumber').value = data.account.number || ''; + document.getElementById('edit_accountStatus').value = data.account.status || ''; + document.getElementById('edit_instituionId').value = data.account.institution_id || ''; + + // Show the modal + const editModal = new bootstrap.Modal(document.getElementById('EditAccountModal')); + editModal.show(); + } else { + alert('Error loading account: ' + data.message); + } + } catch (error) { + console.error('Error:', error); + alert('Error loading account'); + } +} + +function updateAccount(event) { + event.preventDefault(); + + const accountId = document.getElementById('edit_account_id').value; + const institutionId = document.getElementById('edit_instituionId').value; + const accountName = document.getElementById('edit_accountName').value; + const accountNumber = document.getElementById('edit_accountNumber').value; + const accountStatus = document.getElementById('edit_accountStatus').value; + const user_id = document.getElementById('edit_account_user_id').value; + + const data = { + institution_id: institutionId, + name: accountName, + number: accountNumber, + status: accountStatus, + user_id: user_id, + balance: 0, + starting_balance: 0, + account_type: 'checking', + account_class: 'asset' + }; + + fetch(`/api/institution/account/${accountId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(data => { + console.log('Success:', data); + // redirect to the accounts page + window.location.href = '/account'; + }) + .catch((error) => { + console.error('Error:', error); + alert('Error updating account'); + }); +} + +async function deleteAccount(accountId) { + if (!confirm('Are you sure you want to delete this account?')) { + return; + } + + try { + const response = await fetch(`/api/institution/account/${accountId}`, { + method: 'DELETE', + }); + + const data = await response.json(); + + if (response.ok) { + alert('Account deleted successfully'); + window.location.href = '/account'; + } else { + alert('Error deleting account: ' + data.message); + } + } catch (error) { + console.error('Error:', error); + alert('Error deleting account'); + } } \ No newline at end of file diff --git a/app/static/js/transactions/transactions.js b/app/static/js/transactions/transactions.js index a7b6955..88e5337 100644 --- a/app/static/js/transactions/transactions.js +++ b/app/static/js/transactions/transactions.js @@ -68,3 +68,100 @@ document.addEventListener("DOMContentLoaded", function () { console.error("Upload button not found."); } }); + +// Transaction CRUD functions +function transactionsFormSubmit(event) { + event.preventDefault(); + const description = document.getElementById('transactionDescription').value; + const user_id = document.getElementById('user_id').value; + + // For now, this is a simplified version - you may want to add more fields + alert('Transaction creation form needs more fields. Please use CSV import instead.'); +} + +async function editTransaction(transactionId) { + // Fetch the transaction data + try { + const response = await fetch(`/api/transaction/${transactionId}`); + const data = await response.json(); + + if (response.ok) { + // Populate the edit form + document.getElementById('edit_transaction_id').value = transactionId; + document.getElementById('edit_transactionDescription').value = data.transaction.description || ''; + document.getElementById('edit_transactionAmount').value = data.transaction.amount || ''; + document.getElementById('edit_transactionCategory').value = data.transaction.categories_id || ''; + document.getElementById('edit_transactionAccount').value = data.transaction.account_id || ''; + + // Show the modal + const editModal = new bootstrap.Modal(document.getElementById('EditTransactionModal')); + editModal.show(); + } else { + alert('Error loading transaction: ' + data.message); + } + } catch (error) { + console.error('Error:', error); + alert('Error loading transaction'); + } +} + +function updateTransaction(event) { + event.preventDefault(); + + const transactionId = document.getElementById('edit_transaction_id').value; + const description = document.getElementById('edit_transactionDescription').value; + const amount = document.getElementById('edit_transactionAmount').value; + const categoryId = document.getElementById('edit_transactionCategory').value; + const accountId = document.getElementById('edit_transactionAccount').value; + const user_id = document.getElementById('edit_transaction_user_id').value; + + const data = { + description: description, + amount: parseFloat(amount), + categories_id: categoryId, + account_id: accountId, + user_id: user_id + }; + + fetch(`/api/transaction/${transactionId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(response => response.json()) + .then(data => { + console.log('Success:', data); + // redirect to the transactions page + window.location.href = '/transactions'; + }) + .catch((error) => { + console.error('Error:', error); + alert('Error updating transaction'); + }); +} + +async function deleteTransaction(transactionId) { + if (!confirm('Are you sure you want to delete this transaction?')) { + return; + } + + try { + const response = await fetch(`/api/transaction/${transactionId}`, { + method: 'DELETE', + }); + + const data = await response.json(); + + if (response.ok) { + alert('Transaction deleted successfully'); + window.location.href = '/transactions'; + } else { + alert('Error deleting transaction: ' + data.message); + } + } catch (error) { + console.error('Error:', error); + alert('Error deleting transaction'); + } +} diff --git a/app/templates/categories/index.html b/app/templates/categories/index.html index 42ec877..a69786a 100644 --- a/app/templates/categories/index.html +++ b/app/templates/categories/index.html @@ -153,7 +153,12 @@