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
|
||||
|
||||
|
||||
# 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):
|
||||
"""
|
||||
CustomerDetailView handles retrieving and presenting detailed information about
|
||||
@ -2293,17 +2336,22 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
|
||||
dealer = get_user_type(self.request)
|
||||
entity = dealer.entity
|
||||
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)
|
||||
invoices = entity.get_invoices().filter(customer=self.object.customer_model)
|
||||
context['leads']=self.object.customer_leads.all()
|
||||
|
||||
|
||||
total = estimates.count() + invoices.count()
|
||||
|
||||
context["estimates"] = estimates
|
||||
context["invoices"] = invoices
|
||||
context["total"] = total
|
||||
context["note_form"] = forms.NoteForm()
|
||||
return context
|
||||
|
||||
|
||||
@ -4337,15 +4385,12 @@ def sales_list_view(request, dealer_slug):
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
# query = request.GET.get('q')
|
||||
# # if query:
|
||||
# # qs = qs.filter(
|
||||
# # Q(order_number__icontains=query) |
|
||||
# # Q(customer__name__icontains=query) |
|
||||
# # Q(item_details__icontains=query)
|
||||
|
||||
# # ).distinct()
|
||||
#r
|
||||
search_query = request.GET.get('q', None)
|
||||
if search_query:
|
||||
qs = qs.filter(
|
||||
Q(order_number__icontains=search_query)|
|
||||
Q(customer__customer_name__icontains=search_query)
|
||||
).distinct()
|
||||
|
||||
paginator = Paginator(qs, 30)
|
||||
page_number = request.GET.get("page")
|
||||
@ -4445,23 +4490,32 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
related_content_type=ContentType.objects.get_for_model(models.Staff),
|
||||
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
|
||||
|
||||
def get_queryset(self):
|
||||
dealer = get_user_type(self.request)
|
||||
entity = dealer.entity
|
||||
status = self.request.GET.get("status")
|
||||
|
||||
queryset = entity.get_estimates()
|
||||
if status:
|
||||
queryset = queryset.filter(status=status)
|
||||
search_query = self.request.GET.get('q', None)
|
||||
|
||||
|
||||
if search_query:
|
||||
queryset = queryset.filter(
|
||||
Q(estimate_number__icontains=search_query)
|
||||
|
||||
Q(estimate_number__icontains=search_query)|
|
||||
Q(customer__customer_name__icontains=search_query)
|
||||
).distinct()
|
||||
|
||||
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">
|
||||
<h2 class="mb-5">{{ _("Notifications") }}</h2>
|
||||
<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>
|
||||
{% if notifications %}
|
||||
<div class="mx-n4 mx-lg-n6 mb-5 border-bottom">
|
||||
|
||||
@ -88,16 +88,15 @@
|
||||
<div class="card-body">
|
||||
{% if perms.inventory.change_customer %}
|
||||
<div class="d-flex align-items-center justify-content-end">
|
||||
<a id="addBtn"
|
||||
href="#"
|
||||
class="btn btn-sm btn-phoenix-primary mb-3"
|
||||
data-url="{% url 'add_note_to_customer' request.dealer.slug customer.slug %}"
|
||||
data-bs-toggle="modal"
|
||||
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>
|
||||
{% trans 'Add Note' %}
|
||||
</a>
|
||||
{% if perms.inventory.change_lead %}
|
||||
<button class="btn btn-phoenix-primary btn-sm"
|
||||
type="button"
|
||||
onclick=""
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#noteModal">
|
||||
<span class="fas fa-plus me-1"></span>{{ _("Add Note") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<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">{{ _("Date") }}</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
{% for note in customer_notes %}
|
||||
<tbody id="notesTable">
|
||||
{% for note in notes %}
|
||||
<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-body-tertiary text-start white-space-nowrap">{{ note.created }}</td>
|
||||
@ -221,30 +220,86 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade"
|
||||
id="noteModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="noteModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
||||
<h4 class="modal-title" id="noteModalLabel">{% trans 'Notes' %}</h4>
|
||||
<button class="btn p-0 text-body-quaternary fs-6"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close">
|
||||
<span class="fas fa-times"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Content will be loaded here via AJAX -->
|
||||
|
||||
|
||||
<!---->
|
||||
<div class="col-12">
|
||||
<div class="mb-6">
|
||||
<h3 class="mb-4">
|
||||
{%trans 'INFO'%}
|
||||
</h3>
|
||||
<div class="border-top border-bottom border-translucent"
|
||||
id="customerOrdersTable"
|
||||
data-list='{"valueNames":["order","total","payment_status","fulfilment_status","delivery_type","date"],"page":6,"pagination":true}'>
|
||||
<div class="table-responsive scrollbar">
|
||||
<table class="table table-sm fs-9 mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" data-sort="leads">{% trans 'Leads'|upper %}</th>
|
||||
<th class="sort align-middle " scope="col" data-sort="opportunities">{% trans 'Opportunities'|upper %}</th>
|
||||
<th class="sort align-middle " scope="col" data-sort="estimates">{% trans 'Estimates'|upper %}</th>
|
||||
<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>
|
||||
{% include "components/note_modal.html" with content_type="customer" slug=customer.slug %}
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const noteModal = document.getElementById("noteModal");
|
||||
@ -269,5 +324,6 @@
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@ -1,35 +1,16 @@
|
||||
<div class="search-box me-2">
|
||||
<form class="position-relative show" id="search-form">
|
||||
<input name="q"
|
||||
hx-get=""
|
||||
hx-boost="true"
|
||||
hx-trigger="keyup delay:500ms"
|
||||
id="search-input"
|
||||
class="form-control form-control-sm search-input search"
|
||||
type="search"
|
||||
aria-label="Search"
|
||||
placeholder="{{ _("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>
|
||||
<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