Skip to content

Commit 0e64598

Browse files
authored
Merge pull request #4 from caseybecking/feature/edit-delete-functionality
Add edit and delete functionality for institutions, accounts, categories
2 parents d953478 + 7b13b38 commit 0e64598

File tree

13 files changed

+878
-6
lines changed

13 files changed

+878
-6
lines changed

api/categories/controllers.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,69 @@ def post(self):
193193
return make_response(jsonify({
194194
'message': f'Error processing CSV: {str(e)}'
195195
}), 500)
196+
197+
198+
@g.api.route('/categories/<string:id>')
199+
class CategoriesDetail(Resource):
200+
def get(self, id):
201+
"""Get a single category by ID"""
202+
category = CategoriesModel.query.get(id)
203+
if not category:
204+
return make_response(jsonify({'message': 'Category not found'}), 404)
205+
206+
return make_response(jsonify({'category': category.to_dict()}), 200)
207+
208+
@g.api.expect(categories_model)
209+
def put(self, id):
210+
"""Update a category"""
211+
category = CategoriesModel.query.get(id)
212+
if not category:
213+
return make_response(jsonify({'message': 'Category not found'}), 404)
214+
215+
data = request.json
216+
217+
# Validate foreign keys if provided
218+
if 'categories_group_id' in data:
219+
group = CategoriesGroupModel.query.get(data['categories_group_id'])
220+
if not group:
221+
return make_response(jsonify({
222+
'message': 'Invalid categories_group_id'
223+
}), 400)
224+
225+
if 'categories_type_id' in data:
226+
cat_type = CategoriesTypeModel.query.get(data['categories_type_id'])
227+
if not cat_type:
228+
return make_response(jsonify({
229+
'message': 'Invalid categories_type_id'
230+
}), 400)
231+
232+
# Update fields if provided
233+
if 'name' in data:
234+
category.name = data['name']
235+
if 'categories_group_id' in data:
236+
category.categories_group_id = data['categories_group_id']
237+
if 'categories_type_id' in data:
238+
category.categories_type_id = data['categories_type_id']
239+
240+
category.save()
241+
242+
return make_response(jsonify({'message': 'Category updated successfully', 'category': category.to_dict()}), 200)
243+
244+
def delete(self, id):
245+
"""Delete a category"""
246+
from api.transaction.models import TransactionModel
247+
248+
category = CategoriesModel.query.get(id)
249+
if not category:
250+
return make_response(jsonify({'message': 'Category not found'}), 404)
251+
252+
# Check if there are any transactions linked to this category
253+
linked_transactions = TransactionModel.query.filter_by(categories_id=id).count()
254+
if linked_transactions > 0:
255+
return make_response(jsonify({
256+
'message': f'Cannot delete category. There are {linked_transactions} transaction(s) linked to it. Please delete or reassign the transactions first.'
257+
}), 400)
258+
259+
category.delete()
260+
261+
return make_response(jsonify({'message': 'Category deleted successfully'}), 200)

api/institution/controllers.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,53 @@ def get(self):
3939
institutions = InstitutionModel.query.all()
4040
_isntitutions = [institution.to_dict() for institution in institutions]
4141
return make_response(jsonify({'institutions': _isntitutions}), 200)
42+
43+
@g.api.route('/institution/<string:id>')
44+
class InstitutionDetail(Resource):
45+
def get(self, id):
46+
"""Get a single institution by ID"""
47+
institution = InstitutionModel.query.get(id)
48+
if not institution:
49+
return make_response(jsonify({'message': 'Institution not found'}), 404)
50+
51+
return make_response(jsonify({'institution': institution.to_dict()}), 200)
52+
53+
@g.api.expect(institution_model)
54+
def put(self, id):
55+
"""Update an institution"""
56+
institution = InstitutionModel.query.get(id)
57+
if not institution:
58+
return make_response(jsonify({'message': 'Institution not found'}), 404)
59+
60+
data = request.json
61+
62+
# Update fields if provided
63+
if 'name' in data:
64+
institution.name = data['name']
65+
if 'location' in data:
66+
institution.location = data['location']
67+
if 'description' in data:
68+
institution.description = data['description']
69+
70+
institution.save()
71+
72+
return make_response(jsonify({'message': 'Institution updated successfully', 'institution': institution.to_dict()}), 200)
73+
74+
def delete(self, id):
75+
"""Delete an institution"""
76+
from api.institution_account.models import InstitutionAccountModel
77+
78+
institution = InstitutionModel.query.get(id)
79+
if not institution:
80+
return make_response(jsonify({'message': 'Institution not found'}), 404)
81+
82+
# Check if there are any accounts linked to this institution
83+
linked_accounts = InstitutionAccountModel.query.filter_by(institution_id=id).count()
84+
if linked_accounts > 0:
85+
return make_response(jsonify({
86+
'message': f'Cannot delete institution. There are {linked_accounts} account(s) linked to it. Please delete or reassign the accounts first.'
87+
}), 400)
88+
89+
institution.delete()
90+
91+
return make_response(jsonify({'message': 'Institution deleted successfully'}), 200)

