diff --git a/db.sqlite b/db.sqlite index 15d38a38..cc97f682 100644 Binary files a/db.sqlite and b/db.sqlite differ diff --git a/inventory/__pycache__/admin.cpython-311.pyc b/inventory/__pycache__/admin.cpython-311.pyc index 4b7818bc..d6fb450c 100644 Binary files a/inventory/__pycache__/admin.cpython-311.pyc and b/inventory/__pycache__/admin.cpython-311.pyc differ diff --git a/inventory/__pycache__/forms.cpython-311.pyc b/inventory/__pycache__/forms.cpython-311.pyc index d9531abc..90bf1621 100644 Binary files a/inventory/__pycache__/forms.cpython-311.pyc and b/inventory/__pycache__/forms.cpython-311.pyc differ diff --git a/inventory/__pycache__/models.cpython-311.pyc b/inventory/__pycache__/models.cpython-311.pyc index b93494ff..c2489e52 100644 Binary files a/inventory/__pycache__/models.cpython-311.pyc and b/inventory/__pycache__/models.cpython-311.pyc differ diff --git a/inventory/__pycache__/urls.cpython-311.pyc b/inventory/__pycache__/urls.cpython-311.pyc index 8080971d..8cb69e8c 100644 Binary files a/inventory/__pycache__/urls.cpython-311.pyc and b/inventory/__pycache__/urls.cpython-311.pyc differ diff --git a/inventory/__pycache__/views.cpython-311.pyc b/inventory/__pycache__/views.cpython-311.pyc index 78691ac4..2a1e7928 100644 Binary files a/inventory/__pycache__/views.cpython-311.pyc and b/inventory/__pycache__/views.cpython-311.pyc differ diff --git a/inventory/admin.py b/inventory/admin.py index 285dc267..edf1a77c 100644 --- a/inventory/admin.py +++ b/inventory/admin.py @@ -5,7 +5,6 @@ from . import models admin.site.register(models.Dealer) admin.site.register(models.Staff) admin.site.register(models.Vendor) -admin.site.register(models.Customer) admin.site.register(models.SaleQuotation) admin.site.register(models.SaleQuotationCar) admin.site.register(models.SalesOrder) @@ -27,6 +26,10 @@ admin.site.register(models.Representative) admin.site.register(models.CarTrim) admin.site.register(models.AdditionalServices) admin.site.register(models.Payment) +admin.site.register(models.VatRate) +admin.site.register(models.Customer) +admin.site.register(models.Opportunity) +admin.site.register(models.Notification) @admin.register(models.CarMake) class CarMakeAdmin(admin.ModelAdmin): @@ -95,3 +98,4 @@ class CarSpecificationAdmin(admin.ModelAdmin): # list_display = ('user', 'action', 'timestamp') # search_fields = ('user__username', 'action') # list_filter = ('timestamp',) + diff --git a/inventory/forms.py b/inventory/forms.py index d12ee768..be5fd1ee 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -24,6 +24,11 @@ from .models import ( SaleQuotationCar, AdditionalServices, Staff, +<<<<<<< HEAD +======= + Opportunity + +>>>>>>> 8b00f9a40fc336f209f4ae6fb03785df6c97d265 ) from django_ledger.models import ItemModel, InvoiceModel from django.forms import ModelMultipleChoiceField, ValidationError @@ -527,6 +532,7 @@ class ItemForm(forms.Form): unit_sales_price = forms.DecimalField(label="Unit Sales Price", required=True) +<<<<<<< HEAD class PaymentForm(forms.Form): invoice = forms.ModelChoiceField( queryset=InvoiceModel.objects.all(), label="Invoice", required=True @@ -552,4 +558,18 @@ class EmailForm(forms.Form): subject = forms.CharField(max_length=255) message = forms.CharField(widget=forms.Textarea) from_email = forms.EmailField() - to_email = forms.EmailField() \ No newline at end of file + to_email = forms.EmailField() +======= +class OpportunityForm(forms.ModelForm): + class Meta: + model = Opportunity + fields = [ + 'car', 'deal_name', 'deal_value', 'deal_status', + 'priority', 'source' + ] + widgets = { + 'deal_status': forms.Select(choices=Opportunity.DEAL_STATUS_CHOICES), + 'priority': forms.Select(choices=Opportunity.PRIORITY_CHOICES), + 'source': forms.Select(choices=Opportunity.DEAL_SOURCES_CHOICES), + } +>>>>>>> 8b00f9a40fc336f209f4ae6fb03785df6c97d265 diff --git a/inventory/migrations/0016_alter_staff_staff_type.py b/inventory/migrations/0016_alter_staff_staff_type.py new file mode 100644 index 00000000..229113a1 --- /dev/null +++ b/inventory/migrations/0016_alter_staff_staff_type.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2024-12-30 15:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0015_merge_0008_vatrate_0014_useractivitylog'), + ] + + operations = [ + migrations.AlterField( + model_name='staff', + name='staff_type', + field=models.CharField(choices=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales')], max_length=255, verbose_name='Staff Type'), + ), + ] diff --git a/inventory/migrations/0017_customer_is_lead_notification_opportunity.py b/inventory/migrations/0017_customer_is_lead_notification_opportunity.py new file mode 100644 index 00000000..36ddd4f1 --- /dev/null +++ b/inventory/migrations/0017_customer_is_lead_notification_opportunity.py @@ -0,0 +1,54 @@ +# Generated by Django 5.1.4 on 2024-12-30 22:58 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0016_alter_staff_staff_type'), + ] + + operations = [ + migrations.AddField( + model_name='customer', + name='is_lead', + field=models.BooleanField(default=True, verbose_name='Is Lead'), + ), + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('message', models.CharField(max_length=255, verbose_name='Message')), + ('is_read', models.BooleanField(default=False, verbose_name='Is Read')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('staff', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to='inventory.staff')), + ], + options={ + 'verbose_name': 'Notification', + 'verbose_name_plural': 'Notifications', + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='Opportunity', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('deal_name', models.CharField(max_length=255, verbose_name='Deal Name')), + ('deal_value', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Deal Value')), + ('deal_status', models.CharField(choices=[('new', 'New'), ('in_progress', 'In Progress'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], default='new', max_length=50, verbose_name='Deal Status')), + ('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ('urgent', 'Urgent')], default='low', max_length=50, verbose_name='Priority')), + ('source', models.CharField(choices=[('referrals', 'Referrals'), ('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('whatsapp', 'Whatsapp'), ('showroom', 'Showroom'), ('website', 'Website')], default='showroom', max_length=255, verbose_name='Source')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('car', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deals_created', to='inventory.staff')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer')), + ], + options={ + 'verbose_name': 'Opportunity', + 'verbose_name_plural': 'Opportunities', + }, + ), + ] diff --git a/inventory/migrations/0018_remove_notification_staff_notification_user.py b/inventory/migrations/0018_remove_notification_staff_notification_user.py new file mode 100644 index 00000000..d7714e0d --- /dev/null +++ b/inventory/migrations/0018_remove_notification_staff_notification_user.py @@ -0,0 +1,26 @@ +# Generated by Django 5.1.4 on 2024-12-31 03:24 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0017_customer_is_lead_notification_opportunity'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.RemoveField( + model_name='notification', + name='staff', + ), + migrations.AddField( + model_name='notification', + name='user', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL), + preserve_default=False, + ), + ] diff --git a/inventory/models.py b/inventory/models.py index b4a8f4fb..218bfa74 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -686,6 +686,7 @@ class Customer(models.Model): max_length=200, blank=True, null=True, verbose_name=_("Address") ) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) + is_lead = models.BooleanField(default=True, verbose_name=_("Is Lead")) class Meta: verbose_name = _("Customer") @@ -698,7 +699,64 @@ class Customer(models.Model): @property def get_full_name(self): return f"{self.first_name} {self.middle_name} {self.last_name}" - + + +class Opportunity(models.Model): + DEAL_STATUS_CHOICES = [ + ('new', _('New')), + ('in_progress', _('In Progress')), + ('pending', _('Pending')), + ('canceled', _('Canceled')), + ('completed', _('Completed')), + ] + PRIORITY_CHOICES = [ + ('low', _('Low')), + ('medium', _('Medium')), + ('high', _('High')), + ('urgent', _('Urgent')), + ] + DEAL_SOURCES_CHOICES = [ + ('referrals', _('Referrals')), + ('walk_in', _('Walk In')), + ('toll_free', _('Toll Free')), + ('whatsapp', _('Whatsapp')), + ('showroom', _('Showroom')), + ('website', _('Website')), + ] + + customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="opportunities") + car = models.ForeignKey(Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car")) + deal_name = models.CharField(max_length=255, verbose_name=_("Deal Name")) + deal_value = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("Deal Value")) + deal_status = models.CharField(max_length=50, choices=DEAL_STATUS_CHOICES, default='new', verbose_name=_("Deal Status")) + priority = models.CharField(max_length=50, choices=PRIORITY_CHOICES, default='low', verbose_name=_("Priority")) + source = models.CharField(max_length=255, choices=DEAL_SOURCES_CHOICES, default='showroom', verbose_name=_("Source")) + created_by = models.ForeignKey(Staff, on_delete=models.SET_NULL, null=True, related_name="deals_created") + created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) + updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) + + class Meta: + verbose_name = _("Opportunity") + verbose_name_plural = _("Opportunities") + + def __str__(self): + return f"{self.deal_name} - {self.customer.get_full_name}" + + +class Notification(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notifications") + message = models.CharField(max_length=255, verbose_name=_("Message")) + is_read = models.BooleanField(default=False, verbose_name=_("Is Read")) + created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) + + class Meta: + verbose_name = _("Notification") + verbose_name_plural = _("Notifications") + ordering = ['-created_at'] + + def __str__(self): + return self.message + class Organization(models.Model, LocalizedNameMixin): dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='organizations') diff --git a/inventory/signals.py b/inventory/signals.py index bf402937..5fef2283 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -334,3 +334,11 @@ def create_customer(sender, instance, created, **kwargs): # quotation.status = 'pending' # quotation.save() +@receiver(post_save, sender=models.Opportunity) +def notify_staff_on_deal_status_change(sender, instance, **kwargs): + if instance.pk: + previous = models.Opportunity.objects.get(pk=instance.pk) + if previous.deal_status != instance.deal_status: + message = f"Deal '{instance.deal_name}' status changed from {previous.deal_status} to {instance.deal_status}." + models.Notification.objects.create(staff=instance.created_by, message=message) + diff --git a/inventory/urls.py b/inventory/urls.py index 9bffa6c9..95256655 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -27,6 +27,7 @@ urlpatterns = [ path('login/code/', allauth_views.RequestLoginCodeView.as_view(template_name='account/request_login_code.html')), #Dashboards path('dashboards/accounting/', views.AccountingDashboard.as_view(), name='accounting'), + path('dashboards/crm/', views.notifications_view, name='staff_dashboard'), # Dealer URLs path('dealers//', views.DealerDetailView.as_view(), name='dealer_detail'), @@ -40,7 +41,17 @@ urlpatterns = [ path('customers/create/', views.CustomerCreateView.as_view(), name='customer_create'), path('customers//update/', views.CustomerUpdateView.as_view(), name='customer_update'), path('customers//delete/', views.delete_customer, name='customer_delete'), - # Vendor URLs + path('customers//create_lead/', views.create_lead, name='create_lead'), + path('customers//opportunities/create/', views.OpportunityCreateView.as_view(), name='create_opportunity'), + # CRM URLs + path('opportunities//', views.OpportunityDetailView.as_view(), name='opportunity_detail'), + path('opportunities//edit/', views.OpportunityUpdateView.as_view(), name='update_opportunity'), + path('opportunities/', views.OpportunityListView.as_view(), name='opportunity_list'), + path('opportunities//delete/', views.OpportunityDeleteView.as_view(), name='delete_opportunity'), + path('notifications/', views.NotificationListView.as_view(), name='notifications_history'), + path('notifications//mark_as_read/', views.mark_notification_as_read, name='mark_notification_as_read'), + + #Vendor URLs path('vendors', views.VendorListView.as_view(), name='vendor_list'), path('vendors//', views.VendorDetailView.as_view(), name='vendor_detail'), path('vendors/create/', views.VendorCreateView.as_view(), name='vendor_create'), @@ -48,12 +59,8 @@ urlpatterns = [ path('vendors//delete/', views.VendorDetailView.as_view(), name='vendor_delete'), # Car URLs - path('cars/inventory/', - views.CarInventory.as_view(), - name='car_inventory_all'), - path('cars/inventory////', - views.CarInventory.as_view(), - name='car_inventory'), + path('cars/inventory/', views.CarInventory.as_view(), name='car_inventory_all'), + path('cars/inventory////', views.CarInventory.as_view(), name='car_inventory'), path('cars/inventory/stats', views.inventory_stats_view, name='inventory_stats'), path('cars//', views.CarDetailView.as_view(), name='car_detail'), path('cars//update/', views.CarUpdateView.as_view(), name='car_update'), diff --git a/inventory/views.py b/inventory/views.py index 5c621602..c671537f 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1,5 +1,6 @@ from django.core.paginator import Paginator +from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django_ledger.models import EntityModel, InvoiceModel,BankAccountModel,AccountModel,JournalEntryModel,TransactionModel,EstimateModel,CustomerModel,LedgerModel from django_ledger.forms.bank_account import BankAccountCreateForm,BankAccountUpdateForm @@ -38,6 +39,7 @@ from django.contrib import messages from django.db.models import Sum, F, Count from django.db import transaction +from .models import Customer from .services import ( elm, decodevin, @@ -1689,7 +1691,8 @@ class EstimateDetailView(LoginRequiredMixin, DetailView): if estimate.get_itemtxs_data(): total = sum(x.ce_cost_estimate for x in estimate.get_itemtxs_data()[0].all()) vat = models.VatRate.objects.filter(is_active=True).first() - kwargs["vate_amount"] = (total * vat.vat_rate) + # vat = settings.VAT_RATE + kwargs["vat_amount"] = (total * vat.vat_rate) kwargs["total"] = (total * vat.vat_rate) + total kwargs["vat"] = vat.rate kwargs["invoice"] = InvoiceModel.objects.all().filter(ce_model=estimate).first() @@ -1874,7 +1877,7 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView): total = sum(x.unit_cost * x.quantity for x in invoice.get_itemtxs_data()[0].all()) total = int(total) vat = models.VatRate.objects.filter(is_active=True).first() - kwargs["vate_amount"] = (total * vat.vat_rate) + kwargs["vat_amount"] = (total * vat.vat_rate) kwargs["total"] = (total * vat.vat_rate) + total kwargs["vat"] = vat.rate return super().get_context_data(**kwargs) @@ -1994,4 +1997,86 @@ def send_email_view(request,pk): send_email("manager@tenhal.sa", 'user@tenhal.sa',f"Estimate-{estimate.estimate_number}", msg) estimate.mark_as_review() messages.success(request, "Email sent successfully!") - return redirect("estimate_detail", pk=estimate.pk) \ No newline at end of file + return redirect("estimate_detail", pk=estimate.pk) + + +def create_lead(request, pk): + customer = get_object_or_404(models.Customer, pk=pk) + if customer.is_lead: + messages.warning(request, _('Customer is already a lead.')) + else: + customer.is_lead = True + customer.save() + messages.success(request, _('Customer successfully marked as a lead.')) + return redirect(reverse('customer_detail', kwargs={'pk': customer.pk})) + + +class OpportunityCreateView(CreateView): + model = models.Opportunity + form_class = forms.OpportunityForm + template_name = 'crm/opportunity_form.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['customer'] = models.Customer.objects.get(pk=self.kwargs['customer_id']) + context['cars'] = models.Car.objects.all() + return context + + def form_valid(self, form): + form.instance.customer = models.Customer.objects.get(pk=self.kwargs['customer_id']) + form.instance.created_by = self.request.user.staff + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy('opportunity_detail', kwargs={'pk': self.object.pk}) + + +class OpportunityUpdateView(UpdateView): + model = models.Opportunity + form_class = forms.OpportunityForm + template_name = 'crm/opportunity_form.html' + + def get_success_url(self): + return reverse_lazy('opportunity_detail', kwargs={'pk': self.object.pk}) + + +class OpportunityDetailView(DetailView): + model = models.Opportunity + template_name = 'crm/opportunity_detail.html' + context_object_name = 'opportunity' + + +class OpportunityListView(ListView): + model = models.Opportunity + template_name = 'crm/opportunity_list.html' + context_object_name = 'opportunities' + + +class OpportunityDeleteView(DeleteView): + model = models.Opportunity + template_name = 'crm/opportunity_confirm_delete.html' + success_url = reverse_lazy('opportunity_list') + + +def notifications_view(request): + notifications = models.Notification.objects.filter(user=request.user, is_read=False).order_by('-created_at') + return render(request, 'notifications.html', {'notifications': notifications}) + + +class NotificationListView(LoginRequiredMixin, ListView): + model = models.Notification + template_name = 'notifications_history.html' + context_object_name = 'notifications' + paginate_by = 10 + + def get_queryset(self): + return models.Notification.objects.filter(user=self.request.user).order_by('-created_at') + + +def mark_notification_as_read(request, pk): + notification = get_object_or_404(models.Notification, pk=pk) + notification.is_read = True + notification.save() + return redirect('notifications_history') + + diff --git a/static/js/main.js b/static/js/main.js index 0bd343e5..8324353b 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -16,17 +16,7 @@ function getCookie(name) { -const Toast = Swal.mixin({ - toast: true, - position: "top-end", - showConfirmButton: false, - timer: 3000, - timerProgressBar: true, - didOpen: (toast) => { - toast.onmouseenter = Swal.stopTimer; - toast.onmouseleave = Swal.resumeTimer; - } - }); + function notify(tag,msg){ Toast.fire({ icon: tag, diff --git a/static/js/travel-agency-dashboard.js b/static/js/travel-agency-dashboard.js index ae622bfe..1cd65b87 100644 --- a/static/js/travel-agency-dashboard.js +++ b/static/js/travel-agency-dashboard.js @@ -653,21 +653,6 @@ '.echart-financial-Activities' ); - const profitData = [ - [350000, 390000, 410700, 450000, 390000, 410700], - [245000, 310000, 420000, 480000, 530000, 580000], - [278450, 513220, 359890, 444567, 201345, 589000] - ]; - const revenueData = [ - [-810000, -640000, -630000, -590000, -620000, -780000], - [-482310, -726590, -589120, -674832, -811245, -455678], - [-432567, -688921, -517389, -759234, -601876, -485112] - ]; - const expansesData = [ - [-450000, -250000, -200000, -120000, -230000, -270000], - [-243567, -156789, -398234, -120456, -321890, -465678], - [-235678, -142345, -398765, -287456, -173890, -451234] - ]; if ($financialActivitiesChartEl) { const userOptions = getData($financialActivitiesChartEl, 'options'); diff --git a/templates/.DS_Store b/templates/.DS_Store index de0efc82..e94952cd 100644 Binary files a/templates/.DS_Store and b/templates/.DS_Store differ diff --git a/templates/base.html b/templates/base.html index 67ee5def..8858f472 100644 --- a/templates/base.html +++ b/templates/base.html @@ -55,128 +55,7 @@ {% block extra_js %}{% endblock extra_js %} - diff --git a/templates/crm/opportunity_confirm_delete.html b/templates/crm/opportunity_confirm_delete.html new file mode 100644 index 00000000..8f3b38fe --- /dev/null +++ b/templates/crm/opportunity_confirm_delete.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% block content %} +

