update in opportunity

This commit is contained in:
gitea 2025-02-12 15:23:40 +00:00
parent 93674f646b
commit 040e26b3f9
16 changed files with 1753 additions and 1527 deletions

View File

@ -8,11 +8,15 @@ from django.core.validators import RegexValidator
from django import forms
from django.contrib.auth import get_user_model
from phonenumber_field.phonenumber import PhoneNumber
from .models import Status, Stage
from .mixins import AddClassMixin
from django.forms.models import inlineformset_factory
from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase
from django_ledger.forms.estimate import EstimateModelCreateForm as EstimateModelCreateFormBase
from django_ledger.forms.invoice import (
InvoiceModelCreateForm as InvoiceModelCreateFormBase,
)
from django_ledger.forms.estimate import (
EstimateModelCreateForm as EstimateModelCreateFormBase,
)
from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase
from django_ledger.forms.vendor import VendorModelForm
@ -38,10 +42,23 @@ from .models import (
# SaleQuotationCar,
AdditionalServices,
Staff,
Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel,SaleOrder,CarMake
Opportunity,
Priority,
Sources,
Lead,
Activity,
Notes,
CarModel,
SaleOrder,
CarMake,
)
from django_ledger import models as ledger_models
from django.forms import ModelMultipleChoiceField, ValidationError, DateInput,DateTimeInput
from django.forms import (
ModelMultipleChoiceField,
ValidationError,
DateInput,
DateTimeInput,
)
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
from django.forms import formset_factory
@ -111,27 +128,22 @@ class CustomerForm(forms.Form):
last_name = forms.CharField()
national_id = forms.CharField(max_length=10)
email = forms.EmailField()
phone_number = PhoneNumberField(
min_length=10,
max_length=10,
region="SA",
)
phone_number = PhoneNumberField(region="SA")
address = forms.CharField()
class OrganizationForm(forms.Form):
name = forms.CharField()
arabic_name = forms.CharField()
email = forms.EmailField()
phone_number = PhoneNumberField(
min_length=10,
max_length=10,
region="SA",
)
phone_number = PhoneNumberField(region="SA")
crn = forms.CharField()
vrn = forms.CharField()
address = forms.CharField()
contact_person = forms.CharField(required=False)
logo = forms.ImageField(required=False)
# class CustomerForm(forms.ModelForm, AddClassMixin):
# class Meta:
# model = Customer
@ -193,7 +205,9 @@ class CarForm(
(obj.id_car_model, obj.get_local_name()) for obj in queryset
]
if "vendor" in self.fields:
self.fields["vendor"].queryset = ledger_models.VendorModel.objects.filter(active=True)
self.fields["vendor"].queryset = ledger_models.VendorModel.objects.filter(
active=True
)
# queryset = self.fields["vendor"].queryset
# self.fields["vendor"].choices = [
# (obj.pk, obj.get_local_name()) for obj in queryset
@ -303,11 +317,10 @@ class CarRegistrationForm(forms.ModelForm):
fields = ["plate_number", "text1", "text2", "text3", "registration_date"]
widgets = {
'registration_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
"registration_date": forms.DateTimeInput(attrs={"type": "datetime-local"}),
}
# class VendorForm(VendorModelForm):
# pass
class VendorForm(forms.ModelForm):
@ -517,8 +530,6 @@ class WizardForm2(forms.Form):
phone_number = PhoneNumberField(
label=_("Phone Number"),
min_length=10,
max_length=10,
widget=forms.TextInput(
attrs={
"class": "form-control form-control-sm",
@ -598,7 +609,9 @@ class WizardForm3(forms.Form):
class ItemForm(forms.Form):
item = forms.ModelChoiceField(
queryset=ledger_models.ItemModel.objects.all(), label="Item", required=True,
queryset=ledger_models.ItemModel.objects.all(),
label="Item",
required=True,
validators=[MinLengthValidator(5)],
)
quantity = forms.DecimalField(label="Quantity", required=True)
@ -609,7 +622,9 @@ class ItemForm(forms.Form):
class PaymentForm(forms.Form):
invoice = forms.ModelChoiceField(
queryset=ledger_models.InvoiceModel.objects.all(), label="Invoice", required=False
queryset=ledger_models.InvoiceModel.objects.all(),
label="Invoice",
required=False,
)
bill = forms.ModelChoiceField(
queryset=ledger_models.BillModel.objects.all(), label="Bill", required=False
@ -626,13 +641,15 @@ class PaymentForm(forms.Form):
label="Payment Method",
required=True,
)
payment_date = forms.DateField(label="Payment Date", widget=DateInput(attrs={'type': 'date'}), required=True)
payment_date = forms.DateField(
label="Payment Date", widget=DateInput(attrs={"type": "date"}), required=True
)
def clean_amount(self):
invoice = self.cleaned_data['invoice']
bill = self.cleaned_data['bill']
invoice = self.cleaned_data["invoice"]
bill = self.cleaned_data["bill"]
model = invoice if invoice else bill
amount = self.cleaned_data['amount']
amount = self.cleaned_data["amount"]
if amount + model.amount_paid > model.amount_due:
raise forms.ValidationError("Payment amount is greater than amount due")
if amount <= 0:
@ -652,103 +669,136 @@ class EmailForm(forms.Form):
class LeadForm(forms.ModelForm):
id_car_make = forms.ModelChoiceField(label="Make",
id_car_make = forms.ModelChoiceField(
label="Make",
queryset=CarMake.objects.filter(is_sa_import=True),
widget=forms.Select(attrs={"class": "form-control form-control-sm","hx-get":"","hx-include":"#id_id_car_make","hx-select":"#div_id_id_car_model","hx-target":"#div_id_id_car_model","hx-swap":"outerHTML"}),
required=True
widget=forms.Select(
attrs={
"class": "form-control form-control-sm",
"hx-get": "",
"hx-include": "#id_id_car_make",
"hx-select": "#div_id_id_car_model",
"hx-target": "#div_id_id_car_model",
"hx-swap": "outerHTML",
"hx-on::before-request": "document.querySelector('#id_id_car_model').setAttribute('disabled', true)",
"hx-on::after-request": "document.querySelector('#id_id_car_model').removeAttribute('disabled')",
}
),
required=True,
)
id_car_model = forms.ModelChoiceField(label="Model", queryset=CarModel.objects.none(),widget=forms.Select(attrs={"class": "form-control form-control-sm"}),required=True)
id_car_model = forms.ModelChoiceField(
label="Model",
queryset=CarModel.objects.none(),
widget=forms.Select(attrs={"class": "form-control form-control-sm"}),
required=True,
)
class Meta:
model = Lead
fields = [
'first_name',
'last_name',
'email',
'phone_number',
'address',
'id_car_make',
'id_car_model',
'year',
'source',
'channel',
'staff',
'priority',
]
"first_name",
"last_name",
"email",
"phone_number",
"address",
"id_car_make",
"id_car_model",
"year",
"source",
"channel",
"staff",
"priority",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
if "id_car_make" in self.fields:
queryset = self.fields["id_car_make"].queryset.filter(is_sa_import=True)
self.fields["id_car_make"].choices = [
(obj.id_car_make, obj.get_local_name()) for obj in queryset
]
class ScheduleForm(forms.ModelForm):
scheduled_at = forms.DateTimeField(widget=DateTimeInput(attrs={'type': 'datetime-local'}))
scheduled_at = forms.DateTimeField(
widget=DateTimeInput(attrs={"type": "datetime-local"})
)
class Meta:
model = Schedule
fields = ['purpose','scheduled_type', 'scheduled_at','duration', 'notes']
fields = ["purpose", "scheduled_type", "scheduled_at", "duration", "notes"]
class NoteForm(forms.ModelForm):
class Meta:
model = Notes
fields = ['note']
fields = ["note"]
class ActivityForm(forms.ModelForm):
class Meta:
model = Activity
fields = ['activity_type', 'notes']
fields = ["activity_type", "notes"]
class OpportunityForm(forms.ModelForm):
class Meta:
model = Opportunity
fields = ['customer', 'car', 'stage', 'probability', 'staff', 'closing_date']
fields = ["customer", "car", "stage", "probability", "staff", "closing_date"]
class InvoiceModelCreateForm(InvoiceModelCreateFormBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['cash_account'].widget = forms.HiddenInput()
self.fields['prepaid_account'].widget = forms.HiddenInput()
self.fields['unearned_account'].widget = forms.HiddenInput()
self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'}))
self.fields["cash_account"].widget = forms.HiddenInput()
self.fields["prepaid_account"].widget = forms.HiddenInput()
self.fields["unearned_account"].widget = forms.HiddenInput()
self.fields["date_draft"] = forms.DateField(
widget=DateInput(attrs={"type": "date"})
)
def get_customer_queryset(self):
if 'customer' in self.fields:
self.fields['customer'].queryset = self.USER_MODEL.dealer.entity.get_customers()
if "customer" in self.fields:
self.fields[
"customer"
].queryset = self.USER_MODEL.dealer.entity.get_customers()
class BillModelCreateForm(BillModelCreateFormBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['cash_account'].widget = forms.HiddenInput()
self.fields['prepaid_account'].widget = forms.HiddenInput()
self.fields['unearned_account'].widget = forms.HiddenInput()
self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'}))
self.fields["cash_account"].widget = forms.HiddenInput()
self.fields["prepaid_account"].widget = forms.HiddenInput()
self.fields["unearned_account"].widget = forms.HiddenInput()
self.fields["date_draft"] = forms.DateField(
widget=DateInput(attrs={"type": "date"})
)
class SaleOrderForm(forms.ModelForm):
class Meta:
model = SaleOrder
fields = ['estimate','payment_method', 'comments']
widgets = {
'comments': forms.Textarea(attrs={'rows': 3}),
fields = ["estimate", "payment_method", "comments"]
widgets = {
"comments": forms.Textarea(attrs={"rows": 3}),
}
class EstimateModelCreateForm(EstimateModelCreateFormBase):
class Meta:
model = ledger_models.EstimateModel
fields = ['title', 'customer', 'terms']
fields = ["title","customer", "terms"]
widgets = {
'customer': forms.Select(attrs={
'id': 'djl-customer-estimate-customer-input',
'class': 'input',
'label': _('Customer'),
}),
"customer": forms.Select(
attrs={
"id": "djl-customer-estimate-customer-input",
"class": "input",
"label": _("Customer"),
}
),
'terms': forms.Select(attrs={
'id': 'djl-customer-estimate-terms-input',
'class': 'input',
@ -757,23 +807,57 @@ class EstimateModelCreateForm(EstimateModelCreateFormBase):
'title': forms.TextInput(attrs={
'id': 'djl-customer-job-title-input',
'class': 'input' + ' is-large',
'label': _('Title'),
})
}
labels = {
'title': _('Title'),
'terms': _('Terms'),
'customer': _('Customer'),
"customer": _("Customer"),
}
def __init__(self, *args, entity_slug, user_model, **kwargs):
super(EstimateModelCreateForm, self).__init__(*args, entity_slug=entity_slug, user_model=user_model, **kwargs)
super(EstimateModelCreateForm, self).__init__(
*args, entity_slug=entity_slug, user_model=user_model, **kwargs
)
self.ENTITY_SLUG = entity_slug
self.USER_MODEL = user_model
self.fields['customer'].queryset = self.get_customer_queryset()
self.fields["customer"].queryset = self.get_customer_queryset()
def get_customer_queryset(self):
return self.USER_MODEL.dealer.entity.get_customers()
class OpportunityStatusForm(forms.Form):
status = forms.ChoiceField(
label="Status",
choices=Status.choices,
widget=forms.Select(
attrs={
"class": "form-control form-control-sm",
"hx-get": "{% url 'opportunity_update_status' opportunity.id %}",
"hx-target": ".other-information",
"hx-select": ".other-information",
"hx-swap": "outerHTML",
"hx-on::after-request": "this.setAttribute('disabled','true')",
"disabled": "disabled",
}
),
required=True,
)
stage = forms.ChoiceField(
label="Stage",
choices=Stage.choices,
widget=forms.Select(
attrs={
"class": "form-control form-control-sm",
"hx-target": ".other-information",
"hx-select": ".other-information",
"hx-swap": "outerHTML",
"hx-on::after-request": "this.setAttribute('disabled','true')",
"disabled": "disabled",
}
),
required=True,
)

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.17 on 2025-02-12 10:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0018_customer_user'),
]
operations = [
migrations.AddField(
model_name='opportunity',
name='lead',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='inventory.lead'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.17 on 2025-02-12 10:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0019_opportunity_lead'),
]
operations = [
migrations.AlterField(
model_name='opportunity',
name='closing_date',
field=models.DateField(blank=True, null=True, verbose_name='Closing Date'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.17 on 2025-02-12 10:37
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('inventory', '0020_alter_opportunity_closing_date'),
]
operations = [
migrations.AlterField(
model_name='opportunity',
name='lead',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunity', to='inventory.lead'),
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 4.2.17 on 2025-02-12 14:09
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.DJANGO_LEDGER_ESTIMATE_MODEL),
('inventory', '0021_alter_opportunity_lead'),
]
operations = [
migrations.AddField(
model_name='opportunity',
name='estimate',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to=settings.DJANGO_LEDGER_ESTIMATE_MODEL),
),
]

View File

@ -1203,8 +1203,10 @@ class Lead(models.Model):
customer.additional_info = {}
customer.additional_info.update({"info":self.to_dict()})
self.customer = customer
self.status = Status.QUALIFIED
customer.save()
self.save()
return customer
def get_latest_schedule(self):
return self.schedules.order_by('-scheduled_at').first()
@ -1307,18 +1309,19 @@ class Opportunity(models.Model):
related_name="owner",
verbose_name=_("Owner"),
)
lead = models.OneToOneField("Lead",related_name="opportunity", on_delete=models.CASCADE,null=True,blank=True)
probability = models.PositiveIntegerField(validators=[validate_probability])
closing_date = models.DateField(verbose_name=_("Closing Date"))
closing_date = models.DateField(verbose_name=_("Closing Date"),null=True,blank=True)
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
closed = models.BooleanField(default=False, verbose_name=_("Closed"))
estimate = models.OneToOneField(EstimateModel, related_name="opportunity",on_delete=models.SET_NULL,null=True,blank=True)
class Meta:
verbose_name = _("Opportunity")
verbose_name_plural = _("Opportunities")
def __str__(self):
return f"{self.car.id_car_make.name} - {self.car.id_car_model.name} : {self.customer}"
return f"Opportunity for {self.customer.customer_name}"
class Notes(models.Model):

View File

@ -664,21 +664,35 @@ def create_customer(sender, instance, created, **kwargs):
print(f"Customer created: {name}")
@receiver(post_save, sender=models.Customer)
@receiver(post_save, sender=models.CustomerModel)
def create_customer_user(sender, instance, created, **kwargs):
if created:
user = User.objects.create(
username=instance.email,
email=instance.email,
password=None,
first_name=instance.first_name,
last_name=instance.last_name
email=instance.email,
first_name=instance.additional_info.get('first_name',''),
last_name=instance.additional_info.get('last_name','')
)
user.is_active = True
user.is_staff = True
user.is_active = False
user.is_staff = False
user.save()
instance.user = user
instance.save()
# @receiver(post_save, sender=models.Customer)
# def create_customer_user(sender, instance, created, **kwargs):
# if created:
# user = User.objects.create(
# username=instance.email,
# email=instance.email,
# password=None,
# first_name=instance.first_name,
# last_name=instance.last_name
# )
# user.is_active = True
# user.is_staff = True
# user.save()
# instance.user = user
# instance.save()
# Create Item

View File

@ -143,6 +143,11 @@ urlpatterns = [
views.delete_opportunity,
name="delete_opportunity",
),
path(
"crm/opportunities/<int:pk>/opportunity_update_status/",
views.opportunity_update_status,
name="opportunity_update_status",
),
# path('crm/opportunities/<int:pk>/logs/', views.OpportunityLogsView.as_view(), name='opportunity_logs'),
path(
"crm/notifications/",
@ -414,6 +419,7 @@ urlpatterns = [
name="estimate_detail",
),
path("sales/estimates/create/", views.create_estimate, name="estimate_create"),
path("sales/estimates/create/<int:pk>/", views.create_estimate, name="estimate_create_from_opportunity"),
path(
"sales/estimates/<uuid:pk>/estimate_mark_as/",
views.estimate_mark_as,

View File

@ -1256,35 +1256,39 @@ def add_activity_to_customer(request, pk):
def CustomerCreateView(request):
if request.method == "POST":
customer_dict = {
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
}
dealer = get_user_type(request)
customer_name = (
customer_dict["first_name"]
+ " "
+ customer_dict["middle_name"]
+ " "
+ customer_dict["last_name"]
)
instance = dealer.entity.create_customer(
customer_model_kwargs={
"customer_name": customer_name,
"address_1": customer_dict["address"],
"phone": customer_dict["phone_number"],
"email": customer_dict["email"],
}
)
customer_dict["pk"] = str(instance.pk)
instance.additional_info["customer_info"] = customer_dict
instance.additional_info["type"] = "customer"
instance.save()
messages.success(request, _("Customer created successfully."))
return redirect("customer_list")
form = forms.CustomerForm()
if request.method == "POST":
dealer = get_user_type(request)
if dealer.entity.get_customers().filter(email=request.POST["email"]).exists():
messages.error(request, _("Customer with this email already exists."))
form = forms.CustomerForm(request.POST)
else:
customer_dict = {
x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"
}
customer_name = (
customer_dict["first_name"]
+ " "
+ customer_dict["middle_name"]
+ " "
+ customer_dict["last_name"]
)
instance = dealer.entity.create_customer(
customer_model_kwargs={
"customer_name": customer_name,
"address_1": customer_dict["address"],
"phone": customer_dict["phone_number"],
"email": customer_dict["email"],
}
)
customer_dict["pk"] = str(instance.pk)
instance.additional_info = {}
instance.additional_info["customer_info"] = customer_dict
instance.additional_info["type"] = "customer"
instance.save()
messages.success(request, _("Customer created successfully."))
return redirect("customer_list")
return render(request, "customers/customer_form.html", {"form": form})
@ -2405,11 +2409,11 @@ class EstimateListView(LoginRequiredMixin, ListView):
# @csrf_exempt
@login_required
def create_estimate(request):
def create_estimate(request,pk=None):
dealer = get_user_type(request)
entity = dealer.entity
if request.method == "POST":
if request.method == "POST":
# try:
data = json.loads(request.body)
title = data.get("title")
@ -2522,7 +2526,13 @@ def create_estimate(request):
item_instance = ItemModel.objects.get(additioinal_info__car_info__hash=items)
instance = models.Car.objects.get(hash=item)
response = reserve_car(instance, request)
opportunity_id = data.get("opportunity_id")
if opportunity_id:
opportunity = models.Opportunity.objects.get(pk=int(opportunity_id))
opportunity.estimate = estimate
opportunity.save()
url = reverse("estimate_detail", kwargs={"pk": estimate.pk})
return JsonResponse(
{
@ -2536,6 +2546,12 @@ def create_estimate(request):
entity_slug=entity.slug, user_model=entity.admin
)
form.fields["customer"].queryset = entity.get_customers().filter(active=True)
if pk:
opportunity = models.Opportunity.objects.get(pk=pk)
customer = opportunity.customer
form.initial['customer'] = customer
car_list = models.Car.objects.filter(dealer=dealer).exclude(status="reserved").annotate(color=F('colors__exterior__rgb'),color_name=F('colors__exterior__name')).values_list(
'id_car_make__name', 'id_car_model__name','id_car_serie__name','id_car_trim__name','color','color_name','hash').distinct()
context = {
@ -2552,6 +2568,7 @@ def create_estimate(request):
}
for x in car_list
],
"opportunity_id": opportunity_id
}
return render(request, "sales/estimates/estimate_form.html", context)
@ -2567,7 +2584,7 @@ class EstimateDetailView(LoginRequiredMixin, DetailView):
if estimate.get_itemtxs_data():
calculator = CarFinanceCalculator(estimate)
finance_data = calculator.get_finance_data()
print(finance_data.get("cars"))
kwargs["data"] = finance_data
kwargs["invoice"] = (
InvoiceModel.objects.all().filter(ce_model=estimate).first()
@ -3147,14 +3164,11 @@ def lead_convert(request, pk):
dealer = get_user_type(request)
if lead.is_converted:
messages.error(request, "Lead is already converted to customer.")
return redirect("opportunity_create",pk=lead.pk)
if not models.Car.objects.filter(id_car_make=lead.id_car_make,id_car_model=lead.id_car_model).first():
messages.error(request, "Cannot convert lead to customer. Car model not found.")
return redirect("lead_list")
lead.convert_to_customer(dealer.entity)
messages.success(request, "Lead converted to customer successfully!")
return redirect("opportunity_create",pk=lead.pk)
else:
customer = lead.convert_to_customer(dealer.entity)
models.Opportunity.objects.create(dealer=dealer,customer=customer,lead=lead,probability=50,stage=models.Stage.PROSPECT,staff=lead.staff,status=models.Status.QUALIFIED)
messages.success(request, "Lead converted to customer successfully!")
return redirect("lead_list")
@login_required
def schedule_lead(request, pk):
@ -3206,7 +3220,9 @@ def schedule_lead(request, pk):
def send_lead_email(request, pk):
lead = get_object_or_404(models.Lead, pk=pk)
dealer = get_user_type(request)
lead.convert_to_customer(dealer.entity)
lead.status = models.Status.IN_PROGRESS
lead.save()
# lead.convert_to_customer(dealer.entity)
if request.method == "POST":
send_email(
@ -3307,6 +3323,17 @@ class OpportunityDetailView(DetailView):
template_name = "crm/opportunities/opportunity_detail.html"
context_object_name = "opportunity"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = forms.OpportunityStatusForm()
url = reverse("opportunity_update_status", args=[self.object.pk])
form.fields["status"].widget.attrs["hx-get"] = url
form.fields["stage"].widget.attrs["hx-get"] = url
form.fields["status"].initial = self.object.status
form.fields["stage"].initial = self.object.stage
context["status_form"] = form
return context
class OpportunityListView(ListView):
model = models.Opportunity
@ -3317,8 +3344,7 @@ class OpportunityListView(ListView):
def get_queryset(self):
dealer = get_user_type(self.request)
return models.Opportunity.objects.filter(dealer=dealer).all()
@login_required
def delete_opportunity(request, pk):
opportunity = get_object_or_404(models.Opportunity, pk=pk)
@ -3326,6 +3352,21 @@ def delete_opportunity(request, pk):
messages.success(request, _("Opportunity deleted successfully."))
return redirect("opportunity_list")
def opportunity_update_status(request,pk):
opportunity = get_object_or_404(models.Opportunity, pk=pk)
status = request.GET.get("status")
stage = request.GET.get("stage")
if status:
opportunity.status = status
if stage:
opportunity.stage = stage
opportunity.save()
messages.success(request,"Opportunity status updated successfully")
# response = HttpResponse(render(request, "crm/opportunities/opportunity_detail.html"),{"opportunity":opportunity})
response = HttpResponse(redirect("opportunity_detail",pk=opportunity.pk))
response['HX-Refresh'] = 'true'
return response
# return render(request,"crm/opportunities/opportunity_detail.html",{"opportunity":opportunity})
# class OpportunityLogsView(LoginRequiredMixin, ListView):
# model = models.OpportunityLog

View File

@ -36,7 +36,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&amp;display=swap" rel="stylesheet">
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" rel="stylesheet">
<link href="{% static 'css/sweetalert2.min.css' %}" rel="stylesheet">
<link href="{% static 'vendors/flatpickr/flatpickr.min.css' %}" rel="stylesheet">
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.8/css/line.css">
<link href="{% static 'css/custom.css' %}" rel="stylesheet">
@ -57,38 +57,10 @@
{% endblock %}
</head>
<body>
<script>
{% if messages %}
{% for message in messages %}
const Toast = Swal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 2000,
timerProgressBar: false,
didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer;
}
});
Toast.fire({
icon: "{{ message.tags }}",
titleText: "{{ message| safe }}"});
{% endfor %}
{% endif %}
function notify(tag,msg){
Toast.fire({
icon: tag,
titleText: msg
});
}
</script>
{% include "toast-alert.html" %}
<main class="main" id="top">
{% include 'header.html' %}
<div class="content">
{% include "plans/expiration_messages.html" %}
{% block period_navigation %}

View File

@ -79,6 +79,18 @@
<span>{{ _("Channel")|capfirst }}</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="grid"></span></div>
<span>{{ _("Stage")|capfirst }}</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="grid"></span></div>
<span>{{ _("Is Opportunity")|capfirst }}</span>
</div>
</th>
<th class="align-middle white-space-nowrap text-uppercase" scope="col" style="width: 15%;">
{{ _("Create date") }}
</th>
@ -152,6 +164,28 @@
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.staff|upper }}</td>
<td class="align-middle white-space-nowrap fw-semibold text-body-highlight">{{ lead.source|upper }}</td>
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">{{ lead.channel|upper }}</td>
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
{% if lead.opportunity.stage == "prospect" %}
<span class="badge text-bg-primary">{{ lead.opportunity.stage|upper }}</span>
{% elif lead.opportunity.stage == "proposal" %}
<span class="badge text-bg-info">{{ lead.opportunity.stage|upper }}</span>
{% elif lead.opportunity.stage == "negotiation" %}
<span class="badge text-bg-warning">{{ lead.opportunity.stage|upper }}</span>
{% elif lead.opportunity.stage == "closed_won" %}
<span class="badge text-bg-success">{{ lead.opportunity.stage|upper }}</span>
{% elif lead.opportunity.stage == "closed_lost" %}
<span class="badge text-bg-danger">{{ lead.opportunity.stage|upper }}</span>
{% endif %}
</td>
<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' lead.opportunity.id %}">
<span class="badge badge-phoenix badge-phoenix-success">View Details <i class="fa-solid fa-arrow-up-right-from-square"></i></span>
</a>
{% else %}
<span class="badge badge-phoenix badge-phoenix-danger">{{ _("No") }}</span>
{% endif %}
</td>
<td class="align-middle white-space-nowrap text-end">
<div class="btn-reveal-trigger position-static">
<button
@ -169,7 +203,7 @@
<a href="{% url 'send_lead_email' lead.id %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
<a href="{% url 'schedule_lead' lead.id %}" class="dropdown-item text-success-dark">{% trans "Set Schedule" %}</a>
{% if not lead.is_converted %}
<a href="{% url 'lead_convert' lead.id %}" class="dropdown-item text-success-dark">{% trans "Convert To Customer" %}</a>
<a href="{% url 'lead_convert' lead.id %}" class="dropdown-item text-success-dark">{% trans "Convert To Opportunity" %}</a>
{% endif %}
<div class="dropdown-divider"></div>
<button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans "Delete" %}</button>

File diff suppressed because it is too large Load Diff

View File

@ -69,7 +69,6 @@
{% for customer in customers %}
<!-- Delete Modal -->
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td>
@ -89,7 +88,7 @@
{{ customer.address_1 }}</td>
<td class="date align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 text-body-tertiary">{{ customer.created|date }}</td>
<td class="align-middle white-space-nowrap text-end pe-0 ps-4">
<a href="#" class="btn btn-sm btn-phoenix-primary me-2" data-url="{% url 'customer_update' customer.pk %}">
<a href="{% url 'customer_update' customer.pk %}" class="btn btn-sm btn-phoenix-primary me-2" data-url="{% url 'customer_update' customer.pk %}">
<i class="fas fa-pen"></i>
</a>
<button class="btn btn-phoenix-danger btn-sm delete-btn"

View File

@ -186,7 +186,7 @@
</div>
</a>
<!-- more inner pages-->
</li>
{% comment %} </li>
<li class="nav-item">
<a class="nav-link" href="{% url 'opportunity_list' %}">
<div class="d-flex align-items-center">
@ -194,7 +194,7 @@
</div>
</a>
<!-- more inner pages-->
</li>
</li> {% endcomment %}
</ul>
</div>
</div>

View File

@ -111,7 +111,8 @@
customer: document.querySelector('[name=customer]').value,
terms: document.querySelector('[name=terms]').value,
item: [],
quantity: []
quantity: [],
opportunity_id: "{{opportunity_id}}"
};

View File

@ -0,0 +1,26 @@
<div class="toast-container">
<script>
{% if messages %}
{% for message in messages %}
try {
let Toast = Swal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 2000,
timerProgressBar: false,
didOpen: (toast) => {
toast.onmouseenter = Swal.stopTimer;
toast.onmouseleave = Swal.resumeTimer;
} });
Toast.fire({
icon: "{{ message.tags }}",
titleText: "{{ message| safe }}"});
} catch (error) {
console.log(error);
}
{% endfor %}
{% endif %}
function notify(tag,msg){Toast.fire({icon: tag,titleText: msg});}
</script>
</div>