api/institution_account/controllers.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,85 @@ def get(self):
8787
new_account_balance = starting_balance + total
8888
# we need to offset the transactions
8989
print(f'Updating account {account.name} balance from {account.balance} to {new_account_balance}')
90-
90+
account.balance = new_account_balance
91+
account.save()
92+
__accounts.append(account.to_dict())
93+
return make_response(jsonify({'accounts': __accounts}), 200)
94+
95+
@g.api.route('/institution/account/<string:id>')
96+
class InstitutionAccountDetail(Resource):
97+
def get(self, id):
98+
"""Get a single account by ID"""
99+
account = InstitutionAccountModel.query.get(id)
100+
if not account:
101+
return make_response(jsonify({'message': 'Account not found'}), 404)
102+
103+
return make_response(jsonify({'account': account.to_dict()}), 200)
104+
105+
@g.api.expect(institution_account_model)
106+
def put(self, id):
107+
"""Update an account"""
108+
account = InstitutionAccountModel.query.get(id)
109+
if not account:
110+
return make_response(jsonify({'message': 'Account not found'}), 404)
111+
112+
data = request.json
113+
114+
# Validate enum fields if provided
115+
valid_statuses = ['active', 'inactive']
116+
valid_types = ['checking', 'savings', 'credit', 'loan', 'investment', 'other']
117+
valid_classes = ['asset', 'liability']
118+
119+
if 'status' in data and data['status'] and data['status'] not in valid_statuses:
120+
return make_response(jsonify({
121+
'message': f'Invalid status. Must be one of: {valid_statuses}'
122+
}), 400)
123+
124+
if 'account_type' in data and data['account_type'] and data['account_type'] not in valid_types:
125+
return make_response(jsonify({
126+
'message': f'Invalid account type. Must be one of: {valid_types}'
127+
}), 400)
128+
129+
if 'account_class' in data and data['account_class'] and data['account_class'] not in valid_classes:
130+
return make_response(jsonify({
131+
'message': f'Invalid account class. Must be one of: {valid_classes}'
132+
}), 400)
133+
134+
# Update fields if provided
135+
if 'name' in data:
136+
account.name = data['name']
137+
if 'number' in data:
138+
account.number = data['number']
139+
if 'status' in data:
140+
account.status = data['status']
141+
if 'balance' in data:
142+
account.balance = data['balance']
143+
if 'starting_balance' in data:
144+
account.starting_balance = data['starting_balance']
145+
if 'account_type' in data:
146+
account.account_type = data['account_type']
147+
if 'account_class' in data:
148+
account.account_class = data['account_class']
149+
if 'institution_id' in data:
150+
account.institution_id = data['institution_id']
151+
152+
account.save()
153+
154+
return make_response(jsonify({'message': 'Account updated successfully', 'account': account.to_dict()}), 200)
155+
156+
def delete(self, id):
157+
"""Delete an account"""
158+
account = InstitutionAccountModel.query.get(id)
159+
if not account:
160+
return make_response(jsonify({'message': 'Account not found'}), 404)
161+
162+
# Check if there are any transactions linked to this account
163+
linked_transactions = TransactionModel.query.filter_by(account_id=id).count()
164+
if linked_transactions > 0:
165+
return make_response(jsonify({
166+
'message': f'Cannot delete account. There are {linked_transactions} transaction(s) linked to it. Please delete the transactions first.'
167+
}), 400)
168+
169+
account.delete()
170+
171+
return make_response(jsonify({'message': 'Account deleted successfully'}), 200)