Delete Opportunity

+

Are you sure you want to delete "{{ object.deal_name }}"?

+
+ {% csrf_token %} + + Cancel +
+{% endblock %} \ No newline at end of file diff --git a/templates/crm/opportunity_detail.html b/templates/crm/opportunity_detail.html new file mode 100644 index 00000000..70d85623 --- /dev/null +++ b/templates/crm/opportunity_detail.html @@ -0,0 +1,11 @@ +

{{ opportunity.deal_name }}

+

Customer: {{ opportunity.customer.get_full_name }}

+

Car: {{ opportunity.car }}

+

Deal Value: {{ opportunity.deal_value }}

+

Deal Status: {{ opportunity.get_deal_status_display }}

+

Priority: {{ opportunity.get_priority_display }}

+

Source: {{ opportunity.get_source_display }}

+

Created By: {{ opportunity.created_by.name }}

+

Created At: {{ opportunity.created_at }}

+

Updated At: {{ opportunity.updated_at }}

+Edit \ No newline at end of file diff --git a/templates/crm/opportunity_form.html b/templates/crm/opportunity_form.html new file mode 100644 index 00000000..fa43ec7c --- /dev/null +++ b/templates/crm/opportunity_form.html @@ -0,0 +1,6 @@ +

{% if form.instance.pk %}Edit{% else %}Create{% endif %} Opportunity

