-
-
Notifications
You must be signed in to change notification settings - Fork 165
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding invoice editing functionality to MyFinances (#153)
* Create edit.py and add the base edit invoice function * feat: add require_method of EDIT, allow users to update contributes * Update urls.py to correctly navigate to the invoice edit page * Update _fetch_body.html to activate the edit function from frontend webpage and redirect to edit html * Added temporary html pages for editing invoice * Configured backend.views.core.invoices.edit and wrote the view function used in urlpatterns path for redirect to edit.html * Fixed argument id error when loading page invoices/edit/<str:id> and updated redirect paths in edit.html * Update edit.py by fixing the "invoice_id not found" problem and invoice can be edited now * Update fetch.py by fixing the problem of unable to switch payment status * Update edit.py by redirecting to invoice page after successfully editing invoices * Added method to store existing invoice data in a dict based on id and pass it to frontend for populating fields during an edit * Added comments and ran black formatter for code organization and clarity --------- Co-authored-by: Tianrui-Luo <77422312+Tianrui-Luo@users.noreply.github.com> Co-authored-by: Nuovaxu <nuovaxu@hotmail.com> Co-authored-by: Trey <73353716+TreyWW@users.noreply.github.com>
- Loading branch information
1 parent
03f2e14
commit dccc610
Showing
10 changed files
with
315 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
from django.contrib import messages | ||
from django.http import HttpRequest, JsonResponse, QueryDict | ||
from django.shortcuts import render | ||
from django.views.decorators.http import require_http_methods | ||
|
||
from backend.models import Invoice | ||
from datetime import datetime | ||
|
||
|
||
@require_http_methods(["POST"]) | ||
def edit_invoice(request: HttpRequest): | ||
try: | ||
invoice = Invoice.objects.get(id=request.POST.get("invoice_id")) | ||
except: | ||
return JsonResponse({"message": "Invoice not found"}, status=404) | ||
|
||
attributes_to_updates = { | ||
"date_due": datetime.strptime(request.POST.get("date_due"), "%Y-%m-%d").date(), | ||
"date_issued": request.POST.get("date_issued"), | ||
"client_name": request.POST.get("to_name"), | ||
"client_company": request.POST.get("to_company"), | ||
"client_address": request.POST.get("to_address"), | ||
"client_city": request.POST.get("to_city"), | ||
"client_county": request.POST.get("to_county"), | ||
"client_country": request.POST.get("to_country"), | ||
"self_name": request.POST.get("from_name"), | ||
"self_company": request.POST.get("from_company"), | ||
"self_address": request.POST.get("from_address"), | ||
"self_city": request.POST.get("from_city"), | ||
"self_county": request.POST.get("from_county"), | ||
"self_country": request.POST.get("from_country"), | ||
"notes": request.POST.get("notes"), | ||
"invoice_number": request.POST.get("invoice_number"), | ||
"vat_number": request.POST.get("vat_number"), | ||
"reference": request.POST.get("reference"), | ||
"sort_code": request.POST.get("sort_code"), | ||
"account_number": request.POST.get("account_number"), | ||
"account_holder_name": request.POST.get("account_holder_name"), | ||
} | ||
|
||
for column_name, new_value in attributes_to_updates.items(): | ||
setattr(invoice, column_name, new_value) | ||
|
||
invoice.save() | ||
|
||
if request.htmx: | ||
messages.success(request, "Invoice edited") | ||
return render(request, "partials/base/toasts.html") | ||
|
||
return JsonResponse({"message": "Invoice successfully edited"}, status=200) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
from django.contrib import messages | ||
from django.http import HttpRequest, JsonResponse, QueryDict | ||
from django.shortcuts import render | ||
from django.views.decorators.http import require_http_methods | ||
|
||
from backend.models import Invoice | ||
from datetime import datetime | ||
|
||
# RELATED PATH FILES : \frontend\templates\pages\invoices\dashboard\_fetch_body.html, \backend\urls.py | ||
|
||
|
||
# Function that takes an invoice object and makes a dict of its attributes | ||
def invoice_get_existing_data(invoice_obj): | ||
stored_data = { | ||
"og_name": invoice_obj.self_name, | ||
"og_company": invoice_obj.self_company, | ||
"og_address": invoice_obj.self_address, | ||
"og_city": invoice_obj.self_city, | ||
"og_county": invoice_obj.self_county, | ||
"og_country": invoice_obj.self_country, | ||
"og_cilent_name": invoice_obj.client_name, | ||
"og_cilent_company": invoice_obj.client_company, | ||
"og_cilent_address": invoice_obj.client_address, | ||
"og_cilent_city": invoice_obj.client_city, | ||
"og_cilent_county": invoice_obj.client_county, | ||
"og_cilent_country": invoice_obj.client_country, | ||
} | ||
return stored_data | ||
|
||
|
||
# gets invoice object from invoice id, convert obj to dict, and renders edit.html while passing the stored invoice values to frontend | ||
def invoice_edit_page_get(request, invoice_id): | ||
context = {"type": "edit"} | ||
try: | ||
invoice = Invoice.objects.get(id=invoice_id) | ||
except: | ||
return JsonResponse({"message": "Invoice not found"}, status=404) | ||
|
||
# use to populate fields with existing data in edit_from_destination.html AND edit_to_destination.html | ||
data_to_populate = invoice_get_existing_data(invoice) | ||
return render(request, "pages/invoices/edit/edit.html", data_to_populate) | ||
|
||
|
||
# when user changes/modifies any of the fields with new information (during edit invoice) | ||
@require_http_methods(["POST"]) | ||
def edit_invoice(request: HttpRequest, invoice_id): | ||
try: | ||
invoice = Invoice.objects.get(id=invoice_id) | ||
except: | ||
return JsonResponse({"message": "Invoice not found"}, status=404) | ||
|
||
attributes_to_updates = { | ||
"date_due": datetime.strptime(request.POST.get("date_due"), "%Y-%m-%d").date(), | ||
"date_issued": request.POST.get("date_issued"), | ||
"client_name": request.POST.get("to_name"), | ||
"client_company": request.POST.get("to_company"), | ||
"client_address": request.POST.get("to_address"), | ||
"client_city": request.POST.get("to_city"), | ||
"client_county": request.POST.get("to_county"), | ||
"client_country": request.POST.get("to_country"), | ||
"self_name": request.POST.get("from_name"), | ||
"self_company": request.POST.get("from_company"), | ||
"self_address": request.POST.get("from_address"), | ||
"self_city": request.POST.get("from_city"), | ||
"self_county": request.POST.get("from_county"), | ||
"self_country": request.POST.get("from_country"), | ||
"notes": request.POST.get("notes"), | ||
"invoice_number": request.POST.get("invoice_number"), | ||
"vat_number": request.POST.get("vat_number"), | ||
"reference": request.POST.get("reference"), | ||
"sort_code": request.POST.get("sort_code"), | ||
"account_number": request.POST.get("account_number"), | ||
"account_holder_name": request.POST.get("account_holder_name"), | ||
} | ||
|
||
for column_name, new_value in attributes_to_updates.items(): | ||
setattr(invoice, column_name, new_value) | ||
|
||
invoice.save() | ||
|
||
if request.htmx: | ||
messages.success(request, "Invoice edited") | ||
return render(request, "partials/base/toasts.html") | ||
|
||
return render(request, "pages/invoices/dashboard/dashboard.html") | ||
# return JsonResponse({"message": "Invoice successfully edited"}, status=200) | ||
|
||
|
||
# decorator & view function for rendering page and updating invoice items in the backend | ||
@require_http_methods(["GET", "POST"]) | ||
def edit_invoice_page(request: HttpRequest, id): | ||
if request.method == "POST": | ||
return edit_invoice(request, id) | ||
return invoice_edit_page_get(request, id) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
{% extends 'base/base.html' %} | ||
{% load static %} | ||
{% load markdownify %} | ||
{% block content %} | ||
{% include 'components/modal.html' with modals=modal_data %} | ||
<form method="post" class="card bg-base-100 p-6 group"> | ||
{% csrf_token %} | ||
<div class="divider">STEP 1 - DESTINATIONS</div> | ||
<div class="my-4 flex w-full flex-col"> | ||
<div class="mb-2 grid grid-cols-2"> | ||
<h3 class="text-sm text-natural font-semibold ms-3">From</h3> | ||
<h3 class="text-sm text-natural font-semibold hidden lg:block text-end me-6">To</h3> | ||
</div> | ||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 w-full" id="to_and_from_container"> | ||
{% include 'pages/invoices/edit/edit_from_destination.html' %} | ||
{% include 'pages/invoices/edit/edit_to_destination.html' %} | ||
</div> | ||
</div> | ||
<div class="divider my-4">STEP 2 - DATES</div> | ||
<div class="my-4 flex w-full flex-col"> | ||
<div class="w-full gap-4 grid grid-cols-1 lg:grid-cols-2"> | ||
<div class="input_card"> | ||
<div class="card-body"> | ||
<div class="form-control w-full"> | ||
<label class="label justify-start"> | ||
Issue date | ||
<span class="required_star">*</span> | ||
</label> | ||
<input required id="dateIssued" name="date_issued" placeholder="" type="date" | ||
class="peer input input-bordered input-block"> | ||
<label class="label peer-[&:not(:placeholder-shown):not(:focus):invalid]:block hidden "> | ||
<span class="label-text-alt text-error">Please enter a valid date.</span> | ||
</label> | ||
</div> | ||
</div> | ||
</div> | ||
<div class="input_card"> | ||
<div class="card-body"> | ||
<div class="form-control w-full"> | ||
<label class="label justify-start"> | ||
Due date | ||
<span class="required_star">*</span> | ||
</label> | ||
<input required name="date_due" id="dueDate" placeholder="" type="date" | ||
class="peer input-bordered input input-block"> | ||
<label class="label peer-[&:not(:placeholder-shown):not(:focus):invalid]:block hidden "> | ||
<span class="label-text-alt text-error">Please enter a valid date.</span> | ||
</label> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
|
||
|
||
<div class="group-invalid:tooltip" data-tip="Fill out all required details to save the invoice."> | ||
<button class="btn btn-primary group-invalid:btn-disabled btn-block"> | ||
Update Invoice | ||
</button> | ||
</div> | ||
|
||
|
||
</form> | ||
|
||
|
||
|
||
<!-- still need to add SERVICE & PAYMENT INFORMATION fields for editing --> | ||
|
||
|
||
|
||
{% endblock content %} |
30 changes: 30 additions & 0 deletions
30
frontend/templates/pages/invoices/edit/edit_from_destination.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{% if not swapping %} | ||
<button onclick="modal_invoices_from_destination.showModal();" id="from_destination" | ||
class="input_card text-left" | ||
hx-trigger="click once" hx-swap="beforeend" hx-target="#modal_container" | ||
hx-get="{% url "api:base:modal retrieve" modal_name="invoices_from_destination" %}"> | ||
{% endif %} | ||
|
||
<!-- *Need to replace the default values with attributes from data_to_populate dict* --> | ||
|
||
|
||
<!-- PRE-POPULATE FIELDS WITH EXISTING DATA OF THE INVOICE--> | ||
<div class="card-body"> | ||
<p class="text-md">{{ name | default:"John Smith" }}</p> | ||
<p class="text-sm">{{ company | default:"Google" }}</p> | ||
<p class="text-sm">{{ address | default:"128 Example Road" }}</p> | ||
<p class="text-sm">{{ city | default:"Oxford" }}</p> | ||
<p class="text-sm">{{ county | default:"Oxfordshire" }}</p> | ||
<p class="text-sm">{{ country | default:"England" }}</p> | ||
</div> | ||
|
||
<!-- REPLACE DEFAULT VALUES WITH WHATEVER IS ALREADY STORED FOR THE INVOICE - if user does not change field, keep whats there--> | ||
<input type="hidden" name="from_name" value="{{ name | default:"John Smith" }}"> | ||
<input type="hidden" name="from_company" value="{{ company | default:"Google" }}"> | ||
<input type="hidden" name="from_address" value="{{ address | default:"128 Example Road" }}"> | ||
<input type="hidden" name="from_city" value="{{ city | default:"Oxford" }}"> | ||
<input type="hidden" name="from_county" value="{{ county | default:"Oxfordshire" }}"> | ||
<input type="hidden" name="from_country" value="{{ country | default:"England" }}"> | ||
{% if not swapping %} | ||
</button> | ||
{% endif %} |
28 changes: 28 additions & 0 deletions
28
frontend/templates/pages/invoices/edit/edit_to_destination.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{% if not swapping %} | ||
<h3 class="text-sm text-natural font-semibold block lg:hidden ms-3">To</h3> | ||
<button onclick="modal_invoices_to_destination.showModal();" id="to_destination" | ||
class="input_card text-left" | ||
hx-trigger="click once" hx-swap="beforeend" hx-target="#modal_container" | ||
hx-get="{% url "api:base:modal retrieve" modal_name="invoices_to_destination" %}"> | ||
{% endif %} | ||
|
||
<!-- PRE-POPULATE FIELDS WITH EXISTING DATA OF THE INVOICE--> | ||
<div class="card-body"> | ||
<p class="text-md">{{ name | default:"John Smith" }}</p> | ||
<p class="text-sm">{{ company | default:"Google" }}</p> | ||
<p class="text-sm">{{ address | default:"128 Example Road" }}</p> | ||
<p class="text-sm">{{ city | default:"Oxford" }}</p> | ||
<p class="text-sm">{{ county | default:"Oxfordshire" }}</p> | ||
<p class="text-sm">{{ country | default:"England" }}</p> | ||
</div> | ||
|
||
<!-- REPLACE DEFAULT VALUES WITH WHATEVER IS ALREADY STORED FOR THE INVOICE - if user does not change field, keep whats there--> | ||
<input type="hidden" name="to_name" value="{{ name | default:"John Smith" }}"> | ||
<input type="hidden" name="to_company" value="{{ company | default:"Google" }}"> | ||
<input type="hidden" name="to_address" value="{{ address | default:"128 Example Road" }}"> | ||
<input type="hidden" name="to_city" value="{{ city | default:"Oxford" }}"> | ||
<input type="hidden" name="to_county" value="{{ county | default:"Oxfordshire" }}"> | ||
<input type="hidden" name="to_country" value="{{ country | default:"England" }}"> | ||
{% if not swapping %} | ||
</button> | ||
{% endif %} |