api/transaction/controllers.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,3 +362,77 @@ def create_transaction(self,user_id, categories_id, account_id, amount, transact
362362
db.session.add(transaction)
363363
db.session.commit()
364364
print(f"Transaction {external_id} created successfully.")
365+
366+
367+
@g.api.route('/transaction/<string:id>')
368+
class TransactionDetail(Resource):
369+
def get(self, id):
370+
"""Get a single transaction by ID"""
371+
transaction = TransactionModel.query.get(id)
372+
if not transaction:
373+
return make_response(jsonify({'message': 'Transaction not found'}), 404)
374+
375+
return make_response(jsonify({'transaction': transaction.to_dict()}), 200)
376+
377+
@g.api.expect(transaction_model)
378+
def put(self, id):
379+
"""Update a transaction"""
380+
transaction = TransactionModel.query.get(id)
381+
if not transaction:
382+
return make_response(jsonify({'message': 'Transaction not found'}), 404)
383+
384+
data = request.json
385+
386+
# Validate amount if provided
387+
if 'amount' in data:
388+
try:
389+
amount = float(data['amount'])
390+
except (ValueError, TypeError):
391+
return make_response(jsonify({
392+
'message': 'Amount must be a valid number'
393+
}), 400)
394+
395+
# Validate foreign keys if provided
396+
if 'categories_id' in data:
397+
category = CategoriesModel.query.get(data['categories_id'])
398+
if not category:
399+
return make_response(jsonify({
400+
'message': 'Invalid categories_id'
401+
}), 400)
402+
403+
if 'account_id' in data:
404+
account = InstitutionAccountModel.query.get(data['account_id'])
405+
if not account:
406+
return make_response(jsonify({
407+
'message': 'Invalid account_id'
408+
}), 400)
409+
410+
# Update fields if provided
411+
if 'description' in data:
412+
transaction.description = data['description']
413+
if 'amount' in data:
414+
transaction.amount = float(data['amount'])
415+
if 'categories_id' in data:
416+
transaction.categories_id = data['categories_id']
417+
if 'account_id' in data:
418+
transaction.account_id = data['account_id']
419+
if 'transaction_type' in data:
420+
transaction.transaction_type = data['transaction_type']
421+
if 'external_date' in data:
422+
transaction.external_date = data['external_date']
423+
if 'external_id' in data:
424+
transaction.external_id = data['external_id']
425+
426+
transaction.save()
427+
428+
return make_response(jsonify({'message': 'Transaction updated successfully', 'transaction': transaction.to_dict()}), 200)
429+
430+
def delete(self, id):
431+
"""Delete a transaction"""
432+
transaction = TransactionModel.query.get(id)
433+
if not transaction:
434+
return make_response(jsonify({'message': 'Transaction not found'}), 404)
435+
436+
transaction.delete()
437+
438+
return make_response(jsonify({'message': 'Transaction deleted successfully'}), 200)

app/static/js/categories/categories.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,88 @@ function categoriesFormSubmit(event) {
3131
.catch((error) => {
3232
console.error('Error:', error);
3333
});
34+
}
35+
36+
async function editCategory(categoryId) {
37+
// Fetch the category data
38+
try {
39+
const response = await fetch(`/api/categories/${categoryId}`);
40+
const data = await response.json();
41+
42+
if (response.ok) {
43+
// Populate the edit form
44+
document.getElementById('edit_category_id').value = categoryId;
45+
document.getElementById('edit_categoriesName').value = data.category.name;
46+
document.getElementById('edit_categoriesGroupId').value = data.category.categories_group_id || '';
47+
document.getElementById('edit_categoriesTypeId').value = data.category.categories_type_id || '';
48+
49+
// Show the modal
50+
const editModal = new bootstrap.Modal(document.getElementById('EditCategoryModal'));
51+
editModal.show();
52+
} else {
53+
alert('Error loading category: ' + data.message);
54+
}
55+
} catch (error) {
56+
console.error('Error:', error);
57+
alert('Error loading category');
58+
}
59+
}
60+
61+
function updateCategory(event) {
62+
event.preventDefault();
63+
64+
const categoryId = document.getElementById('edit_category_id').value;
65+
const categoryName = document.getElementById('edit_categoriesName').value;
66+
const categoriesGroupId = document.getElementById('edit_categoriesGroupId').value;
67+
const categoriesTypeId = document.getElementById('edit_categoriesTypeId').value;
68+
const user_id = document.getElementById('edit_category_user_id').value;
69+
70+
const data = {
71+
name: categoryName,
72+
categories_group_id: categoriesGroupId,
73+
categories_type_id: categoriesTypeId,
74+
user_id: user_id
75+
};
76+
77+
fetch(`/api/categories/${categoryId}`, {
78+
method: 'PUT',
79+
headers: {
80+
'Content-Type': 'application/json',
81+
},
82+
body: JSON.stringify(data),
83+
})
84+
.then(response => response.json())
85+
.then(data => {
86+
console.log('Success:', data);
87+
// redirect to the categories page
88+
window.location.href = '/categories';
89+
})
90+
.catch((error) => {
91+
console.error('Error:', error);
92+
alert('Error updating category');
93+
});
94+
}
95+
96+
async function deleteCategory(categoryId) {
97+
if (!confirm('Are you sure you want to delete this category?')) {
98+
return;
99+
}
100+
101+
try {
102+
const response = await fetch(`/api/categories/${categoryId}`, {
103+
method: 'DELETE',
104+
});
105+
106+
const data = await response.json();
107+
108+
if (response.ok) {
109+
alert('Category deleted successfully');
110+
window.location.href = '/categories';
111+
} else {
112+
alert('Error deleting category: ' + data.message);
113+
}
114+
} catch (error) {
115+
console.error('Error:', error);
116+
alert('Error deleting category');
117+
}
34118
}

0 commit comments

Comments
 (0)