+
+ {% csrf_token %} + {{ form.as_p }} + +
\ No newline at end of file diff --git a/templates/crm/opportunity_list.html b/templates/crm/opportunity_list.html new file mode 100644 index 00000000..9495956d --- /dev/null +++ b/templates/crm/opportunity_list.html @@ -0,0 +1,50 @@ +{% extends 'base.html' %} + +{% block content %} +

Opportunities

+Create New Opportunity + + + + + + + + + + + + + + + + + + {% for opportunity in opportunities %} + + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
Deal NameCustomerCarDeal ValueDeal StatusPrioritySourceCreated ByCreated AtActions
{{ opportunity.deal_name }}{{ opportunity.customer.get_full_name }}{{ opportunity.car }}{{ opportunity.deal_value }}{{ opportunity.get_deal_status_display }}{{ opportunity.get_priority_display }}{{ opportunity.get_source_display }}{{ opportunity.created_by.name }}{{ opportunity.created_at }} + View + Edit +
+ {% csrf_token %} + +
+
No opportunities found.
+{% endblock %} \ No newline at end of file diff --git a/templates/customers/view_customer.html b/templates/customers/view_customer.html index c2d58207..1a4df724 100644 --- a/templates/customers/view_customer.html +++ b/templates/customers/view_customer.html @@ -51,7 +51,12 @@ data-bs-target="#deleteModal">{{ _("Delete") }}
- {{_("Update")}} + {{_("Update")}} + {% if not customer.is_lead %} + {{ _("Mark as Lead")}} + {% else %} + {{_("Opportunity")}} + {% endif %}
@@ -64,7 +69,7 @@
-
+

{{ customer.first_name }} {{ customer.middle_name }} {{ customer.last_name }}

diff --git a/templates/dashboards/accounting.html b/templates/dashboards/accounting.html index 9dd42880..31d24bf3 100644 --- a/templates/dashboards/accounting.html +++ b/templates/dashboards/accounting.html @@ -290,5 +290,23 @@
+ {% endblock %} diff --git a/templates/header.html b/templates/header.html index 76e739dc..400dbd75 100644 --- a/templates/header.html +++ b/templates/header.html @@ -178,127 +178,7 @@