Merge pull request 'Search and bill detail page' (#135) from frontend into main
Reviewed-on: #135
This commit is contained in:
commit
19b2913a4e
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-09 13:00
|
||||
# Generated by Django 5.1.7 on 2025-07-10 12:55
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-09 13:00
|
||||
# Generated by Django 5.1.7 on 2025-07-10 12:55
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-09 13:00
|
||||
# Generated by Django 5.1.7 on 2025-07-10 12:55
|
||||
|
||||
import datetime
|
||||
import django.core.serializers.json
|
||||
@ -22,7 +22,7 @@ class Migration(migrations.Migration):
|
||||
('appointment', '0001_initial'),
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('django_ledger', '0023_merge_20250708_1825'),
|
||||
('django_ledger', '0021_alter_bankaccountmodel_account_model_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
|
||||
@ -2595,7 +2595,7 @@ class CustomGroup(models.Model):
|
||||
"activity",
|
||||
"opportunity",
|
||||
"vendor",
|
||||
"customer"
|
||||
"customer",
|
||||
"notes",
|
||||
"tasks",
|
||||
"activity",
|
||||
@ -2707,7 +2707,8 @@ class CustomGroup(models.Model):
|
||||
"notes",
|
||||
"tasks",
|
||||
"activity",
|
||||
"payment"],
|
||||
"payment",
|
||||
'vendor'],
|
||||
other_perms=[
|
||||
"view_car",
|
||||
"view_carlocation",
|
||||
@ -2715,6 +2716,9 @@ class CustomGroup(models.Model):
|
||||
"view_carcolors",
|
||||
"view_cartransfer",
|
||||
"view_saleorder",
|
||||
"view_leads",
|
||||
"view_opportunity",
|
||||
"view_customers",
|
||||
|
||||
],
|
||||
)
|
||||
|
||||
@ -2496,7 +2496,7 @@ class VendorListView(LoginRequiredMixin,PermissionRequiredMixin, ListView):
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('django_ledger.view_vendormodel',raise_exception=True)
|
||||
@permission_required('inventory.view_vendor',raise_exception=True)
|
||||
def vendorDetailView(request, dealer_slug,slug):
|
||||
"""
|
||||
Fetches and renders the detail view for a specific vendor.
|
||||
@ -3862,6 +3862,15 @@ class BankAccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
|
||||
template_name = "ledger/bank_accounts/bank_account_detail.html"
|
||||
context_object_name = "bank_account"
|
||||
permission_required = ["django_ledger.view_bankaccountmodel"]
|
||||
|
||||
def get_queryset(self):
|
||||
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||
query=self.request.GET.get('q')
|
||||
qs=self.model.objects.filter(entity=dealer.entity)
|
||||
if query:
|
||||
qs=apply_search_filters(qs,query)
|
||||
return qs
|
||||
return qs
|
||||
|
||||
|
||||
class BankAccountUpdateView(
|
||||
@ -5580,14 +5589,23 @@ class LeadListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
query = self.request.GET.get("q")
|
||||
qs = models.Lead.objects.filter(dealer=dealer).exclude(status="converted")
|
||||
if query:
|
||||
qs = apply_search_filters(qs, query)
|
||||
qs = qs.filter(Q(first_name__icontains=query)
|
||||
| Q(last_name__icontains=query)
|
||||
| Q(id_car_make__name__icontains=query)
|
||||
| Q(id_car_model__name__icontains=query)
|
||||
| Q(email__icontains=query)
|
||||
| Q(phone_number__icontains=query)
|
||||
| Q(next_action__icontains=query)
|
||||
| Q(staff__name__icontains=query))
|
||||
|
||||
if self.request.is_dealer:
|
||||
return qs
|
||||
if self.request.user.is_staff:
|
||||
staff = getattr(self.request.user.staffmember, "staff", None)
|
||||
return qs.filter(staff=staff)
|
||||
return models.Lead.objects.none()
|
||||
|
||||
|
||||
|
||||
|
||||
class LeadDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
"""
|
||||
@ -6650,12 +6668,15 @@ class OpportunityListView(LoginRequiredMixin,PermissionRequiredMixin, ListView):
|
||||
queryset = models.Opportunity.objects.filter(dealer=dealer, lead__staff=staff)
|
||||
|
||||
# Search filter
|
||||
search = self.request.GET.get("search")
|
||||
search = self.request.GET.get("q")
|
||||
if search:
|
||||
queryset = queryset.filter(
|
||||
Q(customer__customer_name__icontains=search)
|
||||
Q(customer__first_name__icontains=search)
|
||||
| Q(customer__last_name__icontains=search)
|
||||
| Q(customer__email__icontains=search)
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
|
||||
# Stage filter
|
||||
stage = self.request.GET.get("stage")
|
||||
@ -6943,10 +6964,17 @@ class ItemServiceListView(LoginRequiredMixin, PermissionRequiredMixin, ListView)
|
||||
paginate_by = 30
|
||||
permission_required = ["inventory.view_additionalservices"]
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
dealer = get_user_type(self.request)
|
||||
return models.AdditionalServices.objects.filter(dealer=dealer).all()
|
||||
query=self.request.GET.get('q')
|
||||
qs= models.AdditionalServices.objects.filter(dealer=dealer).all()
|
||||
if query:
|
||||
qs=apply_search_filters(qs,query)
|
||||
return qs
|
||||
|
||||
# def get_queryset(self):
|
||||
# dealer = get_user_type(self.request)
|
||||
# return models.AdditionalServices.objects.filter(dealer=dealer).all()
|
||||
|
||||
|
||||
class ItemExpenseCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
|
||||
@ -7064,9 +7092,18 @@ class ItemExpenseListView(LoginRequiredMixin, PermissionRequiredMixin, ListView)
|
||||
paginate_by = 30
|
||||
permission_required = ["django_ledger.view_itemmodel"]
|
||||
|
||||
# def get_queryset(self):
|
||||
# dealer = get_user_type(self.request)
|
||||
# return dealer.entity.get_items_expenses()
|
||||
|
||||
def get_queryset(self):
|
||||
dealer = get_user_type(self.request)
|
||||
return dealer.entity.get_items_expenses()
|
||||
query=self.request.GET.get('q')
|
||||
qs= dealer.entity.get_items_expenses()
|
||||
if query:
|
||||
qs=apply_search_filters(qs,query)
|
||||
return qs
|
||||
|
||||
|
||||
|
||||
class BillListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
@ -7096,6 +7133,9 @@ class BillListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
def get_queryset(self):
|
||||
dealer = get_user_type(self.request)
|
||||
qs = dealer.entity.get_bills()
|
||||
query=self.request.GET.get('q')
|
||||
if query:
|
||||
qs=qs.filter(vendor__vendor_name__icontains=query)
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@ -9921,10 +9961,19 @@ class PurchaseOrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListVie
|
||||
template_name = "purchase_orders/po_list.html"
|
||||
permission_required = ["django_ledger.view_purchaseordermodel"]
|
||||
|
||||
# def get_queryset(self):
|
||||
# dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||
# return self.model.objects.filter(entity=dealer.entity)
|
||||
def get_queryset(self):
|
||||
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||
return self.model.objects.filter(entity=dealer.entity)
|
||||
|
||||
query=self.request.GET.get("q")
|
||||
qs=self.model.objects.filter(entity=dealer.entity)
|
||||
if query:
|
||||
qs=apply_search_filters(qs,query)
|
||||
return qs
|
||||
|
||||
|
||||
|
||||
# def get_queryset(self):
|
||||
# dealer = get_user_type(self.request)
|
||||
# entity = dealer.entity
|
||||
@ -10007,7 +10056,7 @@ class BasePurchaseOrderActionActionView(BasePurchaseOrderActionActionViewBase):
|
||||
|
||||
class PurchaseOrderModelDeleteView(PurchaseOrderModelDeleteViewBase):
|
||||
template_name = "purchase_orders/po_delete.html"
|
||||
permission_required = "django_ledger.delete_purchaseordermodel"
|
||||
permission_required = "django_ledger.delete_purchaseordermodel"
|
||||
|
||||
def get_success_url(self):
|
||||
messages.add_message(
|
||||
|
||||
@ -36,210 +36,232 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row g-4" >
|
||||
<!-- Left Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm" >
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_bill.html' with dealer_slug=request.dealer.slug bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
|
||||
|
||||
<div class="d-grid mt-4">
|
||||
<a href="{% url 'bill_list' request.dealer.slug %}"
|
||||
class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans 'Bill List' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3 mb-2">
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="col-lg-8 ">
|
||||
{% if bill.is_configured %}
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="row text-center g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Cash Account' %}:
|
||||
<a href="{% url 'account_detail' request.dealer.slug bill.cash_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.cash_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="mb-0" id="djl-bill-detail-amount-paid">
|
||||
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% if bill.accrue %}
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Prepaid Account' %}:
|
||||
<a href="{% url 'account_detail' request.dealer.slug bill.prepaid_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.prepaid_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="text-success mb-0" id="djl-bill-detail-amount-prepaid">
|
||||
{% currency_symbol %}{{ bill.get_amount_prepaid | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Accounts Payable' %}:
|
||||
<a href="{% url 'account_detail' request.dealer.slug bill.unearned_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.unearned_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-unearned">
|
||||
{% currency_symbol %}{{ bill.get_amount_unearned | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Accrued' %} {{ bill.get_progress | percentage }}
|
||||
</h6>
|
||||
<h4 class="mb-0">
|
||||
{% currency_symbol %}{{ bill.get_amount_earned | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-md-3 offset-md-6">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'You Still Owe' %}
|
||||
</h6>
|
||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-owed">
|
||||
{% currency_symbol %}{{ bill.get_amount_open | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="row">
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_bill.html' with dealer_slug=request.dealer.slug bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
|
||||
|
||||
<!-- Bill Items Card -->
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-receipt me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Items' %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pt-0 pb-2">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead >
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Item' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Entity Unit' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Unit Cost' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Quantity' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Total' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle " scope="col">{% trans 'PO' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list fs-9" id="project-list-table-body">
|
||||
{% for bill_item in itemtxs_qs %}
|
||||
<tr>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<div class="d-flex px-2 py-1">
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<h6 class="mb-0 text-sm">{{ bill_item.item_model }}</h6>
|
||||
</div>
|
||||
<div class="d-grid mt-4">
|
||||
<a href="{% url 'bill_list' request.dealer.slug %}" class="btn btn-phoenix-primary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans 'Bill List' %}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% if bill_item.entity_unit %}
|
||||
{{ bill_item.entity_unit }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{{ bill_item.unit_cost | currency_format }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">{{ bill_item.quantity }}</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{{ bill_item.total_amount | currency_format }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-items-start white-space-nowrap pe-2">
|
||||
{% if bill_item.po_model_id %}
|
||||
{% if perms.django_ledger.view_purchaseordermodel%}
|
||||
<a class="btn btn-sm btn-phoenix-primary"
|
||||
href="{% url 'purchase_order_detail' request.dealer.slug request.dealer.entity.slug bill_item.po_model_id %}">
|
||||
{% trans 'View PO' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3"></td>
|
||||
<td class="text-end"><strong>{% trans 'Total' %}</strong></td>
|
||||
<td class="text-end">
|
||||
<strong>
|
||||
{% currency_symbol %}{{ total_amount__sum | currency_format }}
|
||||
</strong>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-12">
|
||||
|
||||
<div class="card shadow-sm ">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-sticky-note me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Notes' %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.django_ledger.change_billmodel%}
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_markdown.html' with style='card_1' title='' notes_html=bill.notes_html %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bill Transactions Card -->
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-exchange-alt me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Transactions' %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pt-0 pb-2 table-responsive">
|
||||
{% transactions_table bill %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bill Notes Card -->
|
||||
<div class="card shadow-sm ">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-sticky-note me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Notes' %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.django_ledger.change_billmodel%}
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_markdown.html' with style='card_1' title='' notes_html=bill.notes_html %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-12 col-md-9">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% if bill.is_configured %}
|
||||
<div class="card mb-3 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="row text-center g-3">
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Cash Account' %}:
|
||||
<a href="{% url 'account_detail' request.dealer.slug bill.cash_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.cash_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="mb-0" id="djl-bill-detail-amount-paid">
|
||||
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% if bill.accrue %}
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Prepaid Account' %}:
|
||||
<a href="{% url 'account_detail' request.dealer.slug bill.prepaid_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.prepaid_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="text-success mb-0" id="djl-bill-detail-amount-prepaid">
|
||||
{% currency_symbol %}{{ bill.get_amount_prepaid | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Accounts Payable' %}:
|
||||
<a href="{% url 'account_detail' request.dealer.slug bill.unearned_account.uuid %}"
|
||||
class="text-decoration-none ms-1">
|
||||
{{ bill.unearned_account.code }}
|
||||
</a>
|
||||
</h6>
|
||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-unearned">
|
||||
{% currency_symbol %}{{ bill.get_amount_unearned | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'Accrued' %} {{ bill.get_progress | percentage }}
|
||||
</h6>
|
||||
<h4 class="mb-0">
|
||||
{% currency_symbol %}{{ bill.get_amount_earned | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-12 col-md-3 offset-md-6">
|
||||
<div class="border rounded p-3">
|
||||
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||
{% trans 'You Still Owe' %}
|
||||
</h6>
|
||||
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-owed">
|
||||
{% currency_symbol %}{{ bill.get_amount_open | currency_format }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
|
||||
<div class="card mb-3 shadow-sm">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-receipt me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Items' %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pt-0 pb-2">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Item' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Entity Unit' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Unit Cost' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Quantity' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Total' %}</th>
|
||||
<th class="sort white-space-nowrap align-middle " scope="col">{% trans 'PO' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list fs-9" id="project-list-table-body">
|
||||
{% for bill_item in itemtxs_qs %}
|
||||
<tr>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<div class="d-flex px-2 py-1">
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<h6 class="mb-0 text-sm">{{ bill_item.item_model }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{% if bill_item.entity_unit %}
|
||||
{{ bill_item.entity_unit }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{{ bill_item.unit_cost | currency_format }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">{{ bill_item.quantity }}</span>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<span class="text-xs font-weight-bold">
|
||||
{{ bill_item.total_amount | currency_format }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-items-start white-space-nowrap pe-2">
|
||||
{% if bill_item.po_model_id %}
|
||||
{% if perms.django_ledger.view_purchaseordermodel%}
|
||||
<a class="btn btn-sm btn-phoenix-primary"
|
||||
href="{% url 'purchase_order_detail' request.dealer.slug request.dealer.entity.slug bill_item.po_model_id %}">
|
||||
{% trans 'View PO' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3"></td>
|
||||
<td class="text-end"><strong>{% trans 'Total' %}</strong></td>
|
||||
<td class="text-end">
|
||||
<strong>
|
||||
{% currency_symbol %}{{ total_amount__sum | currency_format }}
|
||||
</strong>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
|
||||
<div class="card mb-3 shadow-sm">
|
||||
<div class="card-header pb-0">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-exchange-alt me-3 text-primary"></i>
|
||||
<h5 class="mb-0">{% trans 'Bill Transactions' %}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body px-0 pt-0 pb-2 table-responsive">
|
||||
{% transactions_table bill %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% include "bill/includes/mark_as.html" %}
|
||||
{% endblock %}
|
||||
@ -18,7 +18,12 @@
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
{% include 'bill/includes/card_bill.html' with dealer_slug=request.dealer.slug bill=bill_model style='bill-detail' entity_slug=view.kwargs.entity_slug %}
|
||||
<form action="{% url 'bill-update' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}" method="post">
|
||||
<a href="{% url 'bill-detail' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}"
|
||||
class="btn btn-phoenix-secondary w-100 mb-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>{% trans 'Back to Bill Detail' %}
|
||||
</a>
|
||||
|
||||
<form action="{% url 'bill-update' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
@ -29,18 +34,15 @@
|
||||
<i class="fas fa-save me-2"></i>{% trans 'Save Bill' %}
|
||||
</button>
|
||||
|
||||
<a href="{% url
|
||||
<a href="{% url 'bill-detail' dealer_slug=request.dealer.slug entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}"
|
||||
class="btn btn-phoenix-secondary w-100 mb-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>{% trans 'Back to Bill Detail' %}
|
||||
</a>
|
||||
|
||||
|
||||
<a href="{% url 'bill_list' request.dealer.slug %}"
|
||||
class="btn btn-phoenix-info w-100 mb-2">
|
||||
<i class="fas fa-list me-2"></i>{% trans 'Bill List' %}
|
||||
</a>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
<a href="{% url 'bill_list' request.dealer.slug %}"
|
||||
class="btn btn-phoenix-info w-100 mb-2">
|
||||
<i class="fas fa-list me-2"></i>{% trans 'Bill List' %}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -50,18 +50,20 @@
|
||||
<div class="" dir="ltr">{{ _("Phone Number") }}</div>
|
||||
</div>
|
||||
</th>
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center px-1 py-1 bg-primary-subtle rounded me-2"><span class="text-primary-dark" data-feather="phone"></span></div>
|
||||
<span>{{ _("Schedule") }}</span>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="zap"></span></div>
|
||||
<span>{{ _("Action")|capfirst }}</span>
|
||||
<span>{{ _("Next Action")|capfirst }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center px-1 py-1 bg-primary-subtle rounded me-2"><span class="far fa-calendar-alt" ></span></div>
|
||||
<span>{{ _("Scheduled at") }}</span>
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center bg-success-subtle rounded me-2"><span class="text-success-dark" data-feather="user-check"></span></div>
|
||||
@ -69,15 +71,16 @@
|
||||
</div>
|
||||
</th>
|
||||
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
||||
{% comment %} <th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 10%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="grid"></span></div>
|
||||
<span>{{ _("Opportunity")|capfirst }}</span>
|
||||
</div>
|
||||
</th>
|
||||
</th> {% endcomment %}
|
||||
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
|
||||
{{ _("Action") }}
|
||||
</th>
|
||||
|
||||
<th class="text-end white-space-nowrap align-middle" scope="col"></th>
|
||||
</tr>
|
||||
{% for lead in leads %}
|
||||
@ -103,7 +106,7 @@
|
||||
</div>
|
||||
<tbody>
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="name align-middle white-space-nowrap ps-0">
|
||||
<td class="name align-middle white-space-nowrap ps-1">
|
||||
<div class="d-flex align-items-center">
|
||||
<div><a class="fs-8 fw-bold" href="{% url 'lead_detail' request.dealer.slug lead.slug %}">{{lead.full_name|capfirst}}</a>
|
||||
<div class="d-flex align-items-center">
|
||||
@ -128,9 +131,9 @@
|
||||
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.id_car_make.get_local_name }} - {{ lead.id_car_model.get_local_name }} {{ lead.year }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="">{{ lead.email }}</a></td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
|
||||
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.get_status|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold"><a class="text-body-highlight" href="tel:{{ lead.phone_number }}">{{ lead.phone_number }}</a></td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.next_action|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap fw-semibold">{{ lead.next_action_date|upper }}</td>
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>
|
||||
{% comment %} <td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
||||
{% if lead.opportunity.stage == "prospect" %}
|
||||
@ -145,13 +148,13 @@
|
||||
<span class="badge text-bg-danger">{{ lead.opportunity.stage|upper }}</span>
|
||||
{% endif %}
|
||||
</td> {% endcomment %}
|
||||
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
||||
{% comment %} <td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
|
||||
{% if lead.opportunity %}
|
||||
<a href="{% url 'opportunity_detail' request.dealer.slug lead.opportunity.slug %}">
|
||||
<span class="badge badge-phoenix badge-phoenix-success">Opportunity {{ lead.opportunity.lead}} <i class="fa-solid fa-arrow-up-right-from-square"></i></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</td> {% endcomment %}
|
||||
<td class="align-middle white-space-nowrap text-end">
|
||||
{% if user == lead.staff.user or request.is_dealer %}
|
||||
<div class="btn-reveal-trigger position-static">
|
||||
|
||||
@ -12,25 +12,22 @@
|
||||
<!-- Filter Controls -->
|
||||
<div class="d-flex flex-column flex-lg-row align-items-start align-items-lg-center gap-3 w-100" id="filter-container">
|
||||
<!-- Search Input - Wider and properly aligned -->
|
||||
<div class="position-relative flex-grow-1" style="min-width: 300px;">
|
||||
<span class="fas fa-search position-absolute top-50 translate-middle-y ms-3 text-body-tertiary"></span>
|
||||
<input
|
||||
class="form-control ps-6"
|
||||
type="text"
|
||||
placeholder="{% trans 'Search opportunities...' %}"
|
||||
name="search"
|
||||
hx-get="{% url 'opportunity_list' request.dealer.slug %}"
|
||||
hx-trigger="keyup changed delay:500ms"
|
||||
hx-target="#opportunities-grid"
|
||||
hx-select="#opportunities-grid"
|
||||
hx-include="#filter-container select"
|
||||
hx-swap="outerHTML"
|
||||
style="width: 100%;"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="search-box position-relative flex-grow-1 me-2" style="min-width: 200px;">
|
||||
<form class="position-relative show" id="search-form">
|
||||
<input name="q" id="search-input" class="form-control form-control-sm search-input search" type="search"
|
||||
aria-label="Search" placeholder="{{ _('Search') }}" value="{{ request.GET.q }}" />
|
||||
<span class="fa fa-magnifying-glass search-box-icon"></span>
|
||||
{% if request.GET.q %}
|
||||
<button type="button" class="btn-close position-absolute end-0 top-50 translate-middle cursor-pointer shadow-none"
|
||||
id="clear-search" aria-label="Close"></button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Filter Dropdowns - Aligned in a row -->
|
||||
<div class="d-flex flex-column flex-sm-row gap-3 w-100" style="max-width: 500px;">
|
||||
<div class="d-flex flex-column flex-sm-row gap-3 w-100" style="max-width: 400px;">
|
||||
<!-- Stage Filter -->
|
||||
<div class="flex-grow-1">
|
||||
<select
|
||||
@ -78,7 +75,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.inventory.add_opportunity %}
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="d-flex justify-content-between">
|
||||
<a class="btn btn-phoenix-primary btn-sm" href="{% url 'opportunity_create' request.dealer.slug %}">
|
||||
<span class="fas fa-plus me-2"></span>{{ _("Add Opportunity") }}
|
||||
</a>
|
||||
@ -101,4 +98,24 @@
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const searchInput = document.getElementById("search-input");
|
||||
const clearButton = document.getElementById("clear-search");
|
||||
|
||||
if (clearButton) {
|
||||
clearButton.addEventListener("click", function(event) {
|
||||
event.preventDefault();
|
||||
searchInput.value = ""; // Clear input field
|
||||
// Remove query parameter without reloading the page
|
||||
const newUrl = window.location.pathname;
|
||||
history.replaceState(null, "", newUrl);
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -48,7 +48,7 @@
|
||||
</th>
|
||||
<th class="sort align-middle ps-4 pe-5 text-uppercase border-end border-translucent" scope="col" data-sort="company" style="width:15%;">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<div class="d-flex align-items-center px-1 py-1 bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="grid"></span></div><span>{{ _("Address")|capfirst }}</span>
|
||||
<div class="d-flex align-items-center px-1 py-1 bg-warning-subtle rounded me-2"><span class="text-warning-dark" data-feather="home"></span></div><span>{{ _("Address")|capfirst }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="sort align-middle ps-4 pe-5 text-uppercase border-end border-translucent" scope="col" data-sort="company" style="width:15%;">
|
||||
@ -57,7 +57,7 @@
|
||||
</div>
|
||||
</th>
|
||||
<th class="sort align-middle ps-4 pe-5 text-uppercase" scope="col" data-sort="date" style="width:15%;">
|
||||
{{ _("Create date") }}</th>
|
||||
{{ _("Create date") }} <span class="text-warning-dark" data-feather="clock"></span></th>
|
||||
<th class="sort text-end align-middle pe-0 ps-4" scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@ -19,25 +19,10 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<form method="get" class=" mb-4">
|
||||
<div class="input-group input-group-sm">
|
||||
<button class="btn btn-sm btn-phoenix-secondary rounded-start" type="submit">
|
||||
{% trans "search" %}
|
||||
</button>
|
||||
<input type="text"
|
||||
name="q"
|
||||
class="form-control form-control-sm rounded-end"
|
||||
value="{{ request.GET.q }}"
|
||||
placeholder="{% trans 'Search bills...' %}" />
|
||||
{% if request.GET.q %}
|
||||
<a href="{% url request.resolver_match.view_name %}"
|
||||
class="btn btn-sm btn-phoenix-danger ms-1 rounded">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
<div class="col-auto">
|
||||
<div class="d-flex">
|
||||
{% include 'partials/search_box.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@ -149,7 +149,7 @@
|
||||
onclick="showPOModal('Fulfill PO', '{% url 'po-action-mark-as-fulfilled' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Fulfilled')">
|
||||
<i class="fas fa-truck me-2"></i>{% trans 'Mark as Fulfilled' %}
|
||||
</button>
|
||||
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
@ -129,7 +129,7 @@
|
||||
<p class="mb-0 fs-9 text-body">{{tx.customer.phone_number}}</p>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap quotation">
|
||||
{% if tx.estimate %}
|
||||
{% if tx.estimate and perms.django_ledger.view_estimatemodel%}
|
||||
<p class="fw-bo text-body fs-9 mb-0">
|
||||
<a href="{% url 'estimate_detail' request.dealer.slug tx.estimate.uuid %}">
|
||||
{{ tx.estimate.estimate_number}}
|
||||
@ -147,7 +147,7 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap invoice">
|
||||
{% if tx.invoice %}
|
||||
{% if tx.invoice and perms.django_ledger.view_invoicemodel%}
|
||||
<p class="fw-bo text-body fs-9 mb-0">
|
||||
<a href="{% url 'invoice_detail' request.dealer.slug tx.invoice.uuid %}">
|
||||
{{tx.invoice.invoice_number}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user