Skip to content

Commit

Permalink
Added invoice products
Browse files Browse the repository at this point in the history
  • Loading branch information
TreyWW committed Dec 18, 2023
1 parent f0af52e commit 9175dc9
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 34 deletions.
2 changes: 2 additions & 0 deletions backend/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Team,
TeamInvitation,
User,
InvoiceProduct,
)
from django.contrib import admin

Expand All @@ -32,6 +33,7 @@
admin.site.register(Notification)
admin.site.register(Team)
admin.site.register(TeamInvitation)
admin.site.register(InvoiceProduct)
admin.site.register(User, UserAdmin)

admin.site.site_header = "MyFinances Admin"
Expand Down
79 changes: 52 additions & 27 deletions backend/api/invoices/create/services/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@
from django.shortcuts import render
from django.views.decorators.http import require_http_methods

from backend.models import InvoiceProduct


@require_http_methods(["POST"])
def add_service(request: HttpRequest):
context = {}
existing_service = request.POST.get("existing_service")

try:
existing_service_obj = InvoiceProduct.objects.get(
user=request.user, id=existing_service
)
except InvoiceProduct.DoesNotExist:
existing_service_obj = None

list_hours = request.POST.getlist("hours[]")
list_service_name = request.POST.getlist("service_name[]")
list_service_description = request.POST.getlist("service_description[]")
Expand All @@ -17,25 +28,27 @@ def add_service(request: HttpRequest):
)
]

hours = int(request.POST.get("post_hours"))
service_name = request.POST.get("post_service_name")
service_description = request.POST.get("post_service_description")
price_per_hour = int(request.POST.get("post_rate"))
if not existing_service:
hours = int(request.POST.get("post_hours"))
service_name = request.POST.get("post_service_name")
service_description = request.POST.get("post_service_description")
price_per_hour = int(request.POST.get("post_rate"))

if not hours:
return JsonResponse(
{"status": "false", "message": "The hours field is required"}, status=400
)
if not service_name:
return JsonResponse(
{"status": "false", "message": "The service name field is required"},
status=400,
)
if not price_per_hour:
return JsonResponse(
{"status": "false", "message": "The price per hour field is required"},
status=400,
)
if not hours:
return JsonResponse(
{"status": "false", "message": "The hours field is required"},
status=400,
)
if not service_name:
return JsonResponse(
{"status": "false", "message": "The service name field is required"},
status=400,
)
if not price_per_hour:
return JsonResponse(
{"status": "false", "message": "The price per hour field is required"},
status=400,
)

context["rows"] = []
for row in list_of_current_rows:
Expand All @@ -49,14 +62,26 @@ def add_service(request: HttpRequest):
}
)

context["rows"].append(
{
"hours": hours,
"service_name": service_name,
"service_description": service_description,
"price_per_hour": price_per_hour,
"total_price": hours * price_per_hour,
}
)
if existing_service and existing_service_obj:
context["rows"].append(
{
"hours": existing_service_obj.quantity,
"service_name": existing_service_obj.name,
"service_description": existing_service_obj.description,
"price_per_hour": existing_service_obj.rate,
"total_price": existing_service_obj.quantity
* existing_service_obj.rate,
}
)
else:
context["rows"].append(
{
"hours": hours,
"service_name": service_name,
"service_description": service_description,
"price_per_hour": price_per_hour,
"total_price": hours * price_per_hour,
}
)

return render(request, "pages/invoices/create/_services_table_body.html", context)
22 changes: 22 additions & 0 deletions backend/api/products/fetch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.db.models import Q
from django.http import HttpRequest
from django.shortcuts import render

from backend.models import InvoiceProduct


def fetch_products(request: HttpRequest):
results = []
search_text = request.GET.get("search_existing_service")
if search_text:
results = (
InvoiceProduct.objects.filter(user=request.user)
.filter(
Q(name__icontains=search_text) | Q(description__icontains=search_text)
)
.order_by("name")
)
else:
results = InvoiceProduct.objects.filter(user=request.user).order_by("name")

return render(request, "pages/products/fetched_items.html", {"products": results})
9 changes: 9 additions & 0 deletions backend/api/products/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path

from . import fetch

urlpatterns = [
path("fetch/", fetch.fetch_products, name="fetch"),
]

app_name = "products"
1 change: 1 addition & 0 deletions backend/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
path("invoices/", include("backend.api.invoices.urls")),
path("clients/", include("backend.api.clients.urls")),
path("settings/", include("backend.api.settings.urls")),
path("products/", include("backend.api.products.urls")),
]

app_name = "api"
44 changes: 44 additions & 0 deletions backend/migrations/0005_invoiceproduct.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 4.1.7 on 2023-12-18 13:24

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
("backend", "0004_invoice_client_is_representative"),
]

