changes to customer_view and the customer detail page
This commit is contained in:
commit
1ac8f76d8f
@ -2266,6 +2266,49 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
# class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||||
|
# """
|
||||||
|
# CustomerDetailView handles retrieving and presenting detailed information about
|
||||||
|
# a specific customer. It ensures that the user is authenticated and has the
|
||||||
|
# necessary permissions before accessing the customer's details. This view
|
||||||
|
# provides context data including estimates and invoices related to the customer.
|
||||||
|
|
||||||
|
# :ivar model: The model associated with the view.
|
||||||
|
# :type model: CustomerModel
|
||||||
|
# :ivar template_name: The path to the template used for rendering the view.
|
||||||
|
# :type template_name: str
|
||||||
|
# :ivar context_object_name: The name of the variable in the template context
|
||||||
|
# for the object being viewed.
|
||||||
|
# :type context_object_name: str
|
||||||
|
# :ivar permission_required: The list of permissions required to access this view.
|
||||||
|
# :type permission_required: list[str]
|
||||||
|
# """
|
||||||
|
|
||||||
|
# model = models.Customer
|
||||||
|
# template_name = "customers/view_customer.html"
|
||||||
|
# context_object_name = "customer"
|
||||||
|
# permission_required = ["inventory.view_customer"]
|
||||||
|
|
||||||
|
# def get_context_data(self, **kwargs):
|
||||||
|
# dealer = get_user_type(self.request)
|
||||||
|
# entity = dealer.entity
|
||||||
|
# context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
# context["notes"] = models.Notes.objects.filter(
|
||||||
|
# dealer=dealer,
|
||||||
|
# content_type__model="customer", object_id=self.object.id
|
||||||
|
# )
|
||||||
|
# estimates = entity.get_estimates().filter(customer=self.object.customer_model)
|
||||||
|
# invoices = entity.get_invoices().filter(customer=self.object.customer_model)
|
||||||
|
|
||||||
|
# total = estimates.count() + invoices.count()
|
||||||
|
|
||||||
|
# context["estimates"] = estimates
|
||||||
|
# context["invoices"] = invoices
|
||||||
|
# context["total"] = total
|
||||||
|
# context["note_form"] = forms.NoteForm()
|
||||||
|
# return context
|
||||||
|
|
||||||
class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||||
"""
|
"""
|
||||||
CustomerDetailView handles retrieving and presenting detailed information about
|
CustomerDetailView handles retrieving and presenting detailed information about
|
||||||
@ -2293,17 +2336,22 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
|
|||||||
dealer = get_user_type(self.request)
|
dealer = get_user_type(self.request)
|
||||||
entity = dealer.entity
|
entity = dealer.entity
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["customer_notes"] = models.Notes.objects.filter(
|
|
||||||
object_id=self.object.pk
|
context["notes"] = models.Notes.objects.filter(
|
||||||
|
dealer=dealer,
|
||||||
|
content_type__model="customer", object_id=self.object.id
|
||||||
)
|
)
|
||||||
estimates = entity.get_estimates().filter(customer=self.object.customer_model)
|
estimates = entity.get_estimates().filter(customer=self.object.customer_model)
|
||||||
invoices = entity.get_invoices().filter(customer=self.object.customer_model)
|
invoices = entity.get_invoices().filter(customer=self.object.customer_model)
|
||||||
|
context['leads']=self.object.customer_leads.all()
|
||||||
|
|
||||||
|
|
||||||
total = estimates.count() + invoices.count()
|
total = estimates.count() + invoices.count()
|
||||||
|
|
||||||
context["estimates"] = estimates
|
context["estimates"] = estimates
|
||||||
context["invoices"] = invoices
|
context["invoices"] = invoices
|
||||||
context["total"] = total
|
context["total"] = total
|
||||||
|
context["note_form"] = forms.NoteForm()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@ -4337,15 +4385,12 @@ def sales_list_view(request, dealer_slug):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
# query = request.GET.get('q')
|
search_query = request.GET.get('q', None)
|
||||||
# # if query:
|
if search_query:
|
||||||
# # qs = qs.filter(
|
qs = qs.filter(
|
||||||
# # Q(order_number__icontains=query) |
|
Q(order_number__icontains=search_query)|
|
||||||
# # Q(customer__name__icontains=query) |
|
Q(customer__customer_name__icontains=search_query)
|
||||||
# # Q(item_details__icontains=query)
|
).distinct()
|
||||||
|
|
||||||
# # ).distinct()
|
|
||||||
#r
|
|
||||||
|
|
||||||
paginator = Paginator(qs, 30)
|
paginator = Paginator(qs, 30)
|
||||||
page_number = request.GET.get("page")
|
page_number = request.GET.get("page")
|
||||||
@ -4445,13 +4490,21 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|||||||
related_content_type=ContentType.objects.get_for_model(models.Staff),
|
related_content_type=ContentType.objects.get_for_model(models.Staff),
|
||||||
related_object_id=self.request.staff.pk,
|
related_object_id=self.request.staff.pk,
|
||||||
)
|
)
|
||||||
context["staff_estimates"] = EstimateModel.objects.filter(pk__in=[x.content_object.pk for x in qs])
|
qs = EstimateModel.objects.filter(pk__in=[x.content_object.pk for x in qs])
|
||||||
|
search_query = self.request.GET.get('q', None)
|
||||||
|
if search_query:
|
||||||
|
qs = qs.filter(
|
||||||
|
Q(estimate_number__icontains=search_query)|
|
||||||
|
Q(customer__customer_name__icontains=search_query)
|
||||||
|
).distinct()
|
||||||
|
context["staff_estimates"] = qs
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
dealer = get_user_type(self.request)
|
dealer = get_user_type(self.request)
|
||||||
entity = dealer.entity
|
entity = dealer.entity
|
||||||
status = self.request.GET.get("status")
|
status = self.request.GET.get("status")
|
||||||
|
|
||||||
queryset = entity.get_estimates()
|
queryset = entity.get_estimates()
|
||||||
if status:
|
if status:
|
||||||
queryset = queryset.filter(status=status)
|
queryset = queryset.filter(status=status)
|
||||||
@ -4459,9 +4512,10 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|||||||
|
|
||||||
if search_query:
|
if search_query:
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
Q(estimate_number__icontains=search_query)
|
Q(estimate_number__icontains=search_query)|
|
||||||
|
Q(customer__customer_name__icontains=search_query)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -249,3 +249,46 @@ const getDataTableInit = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Register delete modal initializer
|
||||||
|
htmxInitializer.register(function initDeleteModals() {
|
||||||
|
const deleteModal = document.getElementById("deleteModal");
|
||||||
|
const confirmDeleteBtn = document.getElementById("deleteModalConfirm");
|
||||||
|
const deleteModalMessage = document.getElementById("deleteModalText");
|
||||||
|
|
||||||
|
// Clean up old listeners
|
||||||
|
document.querySelectorAll(".delete-btn").forEach(btn => {
|
||||||
|
btn.removeEventListener("click", handleDeleteClick);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add new listeners
|
||||||
|
document.querySelectorAll(".delete-btn").forEach(button => {
|
||||||
|
button.addEventListener("click", handleDeleteClick);
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleDeleteClick() {
|
||||||
|
if (!deleteModal || !confirmDeleteBtn || !deleteModalMessage) return;
|
||||||
|
|
||||||
|
const deleteUrl = this.getAttribute("data-url");
|
||||||
|
const deleteMessage = this.getAttribute("data-message") || "Are you sure?";
|
||||||
|
|
||||||
|
confirmDeleteBtn.setAttribute("href", deleteUrl);
|
||||||
|
deleteModalMessage.textContent = deleteMessage;
|
||||||
|
|
||||||
|
if (typeof htmx !== 'undefined') htmx.process(confirmDeleteBtn);
|
||||||
|
if (typeof bootstrap !== 'undefined') new bootstrap.Modal(deleteModal).show();
|
||||||
|
}
|
||||||
|
}, "delete_modals");
|
||||||
|
|
||||||
|
// Register custom selects initializer
|
||||||
|
htmxInitializer.register(function initCustomSelects() {
|
||||||
|
// Your custom select initialization code
|
||||||
|
}, "custom_selects");
|
||||||
|
|
||||||
|
// Register form submission initializer
|
||||||
|
htmxInitializer.register(function initForms() {
|
||||||
|
// Your form handling code
|
||||||
|
}, "forms");
|
||||||
|
*/
|
||||||
@ -3,7 +3,7 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<h2 class="mb-5">{{ _("Notifications") }}</h2>
|
<h2 class="mb-5">{{ _("Notifications") }}</h2>
|
||||||
<div class="d-flex justify-content-end mb-3">
|
<div class="d-flex justify-content-end mb-3">
|
||||||
<a href="{% url 'mark_all_notifications_as_read' %}" class="btn btn-phoenix-primary"><i class="far fa-envelope fs-8 me-2"></i>{{ _("Mark all as read") }}</a>
|
<a href="{% url 'mark_all_notifications_as_read' %}" hx-select-oob="#notification-counter:outerHTML" class="btn btn-phoenix-primary"><i class="far fa-envelope fs-8 me-2"></i>{{ _("Mark all as read") }}</a>
|
||||||
</div>
|
</div>
|
||||||
{% if notifications %}
|
{% if notifications %}
|
||||||
<div class="mx-n4 mx-lg-n6 mb-5 border-bottom">
|
<div class="mx-n4 mx-lg-n6 mb-5 border-bottom">
|
||||||
|
|||||||
@ -88,16 +88,15 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if perms.inventory.change_customer %}
|
{% if perms.inventory.change_customer %}
|
||||||
<div class="d-flex align-items-center justify-content-end">
|
<div class="d-flex align-items-center justify-content-end">
|
||||||
<a id="addBtn"
|
{% if perms.inventory.change_lead %}
|
||||||
href="#"
|
<button class="btn btn-phoenix-primary btn-sm"
|
||||||
class="btn btn-sm btn-phoenix-primary mb-3"
|
type="button"
|
||||||
data-url="{% url 'add_note_to_customer' request.dealer.slug customer.slug %}"
|
onclick=""
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#noteModal"
|
data-bs-target="#noteModal">
|
||||||
data-note-title="{{ _("Add") }}<i class='fa fa-plus-circle text-success ms-2'></i>">
|
<span class="fas fa-plus me-1"></span>{{ _("Add Note") }}
|
||||||
<span class="fas fa-plus me-1"></span>
|
</button>
|
||||||
{% trans 'Add Note' %}
|
{% endif %}
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<table class="table fs-9 mb-0 table-responsive">
|
<table class="table fs-9 mb-0 table-responsive">
|
||||||
@ -105,8 +104,8 @@
|
|||||||
<th class="align-middle pe-6 text-start" scope="col">{{ _("Note") }}</th>
|
<th class="align-middle pe-6 text-start" scope="col">{{ _("Note") }}</th>
|
||||||
<th class="align-middle pe-6 text-start" scope="col">{{ _("Date") }}</th>
|
<th class="align-middle pe-6 text-start" scope="col">{{ _("Date") }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tbody>
|
<tbody id="notesTable">
|
||||||
{% for note in customer_notes %}
|
{% for note in notes %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{ note.note }}</td>
|
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{ note.note }}</td>
|
||||||
<td class="align-middle text-body-tertiary text-start white-space-nowrap">{{ note.created }}</td>
|
<td class="align-middle text-body-tertiary text-start white-space-nowrap">{{ note.created }}</td>
|
||||||
@ -221,30 +220,86 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal fade"
|
<!---->
|
||||||
id="noteModal"
|
<div class="col-12">
|
||||||
tabindex="-1"
|
<div class="mb-6">
|
||||||
aria-labelledby="noteModalLabel"
|
<h3 class="mb-4">
|
||||||
aria-hidden="true">
|
{%trans 'INFO'%}
|
||||||
<div class="modal-dialog modal-sm">
|
</h3>
|
||||||
<div class="modal-content">
|
<div class="border-top border-bottom border-translucent"
|
||||||
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
id="customerOrdersTable"
|
||||||
<h4 class="modal-title" id="noteModalLabel">{% trans 'Notes' %}</h4>
|
data-list='{"valueNames":["order","total","payment_status","fulfilment_status","delivery_type","date"],"page":6,"pagination":true}'>
|
||||||
<button class="btn p-0 text-body-quaternary fs-6"
|
<div class="table-responsive scrollbar">
|
||||||
data-bs-dismiss="modal"
|
<table class="table table-sm fs-9 mb-0">
|
||||||
aria-label="Close">
|
<thead>
|
||||||
<span class="fas fa-times"></span>
|
<tr>
|
||||||
</button>
|
<th class="sort white-space-nowrap align-middle" scope="col" data-sort="leads">{% trans 'Leads'|upper %}</th>
|
||||||
</div>
|
<th class="sort align-middle " scope="col" data-sort="opportunities">{% trans 'Opportunities'|upper %}</th>
|
||||||
<div class="modal-body">
|
<th class="sort align-middle " scope="col" data-sort="estimates">{% trans 'Estimates'|upper %}</th>
|
||||||
<!-- Content will be loaded here via AJAX -->
|
<th class="sort align-middle " scope="col" data-sort="sale_orders">{% trans 'Sale orders'|upper %}</th>
|
||||||
|
<th class="sort align-middle " scope="col" data-sort="invoices">{% trans 'Invoices'|upper %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="list" id="customer-order-table-body">
|
||||||
|
{% for lead in leads %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="#">{{lead}} ({{ forloop.counter }})<a></td>
|
||||||
|
<td>{{lead.opportunity}} ({{ forloop.counter }})</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
|
||||||
|
{% for estimate in lead.customer.customer_model.estimatemodel_set.all %}
|
||||||
|
<div class="me-2">{{estimate}}</div>
|
||||||
|
<hr>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{% for estimate in lead.customer.customer_model.estimatemodel_set.all %}
|
||||||
|
<div>{{estimate.sale_orders.first}}</div>
|
||||||
|
<hr>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for invoice in lead.customer.customer_model.invoicemodel_set.all %}
|
||||||
|
|
||||||
|
{% if invoice.is_paid %}
|
||||||
|
<span class="badge badge-phoenix fs-10 badge-phoenix-success">
|
||||||
|
<div>{{invoice}}</div>
|
||||||
|
</span>
|
||||||
|
{%else%}
|
||||||
|
<span class="badge badge-phoenix fs-10 badge-phoenix-info">
|
||||||
|
<div>{{invoice}}</div>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!---->
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% include "components/note_modal.html" with content_type="customer" slug=customer.slug %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const noteModal = document.getElementById("noteModal");
|
const noteModal = document.getElementById("noteModal");
|
||||||
@ -269,5 +324,6 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,35 +1,16 @@
|
|||||||
<div class="search-box me-2">
|
<div class="search-box me-2">
|
||||||
<form class="position-relative show" id="search-form">
|
<form class="position-relative show" id="search-form">
|
||||||
<input name="q"
|
<input name="q"
|
||||||
|
hx-get=""
|
||||||
|
hx-boost="true"
|
||||||
|
hx-trigger="keyup delay:500ms"
|
||||||
id="search-input"
|
id="search-input"
|
||||||
class="form-control form-control-sm search-input search"
|
class="form-control form-control-sm search-input search"
|
||||||
type="search"
|
type="search"
|
||||||
aria-label="Search"
|
aria-label="Search"
|
||||||
placeholder="{{ _("Search") }}..."
|
placeholder="{{ _('Search') }}..."
|
||||||
value="{{ request.GET.q }}" />
|
value="{{ request.GET.q }}" />
|
||||||
<span class="fa fa-magnifying-glass search-box-icon"></span>
|
<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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user