Skip to content

Commit

Permalink
Adding invoice editing functionality to MyFinances (#153)
Browse files Browse the repository at this point in the history
* 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
4 people authored Dec 13, 2023
1 parent 03f2e14 commit dccc610
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 18 deletions.
50 changes: 50 additions & 0 deletions backend/api/invoices/edit.py
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)
5 changes: 5 additions & 0 deletions backend/api/invoices/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ def fetch_all_invoices(request: HttpRequest):
) and items.payment_status == "pending":
items.payment_status = "overdue"
items.save()
if (
items.date_due and timezone.now().date() < items.date_due
) and items.payment_status == "overdue":
items.payment_status = "pending"
items.save()

# Apply OR conditions to the invoices queryset
invoices = invoices.filter(or_conditions)
Expand Down
7 changes: 6 additions & 1 deletion backend/api/invoices/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.urls import path

from . import fetch, delete
from . import fetch, delete, edit
from .create import set_destination
from .create.services import add, remove

Expand Down Expand Up @@ -30,6 +30,11 @@
delete.delete_invoice,
name="delete",
),
path(
"edit/",
edit.edit_invoice,
name="edit",
),
path("fetch/", fetch.fetch_all_invoices, name="fetch"),
]

Expand Down
11 changes: 9 additions & 2 deletions backend/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.contrib import admin
from django.urls import re_path as url, path, include
from django.views.static import serve
from .views.core.invoices import edit

from backend.views.core import (
other,
Expand Down Expand Up @@ -115,9 +116,15 @@
invoices.create.create_invoice_page,
name="invoices dashboard create",
),
# path(
# "dashboard/invoices/<str:id>",
# invoices.dashboard.invoices_dashboard_id,
# name="invoices dashboard edit",
# ),
path(
"dashboard/invoices/<str:id>",
invoices.dashboard.invoices_dashboard_id,
"dashboard/invoices/edit/<str:id>",
invoices.edit.edit_invoice_page,
# invoices.edit.invoice_edit_page_get,
name="invoices dashboard edit",
),
# path('dashboard/invoices/<str:id>/edit', invoices.dashboard.invoices_dash~board_id, name='invoices dashboard'),
Expand Down
7 changes: 7 additions & 0 deletions backend/views/core/invoices/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,10 @@ def create_invoice_page(request: HttpRequest):
if request.method == "POST":
return invoice_page_post(request)
return invoice_page_get(request)


@require_http_methods(["GET", "POST"])
def edit_invoice_page(request: HttpRequest):
if request.method == "POST":
return invoice_page_post(request)
return invoice_page_get(request)
94 changes: 94 additions & 0 deletions backend/views/core/invoices/edit.py
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)
29 changes: 14 additions & 15 deletions frontend/templates/pages/invoices/dashboard/_fetch_body.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,23 @@
Manage Access
</a>
</li>
<div class="tooltip tooltip-left" data-tip="These actions aren't yet added.">
<li class="disabled">
<a href="">
<i class="fa-solid fa-regular fa-pencil"></i>
Edit
</a>
</li>
<li>
<button hx-delete="{% url 'api:invoices:delete' %}"

<li>
<a href="{% url "invoices dashboard edit" id=invoice.id %}">
<i class="fa-solid fa-regular fa-pencil"></i>
Edit
</a>
</li>
<li>
<button hx-delete="{% url 'api:invoices:delete' %}"
hx-target="closest tr"
hx-confirm="Are you sure you would like to delete invoice #{{ invoice.id }}?"
hx-vals='{"invoice": "{{ invoice.id }}" }'
>
<i class="fa-solid fa-trash"></i>
Delete
</button>
</li>
</div>
>
<i class="fa-solid fa-trash"></i>
Delete
</button>
</li>
</ul>
</div>
</td>
Expand Down
72 changes: 72 additions & 0 deletions frontend/templates/pages/invoices/edit/edit.html
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-[&amp;: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-[&amp;: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 frontend/templates/pages/invoices/edit/edit_from_destination.html
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 frontend/templates/pages/invoices/edit/edit_to_destination.html
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 %}

0 comments on commit dccc610

Please sign in to comment.