update merge

This commit is contained in:
ismail 2025-08-25 11:52:32 +03:00
parent 2c6249d74c
commit 6a2911cea4
15 changed files with 94 additions and 67 deletions

View File

@ -71,6 +71,7 @@ admin.site.register(models.Notes)
admin.site.register(models.UserActivityLog) admin.site.register(models.UserActivityLog)
admin.site.register(models.DealersMake) admin.site.register(models.DealersMake)
admin.site.register(models.ExtraInfo) admin.site.register(models.ExtraInfo)
admin.site.register(models.Ticket)
@admin.register(models.Car) @admin.register(models.Car)

View File

@ -2214,15 +2214,10 @@ class TicketForm(forms.ModelForm):
class TicketResolutionForm(forms.ModelForm): class TicketResolutionForm(forms.ModelForm):
resolution_notes = forms.CharField(
widget=forms.Textarea(attrs={'rows': 3}),
required=False,
help_text="Optional notes about how the issue was resolved."
)
class Meta: class Meta:
model = Ticket model = Ticket
fields = ['status'] fields = ['status', 'resolution_notes']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)

View File

@ -3679,6 +3679,7 @@ class Ticket(models.Model):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='tickets') dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='tickets')
subject = models.CharField(max_length=200) subject = models.CharField(max_length=200)
description = models.TextField() description = models.TextField()
resolution_notes = models.TextField(blank=True, null=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='open') status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='open')
priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default='medium') priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default='medium')
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)

View File

