Skip to content

Commit

Permalink
[revamp] Implement price filter receipt (#341)
Browse files Browse the repository at this point in the history
* implemented the filter for total price similar to invoice

* Added the edit feature and fixed the bug #328

* added the error handling for receipts

* refactor: Removed duplicate step

* fix: Fixed modals having their own IDs
fix: Default values for non-specified ones
fix: Images cant be provided by backend

---------

Signed-off-by: Trey <[email protected]>
Co-authored-by: atanand2 <[email protected]>
Co-authored-by: atulanand25 <[email protected]>
  • Loading branch information
3 people authored Apr 20, 2024
1 parent 0089492 commit fdec63e
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 21 deletions.
19 changes: 18 additions & 1 deletion backend/api/base/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.http import HttpResponseBadRequest
from django.shortcuts import render

from backend.models import Client
from backend.models import Client, Receipt
from backend.models import Invoice
from backend.models import QuotaLimit
from backend.models import Team
Expand All @@ -31,6 +31,23 @@ def open_modal(request: HttpRequest, modal_name, context_type=None, context_valu
elif context_type == "leave_team":
if request.user.teams_joined.filter(id=context_value).exists():
context["team"] = Team.objects.filter(id=context_value).first()
elif context_type == "edit_receipt":
try:
receipt = Receipt.objects.get(pk=context_value)
except Receipt.DoesNotExist:
return render(request, template_name, context)
receipt_date = receipt.date.strftime("%Y-%m-%d") if receipt.date else ""
context = {
"modal_id": f"modal_{receipt.id}_receipts_upload",
"receipt_id": context_value,
"receipt_name": receipt.name,
"receipt_date": receipt_date,
"merchant_store_name": receipt.merchant_store,
"purchase_category": receipt.purchase_category,
"total_price": receipt.total_price,
"has_receipt_image": True if receipt.image else False,
"edit_flag": True,
}
elif context_type == "edit_invoice_to":
invoice = context_value
try:
Expand Down
87 changes: 87 additions & 0 deletions backend/api/receipts/edit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from datetime import datetime
from typing import NoReturn
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.http import HttpRequest, JsonResponse, HttpResponse, HttpResponseBadRequest
from django.shortcuts import render, redirect
from django.views.decorators.http import require_http_methods, require_POST
from backend.models import Receipt


@require_http_methods(["POST"])
@login_required
def edit_receipt(request, receipt_id):
# Fetch the receipt object from the database
try:
receipt = Receipt.objects.get(pk=receipt_id)

if not receipt.has_access(request.user):
raise Receipt.DoesNotExist
except Receipt.DoesNotExist:
messages.error(request, "Receipt not found")
return render(request, "base/toast.html")

file: InMemoryUploadedFile | None = request.FILES.get("receipt_image")
date = request.POST.get("receipt_date")
name = request.POST.get("receipt_name")
merchant_store = request.POST.get("merchant_store")
purchase_category = request.POST.get("purchase_category")
total_price = request.POST.get("total_price")

if not file and not receipt.image:
messages.error(request, "No image found")
return HttpResponseBadRequest("No image found", status=400)

name = file.name.split(".")[0] if not name else name

if not name:
messages.error(request, "No name provided, or image doesn't contain a valid name.")
return HttpResponseBadRequest("No name provided, or image doesn't contain a valid name.", status=400)

if not date:
date = None

# Compare the values with the existing receipt object
if (
name != receipt.name
or file != receipt.image
or date != receipt.date
or merchant_store != receipt.merchant_store
or purchase_category != receipt.purchase_category
or total_price != receipt.total_price
):

# Update the receipt object
if name:
receipt.name = name
if file:
receipt.image = file
if date:
receipt.date = date
if merchant_store:
receipt.merchant_store = merchant_store
if purchase_category:
receipt.purchase_category = purchase_category
if total_price:
receipt.total_price = total_price

receipt.save()

messages.success(request, f"Receipt {receipt.name} (#{receipt.id}) updated successfully.")
else:
messages.info(request, "No changes were made.")

if request.user.logged_in_as_team:
receipt.organization = request.user.logged_in_as_team
receipts = Receipt.objects.filter(organization=request.user.logged_in_as_team).order_by("-date")
else:
receipt.user = request.user
receipts = Receipt.objects.filter(user=request.user).order_by("-date")

# Pass the receipt object to the template for rendering
return render(
request,
"pages/receipts/_search_results.html",
{"receipts": receipts},
)
14 changes: 14 additions & 0 deletions backend/api/receipts/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ def fetch_all_receipts(request: HttpRequest):
return redirect("receipts dashboard")

search_text = request.GET.get("search")
selected_filters = request.GET.get("filter")

# Define previous filters as a dictionary
previous_filters = {
"amount": {
"20": True if request.GET.get("amount_20+") else False,
"50": True if request.GET.get("amount_50+") else False,
"100": True if request.GET.get("amount_100+") else False,
},
}

results = Receipt.objects.order_by("-date")
if request.user.logged_in_as_team:
Expand All @@ -20,6 +30,10 @@ def fetch_all_receipts(request: HttpRequest):

if search_text:
results = results.filter(Q(name__icontains=search_text) | Q(date__icontains=search_text)).order_by("-date")
elif selected_filters:
context.update({"selected_filters": [selected_filters]})
results = results.filter(total_price__gte=selected_filters).order_by("-date")

context.update({"receipts": results})
context["all_filters"] = {item: [i for i, _ in dictio.items()] for item, dictio in previous_filters.items()}
return render(request, "pages/receipts/_search_results.html", context)
7 changes: 6 additions & 1 deletion backend/api/receipts/urls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.urls import path
from . import delete, new, fetch, download
from . import delete, new, fetch, download, edit

urlpatterns = [
path(
Expand All @@ -12,6 +12,11 @@
new.receipt_create,
name="new",
),
path(
"edit/<int:receipt_id>/",
edit.edit_receipt,
name="edit",
),
path(
"fetch/",
fetch.fetch_all_receipts,
Expand Down
40 changes: 27 additions & 13 deletions frontend/templates/modals/receipts_upload.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{% component_block "modal" id="modal_receipts_upload" start_open="true" title="Upload Receipt" %}
{% component_block "modal" id=modal_id start_open="true" title="Upload Receipt" %}
{% fill "content" %}
<form class="py-4"
id="modal_receipts_upload-form"
hx-post="{% url 'api:receipts:new' %}"
id="{{ modal_id }}-form"
hx-post=" {% if edit_flag %}{% url 'api:receipts:edit' receipt_id=receipt_id %}{% else %}{% url 'api:receipts:new' %}{% endif %}"
hx-swap="innerHTML"
enctype="multipart/form-data"
hx-encoding="multipart/form-data"
Expand All @@ -14,30 +14,43 @@
Receipt Name
<span class="required_star">*</span>
</label>
<input required type="text" name="receipt_name" class="input input-bordered">
<input required
type="text"
name="receipt_name"
class="input input-bordered"
value="{{ receipt_name }}">
</div>
<div class="form-control">
<label class="label justify-start">
Receipt Image
<span class="required_star">*</span>
{% if not has_receipt_image %}<span class="required_star">*</span>{% endif %}
</label>
<input required
<input {% if not has_receipt_image %}required{% endif %}
type="file"
name="receipt_image"
class="file-input file-input-bordered max-w-full"
accept="image/jpeg,image/png,image/jpg,application/pdf">
</div>
<div class="form-control">
<label class="label">Receipt Date</label>
<input type="date" name="receipt_date" class="input input-bordered">
<input type="date"
name="receipt_date"
class="input input-bordered"
value="{{ receipt_date }}">
</div>
<div class="form-control">
<label class="label">Merchant/Store</label>
<input type="text" name="merchant_store" class="input input-bordered">
<input type="text"
name="merchant_store"
class="input input-bordered"
value="{{ merchant_store_name | default:"" }}">
</div>
<div class="form-control">
<label class="label">Purchase Category</label>
<input type="text" name="purchase_category" class="input input-bordered">
<input type="text"
name="purchase_category"
class="input input-bordered"
value="{{ purchase_category | default:"" }}">
</div>
<div class="form-control">
<label class="label justify-start">
Expand All @@ -48,18 +61,19 @@
type="number"
step=".01"
name="total_price"
class="input input-bordered">
class="input input-bordered"
value="{{ total_price | default:"0.00" }}">
</div>
<div class="modal-action">
<button type="submit"
id="modal_receipts_upload-action-btn"
id="{{ modal_id }}-action-btn"
class="btn btn-primary"
_="on click if #modal_receipts_upload-form.checkValidity() call #modal_receipts_upload.close() end">
_="on click if #{{ modal_id }}-form.checkValidity() call #{{ modal_id }}.close() end">
Save
</button>
<button type="reset" class="btn btn-error">Reset</button>
<button type="button"
_="on click call #modal_receipts_upload.close()"
_="on click call #{{ modal_id }}.close()"
class="btn btn-neutral">Close</button>
</div>
</form>
Expand Down
74 changes: 69 additions & 5 deletions frontend/templates/pages/receipts/_search_results.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
{% csrf_token %}
<div class="flex w-full overflow-x-auto" id="items">
<table class="table">
<div class="flex w-full h-full overflow-x-auto overflow-y-auto" id="items">
<table class="table h-fit">
<thead>
<tr>
<tr hx-swap="outerHTML"
hx-target="#items"
hx-indicator=""
hx-include="#filter_list_storage">
<th>ID</th>
<th>Date</th>
<th>Name</th>
<th>Merchant/Store</th>
<th>Category</th>
<th>Amount</th>
<th>
<div class="dropdown"
data-filter-type="amount"
hx-vals='{"filter_type": "amount"}'>
<label tabindex="0" class="border-none">
Amount
<i class="fa-solid fa-caret-down border-none pt-1 pl-1/2"></i>
</label>
<ul class="dropdown-content">
<h2 class="menu-title">Amount</h2>
<div class="divider -mt-2 -mb-1"></div>
<li data-filter-by="20+">
<button class="dropdown-item text-sm border-none"
hx-get="{% url "api:receipts:fetch" %}"
name="filter"
value="20">20+</button>
</li>
<li data-filter-by="50+">
<button class="dropdown-item text-sm border-none"
hx-get="{% url "api:receipts:fetch" %}"
name="filter"
value="50">50+</button>
</li>
<li data-filter-by="100+">
<button class="dropdown-item text-sm border-none"
hx-get="{% url "api:receipts:fetch" %}"
name="filter"
value="100">100+</button>
</li>
</ul>
</div>
</th>
<th>Actions</th>
</tr>
</thead>
Expand All @@ -29,8 +63,19 @@
<button type="button"
onclick="modal_receipt_{{ row.id }}.showModal();"
class="btn btn-outline btn-success btn-sm">Preview</button>
<button onclick="modal_{{ row.id }}_receipts_upload.showModal();"
id="edit_receipt_button"
class="btn btn-outline btn-success btn-sm"
for="edit_receipt"
hx-trigger="click once"
hx-swap="beforeend"
hx-target="#modal_container"
hx-get="{% url "api:base:modal retrieve with context" modal_name="receipts_upload" context_type="edit_receipt" context_value=row.id %}">
<i class="fa-solid fa-receipt pe-1"></i>
Edit Receipt
</button>
<button type="button"
class="btn btn-outline btn-primary btn-sm "
class="btn btn-outline btn-primary btn-sm"
onclick="Download_file('{% url 'api:receipts:generate_download_link' receipt_id=row.id %}')">
Download
</button>
Expand Down Expand Up @@ -80,6 +125,25 @@ <h2 class="text-xl">
{% endfor %}
</tbody>
</table>
{% for filter_type, inner_filters in all_filters.items %}
{% for filter in inner_filters %}
<div hx-swap-oob="innerHTML:div[data-filter-type='{{ filter_type }}'] ul li[data-filter-by='{{ filter }}'] button">
{% if filter in selected_filters %}<i class="fa fa-solid fa-check text-success"></i>{% endif %}
{{ filter | title }}
</div>
{% endfor %}
{% endfor %}
</div>
<div hx-swap-oob="innerHTML:#filter_list_storage">
<input type="hidden"
name="amount_20+"
value="{% if '20' in selected_filters %}true{% endif %}">
<input type="hidden"
name="amount_50+"
value="{% if '50' in selected_filters %}true{% endif %}">
<input type="hidden"
name="amount_100+"
value="{% if '100' in selected_filters %}true{% endif %}">
</div>
<script>
document.body.addEventListener('htmx:configRequest', (event) => {
Expand Down
4 changes: 3 additions & 1 deletion frontend/templates/pages/receipts/dashboard.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{% extends 'base/base.html' %}
{% csrf_token %}
{% block content %}
<div class="card w-full p-6 bg-base-100 shadow-xl mt-2">
<form id="filter_list_storage">
</form>
<div class="card bg-base-100 p-6 mb-4 h-screen">
<h2 class="text-xl">Receipts</h2>
<button onclick="modal_receipts_upload.showModal();"
id="upload_receipt_button"
Expand Down

0 comments on commit fdec63e

Please sign in to comment.