operations = [
migrations.CreateModel(
name="InvoiceProduct",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=50)),
("description", models.CharField(max_length=100)),
("quantity", models.IntegerField()),
(
"rate",
models.DecimalField(
blank=True, decimal_places=2, max_digits=15, null=True
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
8 changes: 8 additions & 0 deletions backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ def __str__(self):
return self.name


class InvoiceProduct(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
description = models.CharField(max_length=100)
quantity = models.IntegerField()
rate = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True)


class InvoiceItem(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=100)
Expand Down
3 changes: 2 additions & 1 deletion backend/views/core/invoices/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views.decorators.http import require_http_methods
from backend.models import Invoice, InvoiceItem, Client
from backend.models import Invoice, InvoiceItem, Client, InvoiceProduct
from datetime import datetime


def invoice_page_get(request: HttpRequest):
context = {
"clients": Client.objects.filter(user=request.user),
"existing_products": InvoiceProduct.objects.filter(user=request.user),
}
return render(request, "pages/invoices/create/create.html", context)

Expand Down
29 changes: 29 additions & 0 deletions frontend/static/src/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -4084,6 +4084,10 @@ html {
margin: auto;
}

.m-2 {
margin: 0.5rem;
}

.-mx-4 {
margin-left: -1rem;
margin-right: -1rem;
Expand Down Expand Up @@ -4449,6 +4453,10 @@ html {
width: 20rem;
}

.w-\[100\%\] {
width: 100%;
}

.w-\[150px\] {
width: 150px;
}
Expand All @@ -4461,6 +4469,10 @@ html {
width: 30%;
}

.w-\[50\%\] {
width: 50%;
}

.w-\[60\%\] {
width: 60%;
}
Expand All @@ -4469,6 +4481,14 @@ html {
width: 100%;
}

.w-1\/3 {
width: 33.333333%;
}

.w-2\/3 {
width: 66.666667%;
}

.min-w-0 {
min-width: 0px;
}
Expand Down Expand Up @@ -4642,6 +4662,11 @@ html {
row-gap: 0.25rem;
}

.gap-x-2 {
-moz-column-gap: 0.5rem;
column-gap: 0.5rem;
}

.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
Expand Down Expand Up @@ -4909,6 +4934,10 @@ html {
padding: 1.5rem;
}

.p-8 {
padding: 2rem;
}

.px-0 {
padding-left: 0px;
padding-right: 0px;
Expand Down
41 changes: 35 additions & 6 deletions frontend/templates/pages/invoices/create/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,41 @@ <h3 class="text-sm text-natural font-semibold hidden lg:block text-end me-6">To<
<div class="divider my-4">STEP 3 - SERVICES</div>
<div class="input_card max-w-full min-w-full">
<div class="card-body">
<button onclick="modal_invoices_add_service.showModal();"
class="btn btn-primary"
hx-trigger="click once" hx-swap="beforeend" hx-target="#modal_container"
hx-get="{% url "api:base:modal retrieve" modal_name="invoices_add_service" %}">
Add Service
</button>
<div class="flex py-4 gap-x-3">
<div class="dropdown w-1/3">
<div tabindex="0" role="button" class="btn btn-primary w-full">
Existing Service
</div>
<ul tabindex="0" class="dropdown-content z-[1] menu p-8 shadow bg-base-200 rounded-box max-w-full
w-full min-w-full">
<input placeholder="🔎 Search" type="text" name="search_existing_service" class="input
input-bordered w-full"
hx-get="{% url 'api:products:fetch' %}"
hx-target="#search_existing_products_items"
hx-trigger="keyup changed delay:500ms,load"
hx-indicator="#search_loading">
<div class="divider">ITEMS (search for > 3)</div>
<span id="search_loading" class="loading loading-spinner loading-htmx-loader-individual"></span>
<div id="search_existing_products_items">
</div>
</ul>
</div>
<script>
const buttons = document.querySelectorAll("button[data-b_type='add_product']")

buttons.forEach(btn => {
btn.addEventListener("click", () => {
$.post("{% url 'api:invoices:services add' %}", {});
})
});
</script>
<button onclick="modal_invoices_add_service.showModal();"
class="btn btn-primary w-2/3"
hx-trigger="click once" hx-swap="beforeend" hx-target="#modal_container"
hx-get="{% url "api:base:modal retrieve" modal_name="invoices_add_service" %}">
Add New Service
</button>
</div>
{# START MODAL #}
{# <div class="modal">#}
{# <div class="form-field w-full">#}
Expand Down
14 changes: 14 additions & 0 deletions frontend/templates/pages/products/fetched_items.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{# This is for the INVOICE CREATION dropdown #}
{% for product in products %}
<li>
<button hx-post="{% url 'api:invoices:services add' %}" hx-include="#services_table_body"
hx-swap="innerHTML"
hx-target="#services_table_body"
hx-vals='{"existing_service": "{{ product.id }}"}'>
{{ product.name }} <br> {{ product.description }}
<div class="btn btn-primary btn-sm btn-circle absolute right-2">
<i class="fa-solid fa-plus"></i>
</div>
</button>
</li>
{% endfor %}

0 comments on commit 9175dc9

Please sign in to comment.