@ -67,9 +67,12 @@ class PurchaseOrderModelUpdateView(
def get_context_data(self, itemtxs_formset=None, **kwargs): def get_context_data(self, itemtxs_formset=None, **kwargs):
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"]) dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
po_model: PurchaseOrderModel = self.object
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["entity_slug"] = dealer.entity.slug context["entity_slug"] = dealer.entity.slug
po_model: PurchaseOrderModel = self.object context["po_ready_to_fulfill"] = [item for item in po_model.get_itemtxs_data()[0] if item.po_item_status == 'received']
if not itemtxs_formset: if not itemtxs_formset:
itemtxs_qs = self.get_po_itemtxs_qs(po_model) itemtxs_qs = self.get_po_itemtxs_qs(po_model)
itemtxs_qs, itemtxs_agg = po_model.get_itemtxs_data(queryset=itemtxs_qs) itemtxs_qs, itemtxs_agg = po_model.get_itemtxs_data(queryset=itemtxs_qs)
@ -530,6 +533,7 @@ class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVie
context["page_title"] = title context["page_title"] = title
context["header_title"] = title context["header_title"] = title
context["header_subtitle"] = bill_model.get_bill_status_display() context["header_subtitle"] = bill_model.get_bill_status_display()
context["can_mark_as_paid"] = bill_model.amount_paid == bill_model.amount_due
if not bill_model.is_configured(): if not bill_model.is_configured():
messages.add_message( messages.add_message(

View File

@ -1245,3 +1245,19 @@ def send_ticket_notification(sender, instance, created, **kwargs):
subject, subject,
message, message,
) )
else:
models.Notification.objects.create(
user=instance.dealer.user,
message=_(
"""
Support Ticket #{ticket_number} has been updated.
<a href="{url}" target="_blank">View</a>.
"""
).format(
ticket_number=instance.pk,
url=reverse(
"ticket_detail",
kwargs={"dealer_slug": instance.dealer.slug, "ticket_id": instance.pk},
),
),
)

View File

@ -1322,9 +1322,9 @@ urlpatterns = [
path('<slug:dealer_slug>/staff/<slug:slug>detail/', views.StaffDetailView.as_view(), name='staff_detail'), path('<slug:dealer_slug>/staff/<slug:slug>detail/', views.StaffDetailView.as_view(), name='staff_detail'),
# tickets # tickets
path('help_center/view/', views.help_center, name='help_center'), path('help_center/view/', views.help_center, name='help_center'),
path('help_center/tickets/', views.ticket_list, name='ticket_list'), path('<slug:dealer_slug>/help_center/tickets/', views.ticket_list, name='ticket_list'),
path('help_center/tickets/create/', views.create_ticket, name='create_ticket'), path('help_center/tickets/<slug:dealer_slug>/create/', views.create_ticket, name='create_ticket'),
path('help_center/tickets/<int:ticket_id>/', views.ticket_detail, name='ticket_detail'), path('<slug:dealer_slug>/help_center/tickets/<int:ticket_id>/', views.ticket_detail, name='ticket_detail'),
path('help_center/tickets/<int:ticket_id>/update/', views.ticket_update, name='ticket_update'), path('help_center/tickets/<int:ticket_id>/update/', views.ticket_update, name='ticket_update'),
# path('help_center/tickets/<int:ticket_id>/ticket_mark_resolved/', views.ticket_mark_resolved, name='ticket_mark_resolved'), # path('help_center/tickets/<int:ticket_id>/ticket_mark_resolved/', views.ticket_mark_resolved, name='ticket_mark_resolved'),

View File

@ -10589,7 +10589,7 @@ class PurchaseOrderDetailView(LoginRequiredMixin, PermissionRequiredMixin, Detai
title = f"Purchase Order {po_model.po_number}" title = f"Purchase Order {po_model.po_number}"
context["page_title"] = title context["page_title"] = title
context["header_title"] = title context["header_title"] = title
context["po_ready_to_fulfill"] = all([item for item in po_model.get_itemtxs_data()[0] if item.po_item_status == 'received'])
po_model: PurchaseOrderModel = self.object po_model: PurchaseOrderModel = self.object
po_items_qs, item_data = po_model.get_itemtxs_data( po_items_qs, item_data = po_model.get_itemtxs_data(
queryset=po_model.itemtransactionmodel_set.all().select_related( queryset=po_model.itemtransactionmodel_set.all().select_related(
@ -10890,6 +10890,7 @@ def upload_cars(request, dealer_slug, pk=None):
year=int(year_model), year=int(year_model),
vendor=vendor, vendor=vendor,
receiving_date=receiving_date, receiving_date=receiving_date,
cost_price=po_item.item.unit_cost,
) )
# if po_item: #TODO:update # if po_item: #TODO:update
# models.CarFinance.objects.create( # models.CarFinance.objects.create(
@ -11410,18 +11411,18 @@ def help_center(request):
@login_required @login_required
@permission_required('inventory.add_ticket') @permission_required('inventory.add_ticket')
def create_ticket(request): def create_ticket(request,dealer_slug):
if not request.is_dealer: if not request.is_dealer:
return redirect('home') return redirect('home')
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
if request.method == 'POST': if request.method == 'POST':
form = forms.TicketForm(request.POST) form = forms.TicketForm(request.POST)
if form.is_valid(): if form.is_valid():
instance = form.save(commit=False) instance = form.save(commit=False)
instance.dealer = request.dealer instance.dealer = dealer
instance.save() instance.save()
messages.success(request, 'Your support ticket has been submitted successfully!') messages.success(request, 'Your support ticket has been submitted successfully!')
return redirect('ticket_list') return redirect('ticket_list',dealer_slug=dealer.slug)
else: else:
form = forms.TicketForm() form = forms.TicketForm()
@ -11429,16 +11430,16 @@ def create_ticket(request):
@login_required @login_required
@permission_required('inventory.view_ticket') @permission_required('inventory.view_ticket')
def ticket_list(request): def ticket_list(request,dealer_slug):
tickets = models.Ticket.objects.all().order_by('-created_at') dealer= get_object_or_404(models.Dealer, slug=dealer_slug)
if request.is_dealer: tickets = models.Ticket.objects.filter(dealer=dealer).order_by('-created_at')
tickets = tickets = tickets.filter(dealer=request.dealer)
return render(request, 'support/ticket_list.html', {'tickets': tickets}) return render(request, 'support/ticket_list.html', {'tickets': tickets})
@login_required @login_required
@permission_required('inventory.change_ticket') @permission_required('inventory.change_ticket')
def ticket_detail(request, ticket_id): def ticket_detail(request, dealer_slug,ticket_id):
ticket = models.Ticket.objects.get(id=ticket_id) dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
ticket = models.Ticket.objects.get(dealer=dealer,id=ticket_id)
return render(request, 'support/ticket_detail.html', {'ticket': ticket}) return render(request, 'support/ticket_detail.html', {'ticket': ticket})
@login_required @login_required
@ -11468,7 +11469,7 @@ def ticket_update(request, ticket_id):
if form.is_valid(): if form.is_valid():
form.save() form.save()
messages.success(request, f'Ticket has been marked as {ticket.get_status_display()}.') messages.success(request, f'Ticket has been marked as {ticket.get_status_display()}.')
return redirect('ticket_detail', ticket_id=ticket.id) return redirect('ticket_detail',dealer_slug=ticket.dealer.slug, ticket_id=ticket.id)
else: else:
form = forms.TicketResolutionForm(instance=ticket) form = forms.TicketResolutionForm(instance=ticket)

View File

@ -4,6 +4,7 @@
{% if not create_bill %} {% if not create_bill %}
{% if style == 'dashboard' %} {% if style == 'dashboard' %}
<!-- Dashboard Style Card --> <!-- Dashboard Style Card -->
<div class=""> <div class="">
<div class="card-body"> <div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3 text-primary"> <div class="d-flex justify-content-between align-items-center mb-3 text-primary">
@ -50,6 +51,7 @@
</div> </div>
<!-- Modal Action --> <!-- Modal Action -->
{% modal_action bill 'get' entity_slug %} {% modal_action bill 'get' entity_slug %}
<div class="d-grid gap-2 d-md-flex justify-content-md-end"> <div class="d-grid gap-2 d-md-flex justify-content-md-end">
<a href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=bill.uuid %}" <a href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=bill.uuid %}"
class="btn btn-sm btn-phoenix-primary me-md-2">{% trans 'View' %}</a> class="btn btn-sm btn-phoenix-primary me-md-2">{% trans 'View' %}</a>
@ -57,8 +59,7 @@
<a hx-boost="true" href="{% url 'django_ledger:bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}" <a hx-boost="true" href="{% url 'django_ledger:bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}"
class="btn btn-sm btn-phoenix-warning me-md-2">{% trans 'Update' %}</a> class="btn btn-sm btn-phoenix-warning me-md-2">{% trans 'Update' %}</a>
{% if bill.can_pay %} {% if bill.can_pay %}
<button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')" <button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')" class="btn btn-sm btn-phoenix-info">{% trans 'Mark as Paid' %}</button>
class="btn btn-sm btn-phoenix-info">{% trans 'Mark as Paid' %}</button>
{% endif %} {% endif %}
{% if bill.can_cancel %} {% if bill.can_cancel %}
<button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')" <button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')"

View File

@ -533,7 +533,7 @@
{% endif %} {% endif %}
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link px-3 d-block" href="#"> <span class="me-2 text-body align-bottom" data-feather="help-circle"></span>{{ _("Help Center") }}</a> <a class="nav-link px-3 d-block" href="{% url 'ticket_list' request.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="help-circle"></span>{{ _("Help Center") }}</a>
</li> </li>
{% if request.is_staff %} {% if request.is_staff %}
<li class="nav-item"> <li class="nav-item">

View File

@ -131,10 +131,17 @@
</button> </button>
{% endif %} {% endif %}
{% if po_model.can_fulfill %} {% if po_model.can_fulfill %}
<button class="btn btn-phoenix-primary" {% if not po_ready_to_fulfill %}
onclick="showPOModal('Fulfill PO', '{% url 'po-action-mark-as-fulfilled' request.dealer.slug entity_slug po_model.pk %}', 'Mark As Fulfilled')"> <button disabled class="btn btn-phoenix-primary">
<i class="fas fa-truck me-2"></i>{% trans 'Mark as Fulfilled' %} <i class="fas fa-truck me-2"></i>{% trans 'Mark as Fulfilled' %}
</button> </button>
{% else %}
<button class="btn btn-phoenix-primary"
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 %}
{% endif %} {% endif %}
{% if po_model.can_delete %} {% if po_model.can_delete %}
{% if perms.django_ledger.delete_purchaseordermodel %} {% if perms.django_ledger.delete_purchaseordermodel %}

View File

@ -14,7 +14,7 @@
{{form|crispy}} {{form|crispy}}
<button type="submit" class="btn btn-primary">Submit Ticket</button> <button type="submit" class="btn btn-primary">Submit Ticket</button>
<a href="{% url 'ticket_list' %}" class="btn btn-secondary">Cancel</a> <a href="{% url 'ticket_list' request.dealer.slug %}" class="btn btn-secondary">Cancel</a>
</form> </form>
</div> </div>
</div> </div>

View File

@ -6,7 +6,7 @@
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Need help?</h5> <h5 class="card-title">Need help?</h5>
<p class="card-text">Raise a ticket and we will get back to you as soon as possible.</p> <p class="card-text">Raise a ticket and we will get back to you as soon as possible.</p>
<a href="{% url 'create_ticket' %}" class="btn btn-phoenix-primary">Raise a Ticket</a> <a href="{% url 'create_ticket' request.dealer.slug %}" class="btn btn-phoenix-primary">Raise a Ticket</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -8,12 +8,7 @@
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<h2 class="h4 mb-0">Ticket #{{ ticket.id }}: {{ ticket.subject }}</h2> <h2 class="h4 mb-0">Ticket #{{ ticket.id }}: {{ ticket.subject }}</h2>
<div> <div>
{% if ticket.status != 'resolved' %} <a href="{% url 'ticket_list' request.dealer.slug %}" class="btn btn-sm btn-outline-secondary">
<a href="{% url 'ticket_update' ticket.id %}" class="btn btn-sm btn-outline-success">
Update Ticket Status
</a>
{% endif %}
<a href="{% url 'ticket_list' %}" class="btn btn-sm btn-outline-secondary">
Back to List Back to List
</a> </a>
</div> </div>
@ -48,10 +43,16 @@
<div class="mb-4"> <div class="mb-4">
<h3 class="h5">Description</h3> <h3 class="h5">Description</h3>
<div class="p-3 bg-light rounded"> <div class="p-3 rounded">
{{ ticket.description|linebreaks }} {{ ticket.description|linebreaks }}
</div> </div>
</div> </div>
<div class="mb-4">
<h3 class="h5">Resolution Notes</h3>
<div class="p-3 rounded">
{{ ticket.resolution_notes|linebreaks }}
</div>
</div>
<!-- You can add comments/replies section here later --> <!-- You can add comments/replies section here later -->
</div> </div>

View File

@ -3,12 +3,13 @@
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="card">
<h1 class="h3 mb-0">My Support Tickets</h1> <div class="card-body">
<a href="{% url 'create_ticket' %}" class="btn btn-primary"> <h5 class="card-title">Need help?</h5>
<i class="bi bi-plus-circle"></i> New Ticket <p class="card-text">Raise a ticket and we will get back to you as soon as possible.</p>
</a> <a href="{% url 'create_ticket' request.dealer.slug %}" class="btn btn-phoenix-primary">Raise a Ticket</a>
</div> </div>
</div>
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
@ -19,17 +20,19 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<div class="card">
<div class="card-body">
<h5 class="card-title">Tickets</h5>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover"> <table class="table table-striped table-hover">
<thead class="table-light"> <thead class="">
<tr> <tr>
<th>ID</th> <th>ID</th>
<th>Subject</th> <th>Subject</th>
<th>Status</th> <th>Status</th>
<th>Priority</th> <th>Priority</th>
<th>Created</th> <th>Created</th>
<th>Resolved At</th>
<th>Time To Resolution</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@ -57,14 +60,9 @@
</span> </span>
</td> </td>
<td>{{ ticket.created_at|date:"M d, Y H:i" }}</td> <td>{{ ticket.created_at|date:"M d, Y H:i" }}</td>
<td>{{ ticket.updated_at|date:"M d, Y H:i" }}</td>
<td> <td>
<p> <a href="{% url 'ticket_detail' request.dealer.slug ticket.id %}" class="btn btn-sm btn-outline-primary">
<i class="fa fa-clock"></i>&nbsp;
{{ ticket.time_to_resolution_display }}
</p>
<td>
<a href="{% url 'ticket_detail' ticket.id %}" class="btn btn-sm btn-outline-primary">
View View
</a> </a>
</td> </td>
@ -77,4 +75,6 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -13,7 +13,7 @@
{% csrf_token %} {% csrf_token %}
{{form|crispy}} {{form|crispy}}
<button type="submit" class="btn btn-primary">Save</button> <button type="submit" class="btn btn-primary">Save</button>
<a href="{% url 'ticket_list' %}" class="btn btn-secondary">Cancel</a> <a href="{% url 'ticket_list' request.dealer.slug %}" class="btn btn-secondary">Cancel</a>
</form> </form>
</div> </div>
</div> </div>