Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding invoice editing functionality to MyFinances #153

Merged
merged 16 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
96952de
Create edit.py and add the base edit invoice function
Tianrui-Luo Dec 12, 2023
1f2d739
Merge branch 'TreyWW:main' into feature/invoices/edit_invoice
HarryHuCodes Dec 12, 2023
ee17863
feat: add require_method of EDIT, allow users to update contributes
Nuovaxu Dec 12, 2023
f7012e1
Update urls.py to correctly navigate to the invoice edit page
Tianrui-Luo Dec 12, 2023
3ed3a89
Update _fetch_body.html to activate the edit function from frontend w…
Tianrui-Luo Dec 12, 2023
bb9e564
Added temporary html pages for editing invoice
HarryHuCodes Dec 12, 2023
0086f06
Configured backend.views.core.invoices.edit and wrote the view functi…
HarryHuCodes Dec 12, 2023
10cd4c9
Fixed argument id error when loading page invoices/edit/<str:id> and …
HarryHuCodes Dec 12, 2023
3cc9a2a
Update edit.py by fixing the "invoice_id not found" problem and invoi…
Tianrui-Luo Dec 12, 2023
cd5d130
Update fetch.py by fixing the problem of unable to switch payment status
Tianrui-Luo Dec 12, 2023
513c50f
Update edit.py by redirecting to invoice page after successfully edit…
Tianrui-Luo Dec 12, 2023
cfe1586
Merge branch 'TreyWW:main' into feature/invoices/edit_invoice
Tianrui-Luo Dec 12, 2023
f04df6b
Added method to store existing invoice data in a dict based on id and…
HarryHuCodes Dec 13, 2023
2adec21
Added comments and ran black formatter for code organization and clarity
HarryHuCodes Dec 13, 2023
dc51f82
Merge branch 'feature/invoices/edit_invoice' of https://github.com/Ha…
HarryHuCodes Dec 13, 2023
140714c
Merge branch 'main' into feature/invoices/edit_invoice
TreyWW Dec 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
15 changes: 10 additions & 5 deletions backend/api/invoices/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ def fetch_all_invoices(request: HttpRequest):
)
)



# Initialize context variables
context["selected_filters"] = []
context["all_filters"] = {
Expand Down Expand Up @@ -98,11 +96,18 @@ def fetch_all_invoices(request: HttpRequest):
# Combine OR conditions for each filter type with AND
or_conditions &= or_conditions_filter

#check/update payment status to make sure it is correct before invoices are filtered and displayed
# check/update payment status to make sure it is correct before invoices are filtered and displayed
for items in invoices:
if (items.date_due and timezone.now().date() > items.date_due) and items.payment_status == "pending":
if (
items.date_due and timezone.now().date() > items.date_due
) 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 All @@ -113,7 +118,7 @@ def fetch_all_invoices(request: HttpRequest):
context["sort"] = sort_by

# Add invoices to the context

context["invoices"] = invoices

# Render the HTMX response
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: 7 additions & 4 deletions backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from shortuuid.django_fields import ShortUUIDField



class CustomUserManager(UserManager):
def get_queryset(self):
return super().get_queryset().select_related("user_profile")
Expand Down Expand Up @@ -161,7 +160,7 @@ def get_total_price(self):
def __str__(self):
return self.description


class Invoice(models.Model):
STATUS_CHOICES = (
("pending", "Pending"),
Expand Down Expand Up @@ -210,11 +209,15 @@ class Invoice(models.Model):

@property
def dynamic_payment_status(self):
if self.date_due and timezone.now().date() > self.date_due and self.payment_status == "pending":
if (
self.date_due
and timezone.now().date() > self.date_due
and self.payment_status == "pending"
):
return "overdue"
else:
return self.payment_status

def __str__(self):
invoice_id = self.invoice_id or self.id
if self.client_name:
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
9 changes: 8 additions & 1 deletion backend/views/core/invoices/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def invoice_page_post(request: HttpRequest):

invoice = Invoice.objects.create(
user=request.user,
date_due=datetime.strptime(request.POST.get("date_due"), '%Y-%m-%d').date(),
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"),
Expand Down 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)
1 change: 1 addition & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- [What is our project]()
- [Get setup with our project](getting-setup/)
- [How to contribute to our project](how-to-contribute)
- [View Changelog](changelog)
16 changes: 16 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Changelog

## [v0.1.1](https://github.com/TreyWW/MyFinances/releases/tag/v0.1.1) <small>([view diff](https://github.com/TreyWW/MyFinances/compare/v0.1.0...v0.1.1))</small>

### Features
* Added documentation
* Added social logins (github + google)
* Added custom colours for tailwind theme
* Added signal for UserSettings addition on user creation

## [v0.1.0](https://github.com/TreyWW/MyFinances/releases/tag/v0.1.0)

First release of the project. Some core features that we made a start to:
- 🧾 Receipts
- 📜 Invoices
- 🧔 Account Management
3 changes: 3 additions & 0 deletions docs/getting-setup/_sidebar.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
- [Home](/)
- [PyCharm Professional IDE](getting-setup/pycharm/fork)
- [Other Environments](getting-setup/other-environments/)
- [Social Login Setup](getting-setup/social-logins?id=social-login)
* [Github](getting-setup/social-logins?id=set-up-social-login-with-github)
* [Google](getting-setup/social-logins?id=set-up-social-login-with-google)
5 changes: 3 additions & 2 deletions docs/getting-setup/social-logins.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Social Login

MyFinances supports social login with manual details and GitHub OAuth.
> ! These docs show you how to get setup locally with these
> logins, but on production make sure to replace `http://127.0.0.1:8000` with `https://` and then your domain name.

!> These docs show you how to get setup locally with these
logins, but on production make sure to replace `http://127.0.0.1:8000` with `https://` and then your domain name.

## Set up Social Login with GitHub

Expand Down
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
Loading
Loading