Compare commits
No commits in common. "f867f925627e171cc60e0e7df58bb144bddb0f8c" and "d3a36aab7b42c83661437d5a315f33fab4221ff9" have entirely different histories.
f867f92562
...
d3a36aab7b
@ -46,3 +46,19 @@ application = ProtocolTypeRouter(
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
|
|
||||||
|
if not settings.DEBUG:
|
||||||
|
site = Site.objects.get(id=settings.SITE_ID)
|
||||||
|
if site.domain != settings.PRODUCTION_DOMAIN:
|
||||||
|
site.domain = settings.PRODUCTION_DOMAIN
|
||||||
|
site.name = settings.SITE_NAME
|
||||||
|
site.save()
|
||||||
|
except Exception as e:
|
||||||
|
# Log error but don't crash the app
|
||||||
|
if settings.DEBUG:
|
||||||
|
print(f"Site configuration error in WSGI: {e}")
|
||||||
@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
sudo apt-get update && sudo apt-get install gettext libgl1 libglib2.0-dev libzbar0 cmake build-essential xmlsec1 libxmlsec1-dev pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl libssl-dev -y
|
sudo apt-get update && sudo apt-get install libgl1 libglib2.0-dev libzbar0 cmake build-essential xmlsec1 libxmlsec1-dev pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl libssl-dev -y
|
||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install -r requirements_dev.txt
|
pip install -r requirements_dev.txt
|
||||||
./apply_initial_migrations.sh
|
./apply_initial_migrations.sh
|
||||||
|
|||||||
@ -2118,39 +2118,12 @@ class AdditionalFinancesForm(forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
for field in self.fields.values():
|
|
||||||
if isinstance(field, forms.ModelMultipleChoiceField):
|
|
||||||
field.widget.choices = [
|
|
||||||
(obj.pk, f"{obj.name} - {obj.price:.2f}")
|
|
||||||
for obj in field.queryset
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class VatRateForm(forms.ModelForm):
|
class VatRateForm(forms.ModelForm):
|
||||||
rate = forms.DecimalField(
|
|
||||||
max_digits=5,
|
|
||||||
decimal_places=2,
|
|
||||||
min_value=0,
|
|
||||||
max_value=100,
|
|
||||||
label=_("VAT Rate (%)"),
|
|
||||||
help_text=_("Enter VAT rate as percentage (e.g., 0.15 for 15%)")
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VatRate
|
model = VatRate
|
||||||
fields = ["rate"]
|
fields = ["rate"]
|
||||||
|
|
||||||
def clean_rate(self):
|
|
||||||
rate = self.cleaned_data['rate']
|
|
||||||
return rate / 100
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
if self.instance and self.instance.pk:
|
|
||||||
self.fields['rate'].initial = self.instance.rate * 100
|
|
||||||
|
|
||||||
|
|
||||||
class CustomSetPasswordForm(SetPasswordForm):
|
class CustomSetPasswordForm(SetPasswordForm):
|
||||||
new_password1 = forms.CharField(
|
new_password1 = forms.CharField(
|
||||||
|
|||||||
@ -17,7 +17,6 @@ def check_create_coa_accounts(task):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
dealer_id = task.kwargs.get('dealer_id')
|
dealer_id = task.kwargs.get('dealer_id')
|
||||||
coa_slug = task.kwargs.get('coa_slug', None)
|
|
||||||
if not dealer_id:
|
if not dealer_id:
|
||||||
logger.error("No dealer_id in task kwargs")
|
logger.error("No dealer_id in task kwargs")
|
||||||
return
|
return
|
||||||
@ -30,13 +29,7 @@ def check_create_coa_accounts(task):
|
|||||||
logger.error(f"No entity for dealer {dealer_id}")
|
logger.error(f"No entity for dealer {dealer_id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
if coa_slug:
|
coa = entity.get_default_coa()
|
||||||
try:
|
|
||||||
coa = entity.get_coa_model_qs().get(slug=coa_slug)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"COA with slug {coa_slug} not found for entity {entity.id}: {e}")
|
|
||||||
else:
|
|
||||||
coa = entity.get_default_coa()
|
|
||||||
if not coa:
|
if not coa:
|
||||||
logger.error(f"No COA for entity {entity.id}")
|
logger.error(f"No COA for entity {entity.id}")
|
||||||
return
|
return
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
from datetime import timedelta
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone
|
|
||||||
from inventory.tasks import send_email
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Deactivates expired user plans"
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
users_without_plan = User.objects.filter(
|
|
||||||
is_active=True, userplan=None, dealer__isnull=False, date_joined__lte=timezone.now()-timedelta(days=7)
|
|
||||||
)
|
|
||||||
|
|
||||||
count = users_without_plan.count()
|
|
||||||
for user in users_without_plan:
|
|
||||||
user.is_active = False
|
|
||||||
user.save()
|
|
||||||
subject = 'Your account has been deactivated'
|
|
||||||
message = """
|
|
||||||
Hello {},\n
|
|
||||||
Your account has been deactivated, please contact us at {} if you have any questions.
|
|
||||||
|
|
||||||
Regards,\n
|
|
||||||
Tenhal Team
|
|
||||||
""".format(user.dealer.name, settings.DEFAULT_FROM_EMAIL)
|
|
||||||
from_email = settings.DEFAULT_FROM_EMAIL
|
|
||||||
recipient_list = user.email
|
|
||||||
send_email(from_email, recipient_list,subject, message)
|
|
||||||
|
|
||||||
self.stdout.write(
|
|
||||||
self.style.SUCCESS(
|
|
||||||
f"Successfully deactivated {count} dealers who created account but dont have userplan"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
@ -57,7 +57,7 @@ class Command(BaseCommand):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
send_email(
|
send_email(
|
||||||
settings.DEFAULT_FROM_EMAIL,
|
"noreply@yourdomain.com",
|
||||||
inv.customer.email,
|
inv.customer.email,
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
@ -118,7 +118,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
# send email to customer
|
# send email to customer
|
||||||
send_email(
|
send_email(
|
||||||
settings.DEFAULT_FROM_EMAIL,
|
"noreply@yourdomain.com",
|
||||||
inv.customer.email,
|
inv.customer.email,
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
|
|||||||
@ -25,7 +25,7 @@ class Command(BaseCommand):
|
|||||||
self.deactivate_expired_plans()
|
self.deactivate_expired_plans()
|
||||||
|
|
||||||
# 3. Clean up old incomplete orders
|
# 3. Clean up old incomplete orders
|
||||||
# self.cleanup_old_orders()
|
self.cleanup_old_orders()
|
||||||
|
|
||||||
self.stdout.write("Maintenance completed!")
|
self.stdout.write("Maintenance completed!")
|
||||||
|
|
||||||
@ -58,19 +58,9 @@ class Command(BaseCommand):
|
|||||||
def deactivate_expired_plans(self):
|
def deactivate_expired_plans(self):
|
||||||
"""Deactivate plans that have expired (synchronous)"""
|
"""Deactivate plans that have expired (synchronous)"""
|
||||||
expired_plans = UserPlan.objects.filter(
|
expired_plans = UserPlan.objects.filter(
|
||||||
active=True, expire__lte=timezone.now() - timedelta(days=7)
|
active=True, expire__lt=timezone.now().date()
|
||||||
)
|
)
|
||||||
|
|
||||||
for plan in expired_plans:
|
|
||||||
# try:
|
|
||||||
if dealer := getattr(plan.user,"dealer", None):
|
|
||||||
dealer.user.is_active = False
|
|
||||||
dealer.user.save()
|
|
||||||
for staff in dealer.get_staff():
|
|
||||||
staff.deactivate_account()
|
|
||||||
count = expired_plans.update(active=False)
|
count = expired_plans.update(active=False)
|
||||||
# except:
|
|
||||||
# logger.warning(f"User {plan.user_id} does not exist")
|
|
||||||
self.stdout.write(f"Deactivated {count} expired plans")
|
self.stdout.write(f"Deactivated {count} expired plans")
|
||||||
|
|
||||||
def cleanup_old_orders(self):
|
def cleanup_old_orders(self):
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
# management/commands/update_site.py
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.contrib.sites.models import Site
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = 'Update the default site domain'
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
site = Site.objects.get_current()
|
|
||||||
site.domain = settings.SITE_DOMAIN
|
|
||||||
site.name = settings.SITE_NAME
|
|
||||||
site.save()
|
|
||||||
self.stdout.write(self.style.SUCCESS(f'Site updated to: {site.domain}'))
|
|
||||||
@ -8,7 +8,7 @@ from decimal import Decimal
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.validators import MinValueValidator,MaxValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
import hashlib
|
import hashlib
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@ -206,25 +206,12 @@ class UnitOfMeasure(models.TextChoices):
|
|||||||
|
|
||||||
class VatRate(models.Model):
|
class VatRate(models.Model):
|
||||||
dealer = models.ForeignKey("Dealer", on_delete=models.CASCADE)
|
dealer = models.ForeignKey("Dealer", on_delete=models.CASCADE)
|
||||||
rate = models.DecimalField(
|
rate = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.15"))
|
||||||
max_digits=5,
|
|
||||||
decimal_places=2,
|
|
||||||
default=Decimal("0.15"),
|
|
||||||
validators=[
|
|
||||||
MinValueValidator(0.0),
|
|
||||||
MaxValueValidator(1.0)
|
|
||||||
],
|
|
||||||
help_text=_("VAT rate as decimal between 0 and 1 (e.g., 0.2 for 20%)")
|
|
||||||
)
|
|
||||||
is_active = models.BooleanField(default=True)
|
is_active = models.BooleanField(default=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Rate: {self.rate * 100}%"
|
return f"Rate: {self.rate}%"
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.full_clean()
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class CarType(models.IntegerChoices):
|
class CarType(models.IntegerChoices):
|
||||||
@ -1378,7 +1365,7 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
options={"quality": 80},
|
options={"quality": 80},
|
||||||
)
|
)
|
||||||
entity = models.ForeignKey(
|
entity = models.ForeignKey(
|
||||||
EntityModel, on_delete=models.SET_NULL, null=True, blank=True,related_name="dealers"
|
EntityModel, on_delete=models.SET_NULL, null=True, blank=True
|
||||||
)
|
)
|
||||||
joined_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Joined At"))
|
joined_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Joined At"))
|
||||||
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
|
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At"))
|
||||||
@ -1410,13 +1397,6 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return None
|
return None
|
||||||
@property
|
|
||||||
def is_plan_expired(self):
|
|
||||||
try:
|
|
||||||
return UserPlan.objects.get(user=self.user, active=True).is_expired()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def customers(self):
|
def customers(self):
|
||||||
@ -1444,8 +1424,6 @@ class Dealer(models.Model, LocalizedNameMixin):
|
|||||||
|
|
||||||
def get_vendors(self):
|
def get_vendors(self):
|
||||||
return VendorModel.objects.filter(entity_model=self.entity)
|
return VendorModel.objects.filter(entity_model=self.entity)
|
||||||
def get_staff(self):
|
|
||||||
return Staff.objects.filter(dealer=self)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_staff_exceed_quota_limit(self):
|
def is_staff_exceed_quota_limit(self):
|
||||||
|
|||||||
@ -20,7 +20,6 @@ from django_ledger.models import (
|
|||||||
PurchaseOrderModel,
|
PurchaseOrderModel,
|
||||||
EstimateModel,
|
EstimateModel,
|
||||||
BillModel,
|
BillModel,
|
||||||
ChartOfAccountModel,
|
|
||||||
)
|
)
|
||||||
from . import models
|
from . import models
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
@ -72,12 +71,13 @@ User = get_user_model()
|
|||||||
# instance.save()
|
# instance.save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=models.Car)
|
# check with marwan
|
||||||
def create_dealers_make(sender, instance, created, **kwargs):
|
# @receiver(post_save, sender=models.Car)
|
||||||
if created:
|
# def create_dealers_make(sender, instance, created, **kwargs):
|
||||||
models.DealersMake.objects.get_or_create(
|
# if created:
|
||||||
dealer=instance.dealer, car_make=instance.id_car_make
|
# models.DealersMake.objects.get_or_create(
|
||||||
)
|
# dealer=instance.dealer, car_make=instance.id_car_make
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=models.Car)
|
@receiver(post_save, sender=models.Car)
|
||||||
@ -1425,20 +1425,3 @@ def handle_user_registration(sender, instance, created, **kwargs):
|
|||||||
|
|
||||||
شكرا لاختيارك لنا.
|
شكرا لاختيارك لنا.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=ChartOfAccountModel)
|
|
||||||
def handle_chart_of_account(sender, instance, created, **kwargs):
|
|
||||||
if created:
|
|
||||||
try:
|
|
||||||
dealer = instance.entity.dealers.first()
|
|
||||||
async_task(
|
|
||||||
func="inventory.tasks.create_coa_accounts",
|
|
||||||
dealer_id=dealer.pk, # Pass ID instead of object
|
|
||||||
coa_slug=instance.slug,
|
|
||||||
hook="inventory.hooks.check_create_coa_accounts",
|
|
||||||
ack_failure=True, # Ensure task failures are acknowledged
|
|
||||||
sync=False # Explicitly set to async
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error handling chart of account: {e}")
|
|
||||||
@ -72,32 +72,23 @@ def create_coa_accounts(dealer_id, **kwargs):
|
|||||||
|
|
||||||
max_retries = 3
|
max_retries = 3
|
||||||
retry_delay = 2 # seconds
|
retry_delay = 2 # seconds
|
||||||
coa_slug = kwargs.get('coa_slug', None)
|
|
||||||
logger.info(f"chart of account model slug {coa_slug}")
|
|
||||||
logger.info(f"Attempting to create accounts for dealer {dealer_id}")
|
|
||||||
for attempt in range(max_retries):
|
for attempt in range(max_retries):
|
||||||
try:
|
try:
|
||||||
logger.info(f"Attempt {attempt + 1} to create accounts for dealer {dealer_id}")
|
logger.info(f"Attempt {attempt + 1} to create accounts for dealer {dealer_id}")
|
||||||
|
|
||||||
instance = Dealer.objects.get(pk=dealer_id)
|
# Get fresh instance from database
|
||||||
|
instance = Dealer.objects.select_related('entity').get(id=dealer_id)
|
||||||
entity = instance.entity
|
entity = instance.entity
|
||||||
|
|
||||||
if not entity:
|
if not entity:
|
||||||
logger.error(f"No entity found for dealer {dealer_id}")
|
logger.error(f"No entity found for dealer {dealer_id}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if coa_slug:
|
coa = entity.get_default_coa()
|
||||||
try:
|
|
||||||
coa = entity.get_coa_model_qs().get(slug=coa_slug)
|
|
||||||
logger.info(f"COA with slug {coa_slug} found for entity {entity.pk}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"COA with slug {coa_slug} not found for entity {entity.pk}: {e}")
|
|
||||||
else:
|
|
||||||
coa = entity.get_default_coa()
|
|
||||||
logger.info(f"Default COA found for entity {entity.pk}")
|
|
||||||
|
|
||||||
if not coa:
|
if not coa:
|
||||||
logger.error(f"No COA found for entity {entity.pk}")
|
logger.error(f"No COA found for entity {entity.id}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info("Creating default accounts")
|
logger.info("Creating default accounts")
|
||||||
@ -121,11 +112,11 @@ def create_coa_accounts(dealer_id, **kwargs):
|
|||||||
else:
|
else:
|
||||||
logger.error(f"All {max_retries} attempts failed for dealer {dealer_id}")
|
logger.error(f"All {max_retries} attempts failed for dealer {dealer_id}")
|
||||||
# Schedule a cleanup or notification task
|
# Schedule a cleanup or notification task
|
||||||
# async_task(
|
async_task(
|
||||||
# "inventory.tasks.handle_account_creation_failure",
|
"inventory.tasks.handle_account_creation_failure",
|
||||||
# dealer_id=dealer_id,
|
dealer_id=dealer_id,
|
||||||
# error=str(e)
|
error=str(e)
|
||||||
# )
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def retry_entity_creation(dealer_id, retry_count=0):
|
def retry_entity_creation(dealer_id, retry_count=0):
|
||||||
|
|||||||
@ -25,7 +25,6 @@ from django_ledger.models import (
|
|||||||
InvoiceModel,
|
InvoiceModel,
|
||||||
BillModel,
|
BillModel,
|
||||||
VendorModel,
|
VendorModel,
|
||||||
AccountModel
|
|
||||||
)
|
)
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django_ledger.models.items import ItemModel
|
from django_ledger.models.items import ItemModel
|
||||||
@ -2392,17 +2391,15 @@ def create_account(entity, coa, account_data):
|
|||||||
Create account with proper validation and error handling
|
Create account with proper validation and error handling
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# existing_account = AccountModel.objects.filter(coa_model=coa,code=account_data["code"])
|
# Check if account already exists
|
||||||
existing_account = entity.get_all_accounts().filter(
|
existing_account = entity.get_all_accounts().filter(
|
||||||
coa_model=coa,
|
|
||||||
code=account_data["code"]
|
code=account_data["code"]
|
||||||
)
|
).first()
|
||||||
|
|
||||||
if existing_account:
|
if existing_account:
|
||||||
logger.info(f"Account already exists: {account_data['code']}")
|
logger.info(f"Account already exists: {account_data['code']}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
logger.info(f"Creating account: {account_data['code']}")
|
|
||||||
account = entity.create_account(
|
account = entity.create_account(
|
||||||
coa_model=coa,
|
coa_model=coa,
|
||||||
code=account_data["code"],
|
code=account_data["code"],
|
||||||
@ -2411,7 +2408,6 @@ def create_account(entity, coa, account_data):
|
|||||||
balance_type=_(account_data["balance_type"]),
|
balance_type=_(account_data["balance_type"]),
|
||||||
active=True,
|
active=True,
|
||||||
)
|
)
|
||||||
logger.info(f"Successfully created account: {account_data['code']}")
|
|
||||||
|
|
||||||
if account:
|
if account:
|
||||||
account.role_default = account_data["default"]
|
account.role_default = account_data["default"]
|
||||||
|
|||||||
@ -2338,17 +2338,8 @@ class DealerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
|||||||
|
|
||||||
def dealer_vat_rate_update(request, slug):
|
def dealer_vat_rate_update(request, slug):
|
||||||
dealer = get_object_or_404(models.Dealer, slug=slug)
|
dealer = get_object_or_404(models.Dealer, slug=slug)
|
||||||
vat_rate, created = models.VatRate.objects.get_or_create(dealer=dealer)
|
models.VatRate.objects.filter(dealer=dealer).update(rate=request.POST.get("rate"))
|
||||||
|
messages.success(request, _("VAT rate updated successfully"))
|
||||||
form = forms.VatRateForm(request.POST, instance=vat_rate)
|
|
||||||
|
|
||||||
if form.is_valid():
|
|
||||||
form.save()
|
|
||||||
messages.success(request, _("VAT rate updated successfully"))
|
|
||||||
else:
|
|
||||||
logger.error(form.errors)
|
|
||||||
messages.error(request, _("VAT rate update failed: %s") % form.errors.as_text())
|
|
||||||
|
|
||||||
return redirect("dealer_detail", slug=slug)
|
return redirect("dealer_detail", slug=slug)
|
||||||
|
|
||||||
|
|
||||||
@ -3638,17 +3629,17 @@ class UserCreateView(
|
|||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
staff = form.save(commit=False)
|
staff = form.save(commit=False)
|
||||||
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
# if dealer.is_staff_exceed_quota_limit:
|
if dealer.is_staff_exceed_quota_limit:
|
||||||
# messages.error(
|
messages.error(
|
||||||
# self.request,
|
self.request,
|
||||||
# _(
|
_(
|
||||||
# "You have reached the maximum number of staff users allowed for your plan"
|
"You have reached the maximum number of staff users allowed for your plan"
|
||||||
# ),
|
),
|
||||||
# )
|
)
|
||||||
# return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
email = form.cleaned_data["email"]
|
email = form.cleaned_data["email"]
|
||||||
if models.Staff.objects.filter(dealer=dealer,user__email=email).exists() or models.Dealer.objects.filter(user__email=email).exists():
|
if models.Staff.objects.filter(user__email=email).exists():
|
||||||
messages.error(
|
messages.error(
|
||||||
self.request,
|
self.request,
|
||||||
_(
|
_(
|
||||||
@ -4199,7 +4190,7 @@ class BankAccountCreateView(
|
|||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
form = super().get_form(form_class)
|
form = super().get_form(form_class)
|
||||||
account_qs = dealer.entity.get_default_coa_accounts().filter(
|
account_qs = dealer.entity.get_all_accounts().filter(
|
||||||
role__in=[
|
role__in=[
|
||||||
roles.ASSET_CA_CASH,
|
roles.ASSET_CA_CASH,
|
||||||
roles.LIABILITY_CL_ACC_PAYABLE,
|
roles.LIABILITY_CL_ACC_PAYABLE,
|
||||||
@ -4294,7 +4285,7 @@ class BankAccountUpdateView(
|
|||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
|
||||||
form = super().get_form(form_class)
|
form = super().get_form(form_class)
|
||||||
account_qs = dealer.entity.get_default_coa_accounts().filter(
|
account_qs = dealer.entity.get_all_accounts().filter(
|
||||||
role__in=[
|
role__in=[
|
||||||
roles.ASSET_CA_CASH,
|
roles.ASSET_CA_CASH,
|
||||||
roles.LIABILITY_CL_ACC_PAYABLE,
|
roles.LIABILITY_CL_ACC_PAYABLE,
|
||||||
@ -5924,9 +5915,6 @@ def PaymentCreateView(request, dealer_slug, pk):
|
|||||||
|
|
||||||
if not model.is_approved():
|
if not model.is_approved():
|
||||||
model.mark_as_approved(user_model=entity.admin)
|
model.mark_as_approved(user_model=entity.admin)
|
||||||
if amount < invoice.amount_due:
|
|
||||||
messages.error(request, _("Amount cannot be less than due amount"))
|
|
||||||
return response
|
|
||||||
if model.amount_paid == model.amount_due:
|
if model.amount_paid == model.amount_due:
|
||||||
messages.error(request, _("fully paid"))
|
messages.error(request, _("fully paid"))
|
||||||
return response
|
return response
|
||||||
@ -6790,10 +6778,8 @@ def delete_note(request, dealer_slug, pk):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
note = get_object_or_404(models.Notes, pk=pk, created_by=request.user)
|
note = get_object_or_404(models.Notes, pk=pk, created_by=request.user)
|
||||||
if isinstance(note.content_object, models.Customer):
|
print(note)
|
||||||
url = "customer_detail"
|
if isinstance(note.content_object, models.Lead):
|
||||||
slug = note.content_object.slug
|
|
||||||
elif isinstance(note.content_object, models.Lead):
|
|
||||||
url = "lead_detail"
|
url = "lead_detail"
|
||||||
slug = note.content_object.slug
|
slug = note.content_object.slug
|
||||||
if hasattr(note.content_object, "opportunity"):
|
if hasattr(note.content_object, "opportunity"):
|
||||||
@ -9813,7 +9799,7 @@ def ledger_unpost_all_journals(request, dealer_slug, entity_slug, pk):
|
|||||||
def pricing_page(request, dealer_slug):
|
def pricing_page(request, dealer_slug):
|
||||||
dealer=get_object_or_404(models.Dealer, slug=dealer_slug)
|
dealer=get_object_or_404(models.Dealer, slug=dealer_slug)
|
||||||
vat = models.VatRate.objects.filter(dealer=dealer).first()
|
vat = models.VatRate.objects.filter(dealer=dealer).first()
|
||||||
if not hasattr(dealer.user,'userplan') or dealer.is_plan_expired:
|
if not dealer.active_plan:
|
||||||
plan_list = PlanPricing.objects.annotate(
|
plan_list = PlanPricing.objects.annotate(
|
||||||
price_with_tax=Round(F('price') * vat.rate + F('price'), 2)
|
price_with_tax=Round(F('price') * vat.rate + F('price'), 2)
|
||||||
).all()
|
).all()
|
||||||
@ -9890,7 +9876,7 @@ def payment_callback(request, dealer_slug):
|
|||||||
UserPlan.objects.create(
|
UserPlan.objects.create(
|
||||||
user=order.user,
|
user=order.user,
|
||||||
plan=order.plan,
|
plan=order.plan,
|
||||||
# expire=datetime.now().date() + timedelta(days=order.get_plan_pricing().pricing.period)
|
expire=datetime.now().date() + timedelta(days=order.get_plan_pricing().pricing.period)
|
||||||
)
|
)
|
||||||
logger.info(f"Created new UserPlan for user {order.user} with plan {order.plan}.")
|
logger.info(f"Created new UserPlan for user {order.user} with plan {order.plan}.")
|
||||||
else:
|
else:
|
||||||
@ -9926,16 +9912,7 @@ def payment_callback(request, dealer_slug):
|
|||||||
history.status = "failed"
|
history.status = "failed"
|
||||||
history.save()
|
history.save()
|
||||||
return render(request, "payment_failed.html", {"message": "Plan activation error"})
|
return render(request, "payment_failed.html", {"message": "Plan activation error"})
|
||||||
finally:
|
|
||||||
if dealer := getattr(order.user,"dealer", None):
|
|
||||||
if not dealer.user.is_active:
|
|
||||||
dealer.user.is_active = True
|
|
||||||
dealer.user.save()
|
|
||||||
for staff in dealer.get_staff():
|
|
||||||
if not staff.user.is_active:
|
|
||||||
staff.activate_account()
|
|
||||||
|
|
||||||
logger.info(f"Order {order.id} for user {order.user} completed successfully. Payment history updated.")
|
|
||||||
elif payment_status == "failed":
|
elif payment_status == "failed":
|
||||||
logger.warning(f"Payment failed for transaction ID {payment_id}. Message: {message}")
|
logger.warning(f"Payment failed for transaction ID {payment_id}. Message: {message}")
|
||||||
history.status = "failed"
|
history.status = "failed"
|
||||||
@ -10630,9 +10607,7 @@ def InventoryItemCreateView(request, dealer_slug):
|
|||||||
messages.error(request, _("Inventory item already exists"))
|
messages.error(request, _("Inventory item already exists"))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
uom = entity.get_uom_all().filter(name="Unit").first()
|
uom = entity.get_uom_all().get(name="Unit")
|
||||||
if not uom:
|
|
||||||
uom = entity.create_uom(name="Unit", unit_abbr="unit")
|
|
||||||
entity.create_item_inventory(
|
entity.create_item_inventory(
|
||||||
name=inventory_name,
|
name=inventory_name,
|
||||||
uom_model=uom,
|
uom_model=uom,
|
||||||
@ -11488,12 +11463,15 @@ def staff_password_reset_view(request, dealer_slug, user_pk):
|
|||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = forms.CustomSetPasswordForm(staff.user, request.POST)
|
form = forms.CustomSetPasswordForm(staff.user, request.POST)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
print(form.cleaned_data['new_password1'])
|
||||||
|
print(form.cleaned_data['new_password2'])
|
||||||
form.save()
|
form.save()
|
||||||
messages.success(request, _('Your password has been set. You may go ahead and log in now.'))
|
messages.success(request, _('Your password has been set. You may go ahead and log in now.'))
|
||||||
return redirect('user_detail',dealer_slug=dealer_slug,slug=staff.slug)
|
return redirect('user_detail',dealer_slug=dealer_slug,slug=staff.slug)
|
||||||
else:
|
else:
|
||||||
messages.error(request, _(f'Invalid password. {str(form.errors)}'))
|
messages.error(request, _('Invalid password. Please try again.'))
|
||||||
form = forms.CustomSetPasswordForm(staff.user)
|
form = forms.CustomSetPasswordForm(staff.user)
|
||||||
return render(request, 'users/user_password_reset.html', {'form': form})
|
return render(request, 'users/user_password_reset.html', {'form': form})
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,4 @@ python3 manage.py tenhal_plan
|
|||||||
|
|
||||||
python3 manage.py set_custom_permissions
|
python3 manage.py set_custom_permissions
|
||||||
|
|
||||||
echo "Updating site domain"
|
|
||||||
python3 manage.py update_site
|
|
||||||
|
|
||||||
echo "Done"
|
echo "Done"
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -1,162 +0,0 @@
|
|||||||
annotated-types==0.7.0
|
|
||||||
anyio==4.9.0
|
|
||||||
arrow==1.3.0
|
|
||||||
asgiref==3.9.1
|
|
||||||
attrs==25.3.0
|
|
||||||
autobahn==24.4.2
|
|
||||||
Automat==25.4.16
|
|
||||||
Babel==2.15.0
|
|
||||||
beautifulsoup4==4.13.4
|
|
||||||
blessed==1.21.0
|
|
||||||
cattrs==25.1.1
|
|
||||||
certifi==2025.7.9
|
|
||||||
cffi==1.17.1
|
|
||||||
channels==4.2.2
|
|
||||||
charset-normalizer==3.4.2
|
|
||||||
click==8.2.1
|
|
||||||
colorama==0.4.6
|
|
||||||
constantly==23.10.4
|
|
||||||
crispy-bootstrap5==2025.6
|
|
||||||
cryptography==45.0.5
|
|
||||||
cssbeautifier==1.15.4
|
|
||||||
daphne==4.2.1
|
|
||||||
defusedxml==0.7.1
|
|
||||||
diff-match-patch==20241021
|
|
||||||
distro==1.9.0
|
|
||||||
Django==5.2.4
|
|
||||||
django-allauth==65.10.0
|
|
||||||
django-appconf==1.1.0
|
|
||||||
django-appointment==3.8.0
|
|
||||||
django-background-tasks==1.2.8
|
|
||||||
django-bootstrap5==25.1
|
|
||||||
django-ckeditor==6.7.3
|
|
||||||
django-cors-headers==4.7.0
|
|
||||||
django-countries==7.6.1
|
|
||||||
django-crispy-forms==2.4
|
|
||||||
django-debug-toolbar==5.2.0
|
|
||||||
django-easy-audit==1.3.7
|
|
||||||
django-encrypted-model-fields==0.6.5
|
|
||||||
django-extensions==4.1
|
|
||||||
django-filter==25.1
|
|
||||||
django-imagekit==5.0.0
|
|
||||||
django-import-export==4.3.8
|
|
||||||
django-js-asset==3.1.2
|
|
||||||
django-ledger==0.7.11
|
|
||||||
django-manager-utils==3.1.5
|
|
||||||
django-next-url-mixin==0.4.0
|
|
||||||
django-ordered-model==3.7.4
|
|
||||||
django-phonenumber-field==8.0.0
|
|
||||||
django-picklefield==3.3
|
|
||||||
django-plans==2.0.0
|
|
||||||
django-prometheus==2.4.1
|
|
||||||
django-q2==1.8.0
|
|
||||||
django-query-builder==3.2.0
|
|
||||||
django-schema-graph==3.1.0
|
|
||||||
django-sequences==3.0
|
|
||||||
django-tables2==2.7.5
|
|
||||||
django-treebeard==4.7.1
|
|
||||||
django-widget-tweaks==1.5.0
|
|
||||||
djangorestframework==3.16.0
|
|
||||||
djhtml==3.0.8
|
|
||||||
djlint==1.36.4
|
|
||||||
dnspython==2.7.0
|
|
||||||
docopt==0.6.2
|
|
||||||
EditorConfig==0.17.1
|
|
||||||
Faker==37.4.0
|
|
||||||
fleming==0.7.0
|
|
||||||
fonttools==4.58.5
|
|
||||||
fpdf==1.7.2
|
|
||||||
fpdf2==2.8.3
|
|
||||||
greenlet==3.2.3
|
|
||||||
gunicorn==23.0.0
|
|
||||||
h11==0.16.0
|
|
||||||
h2==4.2.0
|
|
||||||
hpack==4.1.0
|
|
||||||
httpcore==1.0.9
|
|
||||||
httpx==0.28.1
|
|
||||||
hyperframe==6.1.0
|
|
||||||
hyperlink==21.0.0
|
|
||||||
icalendar==6.3.1
|
|
||||||
idna==3.10
|
|
||||||
incremental==24.7.2
|
|
||||||
iron-core==1.2.1
|
|
||||||
iron-mq==0.9
|
|
||||||
jiter==0.10.0
|
|
||||||
jsbeautifier==1.15.4
|
|
||||||
json5==0.12.0
|
|
||||||
jsonpatch==1.33
|
|
||||||
jsonpointer==3.0.0
|
|
||||||
jwt==1.4.0
|
|
||||||
langchain==0.3.26
|
|
||||||
langchain-core==0.3.68
|
|
||||||
langchain-ollama==0.3.4
|
|
||||||
langchain-text-splitters==0.3.8
|
|
||||||
langsmith==0.4.4
|
|
||||||
luhnchecker==0.0.12
|
|
||||||
Markdown==3.8.2
|
|
||||||
markdown-it-py==3.0.0
|
|
||||||
mdurl==0.1.2
|
|
||||||
num2words==0.5.14
|
|
||||||
numpy==2.3.1
|
|
||||||
ofxtools==0.9.5
|
|
||||||
ollama==0.5.1
|
|
||||||
openai==1.93.3
|
|
||||||
opencv-python==4.11.0.86
|
|
||||||
orjson==3.10.18
|
|
||||||
packaging==24.2
|
|
||||||
pandas==2.3.1
|
|
||||||
pathspec==0.12.1
|
|
||||||
phonenumbers==8.13.42
|
|
||||||
pilkit==3.0
|
|
||||||
pillow==10.4.0
|
|
||||||
priority==1.3.0
|
|
||||||
prometheus_client==0.22.1
|
|
||||||
psycopg2-binary==2.9.10
|
|
||||||
pyasn1==0.6.1
|
|
||||||
pyasn1_modules==0.4.2
|
|
||||||
pycparser==2.22
|
|
||||||
pydantic==2.11.7
|
|
||||||
pydantic_core==2.33.2
|
|
||||||
Pygments==2.19.2
|
|
||||||
pymongo==4.14.1
|
|
||||||
pyOpenSSL==25.1.0
|
|
||||||
python-dateutil==2.9.0.post0
|
|
||||||
python-dotenv==1.1.1
|
|
||||||
python-slugify==8.0.4
|
|
||||||
python-stdnum==2.1
|
|
||||||
pytz==2025.2
|
|
||||||
pyvin==0.0.2
|
|
||||||
PyYAML==6.0.2
|
|
||||||
pyzbar==0.1.9
|
|
||||||
redis==6.2.0
|
|
||||||
regex==2024.11.6
|
|
||||||
requests==2.32.4
|
|
||||||
requests-toolbelt==1.0.0
|
|
||||||
rich==14.0.0
|
|
||||||
ruff==0.12.2
|
|
||||||
service-identity==24.2.0
|
|
||||||
setuptools==80.9.0
|
|
||||||
six==1.17.0
|
|
||||||
sniffio==1.3.1
|
|
||||||
soupsieve==2.7
|
|
||||||
SQLAlchemy==2.0.41
|
|
||||||
sqlparse==0.5.3
|
|
||||||
suds==1.2.0
|
|
||||||
swapper==1.3.0
|
|
||||||
tablib==3.8.0
|
|
||||||
tenacity==9.1.2
|
|
||||||
text-unidecode==1.3
|
|
||||||
tqdm==4.67.1
|
|
||||||
Twisted==25.5.0
|
|
||||||
txaio==25.6.1
|
|
||||||
types-python-dateutil==2.9.0.20250708
|
|
||||||
typing-inspection==0.4.1
|
|
||||||
typing_extensions==4.14.1
|
|
||||||
tzdata==2025.2
|
|
||||||
urllib3==2.5.0
|
|
||||||
uvicorn==0.35.0
|
|
||||||
uvicorn-worker==0.3.0
|
|
||||||
wcwidth==0.2.13
|
|
||||||
whitenoise==6.9.0
|
|
||||||
zope.interface==7.2
|
|
||||||
zstandard==0.23.0
|
|
||||||
260
requirements.txt
260
requirements.txt
@ -1,374 +1,162 @@
|
|||||||
aiofiles==24.1.0
|
|
||||||
aiohappyeyeballs==2.6.1
|
|
||||||
aiohttp==3.11.14
|
|
||||||
aiohttp-retry==2.9.1
|
|
||||||
aiosignal==1.3.2
|
|
||||||
alabaster==1.0.0
|
|
||||||
albucore==0.0.23
|
|
||||||
albumentations==2.0.5
|
|
||||||
annotated-types==0.7.0
|
annotated-types==0.7.0
|
||||||
anyio==4.9.0
|
anyio==4.9.0
|
||||||
arabic-reshaper==3.0.0
|
|
||||||
arrow==1.3.0
|
arrow==1.3.0
|
||||||
asgiref==3.9.1
|
asgiref==3.9.1
|
||||||
astor==0.8.1
|
|
||||||
astroid==3.3.9
|
|
||||||
attrs==25.3.0
|
attrs==25.3.0
|
||||||
autopep8==2.3.2
|
autobahn==24.4.2
|
||||||
|
Automat==25.4.16
|
||||||
Babel==2.15.0
|
Babel==2.15.0
|
||||||
beautifulsoup4==4.13.4
|
beautifulsoup4==4.13.4
|
||||||
bidict==0.23.1
|
|
||||||
binaryornot==0.4.4
|
|
||||||
bleach==6.2.0
|
|
||||||
blessed==1.21.0
|
blessed==1.21.0
|
||||||
blinker==1.9.0
|
|
||||||
Brotli==1.1.0
|
|
||||||
cattrs==25.1.1
|
cattrs==25.1.1
|
||||||
certifi==2025.7.9
|
certifi==2025.7.9
|
||||||
cffi==1.17.1
|
cffi==1.17.1
|
||||||
chardet==5.2.0
|
channels==4.2.2
|
||||||
charset-normalizer==3.4.2
|
charset-normalizer==3.4.2
|
||||||
click==8.2.1
|
click==8.2.1
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
commonmark==0.9.1
|
constantly==23.10.4
|
||||||
contourpy==1.3.1
|
|
||||||
cookiecutter==2.6.0
|
|
||||||
crispy-bootstrap5==2025.6
|
crispy-bootstrap5==2025.6
|
||||||
cryptography==45.0.5
|
cryptography==45.0.5
|
||||||
cssbeautifier==1.15.4
|
cssbeautifier==1.15.4
|
||||||
cssselect2==0.8.0
|
daphne==4.2.1
|
||||||
cycler==0.12.1
|
|
||||||
Cython==3.0.12
|
|
||||||
datastar-py==0.6.2
|
|
||||||
decorator==5.2.1
|
|
||||||
defusedxml==0.7.1
|
defusedxml==0.7.1
|
||||||
desert==2020.11.18
|
|
||||||
diff-match-patch==20241021
|
diff-match-patch==20241021
|
||||||
dill==0.3.9
|
|
||||||
distlib==0.3.9
|
|
||||||
distro==1.9.0
|
distro==1.9.0
|
||||||
dj-rest-auth==7.0.1
|
|
||||||
dj-shop-cart==7.1.1
|
|
||||||
Django==5.2.4
|
Django==5.2.4
|
||||||
django-admin-sortable2==1.0.4
|
|
||||||
django-allauth==65.10.0
|
django-allauth==65.10.0
|
||||||
django-angular==2.3.1
|
|
||||||
django-appconf==1.1.0
|
django-appconf==1.1.0
|
||||||
django-appointment==3.8.0
|
django-appointment==3.8.0
|
||||||
django-background-tasks==1.2.8
|
django-background-tasks==1.2.8
|
||||||
django-bootstrap5==25.1
|
django-bootstrap5==25.1
|
||||||
django-ckeditor==6.7.3
|
django-ckeditor==6.7.3
|
||||||
django-classy-tags==3.0.1
|
|
||||||
django-cms==3.11.3
|
|
||||||
django-cors-headers==4.7.0
|
django-cors-headers==4.7.0
|
||||||
django-countries==7.6.1
|
django-countries==7.6.1
|
||||||
django-crispy-forms==2.4
|
django-crispy-forms==2.4
|
||||||
django-debug-toolbar==5.2.0
|
django-debug-toolbar==5.2.0
|
||||||
django-easy-audit==1.3.7
|
django-easy-audit==1.3.7
|
||||||
django-encrypted-model-fields==0.6.5
|
django-encrypted-model-fields==0.6.5
|
||||||
django-entangled==0.6.2
|
|
||||||
django-extensions==4.1
|
django-extensions==4.1
|
||||||
django-extra-views==0.14.0
|
|
||||||
django-filer==3.0.3
|
|
||||||
django-filter==25.1
|
django-filter==25.1
|
||||||
django-formtools==2.4
|
|
||||||
django-fsm==3.0.0
|
|
||||||
django-fsm-admin==1.2.5
|
|
||||||
django-haystack==3.3.0
|
|
||||||
django-imagekit==5.0.0
|
django-imagekit==5.0.0
|
||||||
django-import-export==4.3.8
|
django-import-export==4.3.8
|
||||||
django-ipware==7.0.1
|
|
||||||
django-js-asset==3.1.2
|
django-js-asset==3.1.2
|
||||||
django-ledger==0.7.6.1
|
django-ledger==0.7.6.1
|
||||||
django-manager-utils==3.1.5
|
django-manager-utils==3.1.5
|
||||||
django-model-utils==5.0.0
|
|
||||||
django-money==3.5.3
|
|
||||||
django-next-url-mixin==0.4.0
|
django-next-url-mixin==0.4.0
|
||||||
django-nine==0.2.7
|
|
||||||
django-nonefield==0.4
|
|
||||||
django-ordered-model==3.7.4
|
django-ordered-model==3.7.4
|
||||||
django-oscar==3.2.5
|
|
||||||
django-pdf-actions==0.1.44
|
|
||||||
django-phonenumber-field==8.0.0
|
django-phonenumber-field==8.0.0
|
||||||
django-picklefield==3.3
|
django-picklefield==3.3
|
||||||
django-plans==2.0.0
|
django-plans==2.0.0
|
||||||
django-polymorphic==3.1.0
|
django-prometheus==2.4.1
|
||||||
django-post-office==3.6.3
|
|
||||||
django-prometheus==2.3.1
|
|
||||||
django-q2==1.8.0
|
django-q2==1.8.0
|
||||||
django-query-builder==3.2.0
|
django-query-builder==3.2.0
|
||||||
django-rest-auth==0.9.5
|
|
||||||
django-schema-graph==3.1.0
|
django-schema-graph==3.1.0
|
||||||
django-sekizai==3.0.1
|
|
||||||
django-select2==7.10.0
|
|
||||||
django-sequences==3.0
|
django-sequences==3.0
|
||||||
django-shop==1.2.4
|
|
||||||
django-silk==5.3.2
|
|
||||||
django-sms==0.7.0
|
|
||||||
django-sslserver==0.22
|
|
||||||
django-tables2==2.7.5
|
django-tables2==2.7.5
|
||||||
django-tailwind==4.0.1
|
|
||||||
django-treebeard==4.7.1
|
django-treebeard==4.7.1
|
||||||
django-view-breadcrumbs==2.5.1
|
|
||||||
django-viewflow==2.2.12
|
|
||||||
django-widget-tweaks==1.5.0
|
django-widget-tweaks==1.5.0
|
||||||
djangocms-admin-style==3.3.1
|
|
||||||
djangocms-cascade==1.3.7
|
|
||||||
djangocms-text-ckeditor==5.1.7
|
|
||||||
djangorestframework==3.16.0
|
djangorestframework==3.16.0
|
||||||
djangorestframework_simplejwt==5.5.0
|
|
||||||
djangoviz==0.1.1
|
|
||||||
djhtml==3.0.8
|
djhtml==3.0.8
|
||||||
djlint==1.36.4
|
djlint==1.36.4
|
||||||
|
dnspython==2.7.0
|
||||||
docopt==0.6.2
|
docopt==0.6.2
|
||||||
docutils==0.21.2
|
|
||||||
easy-thumbnails==2.9
|
|
||||||
ecdsa==0.19.1
|
|
||||||
EditorConfig==0.17.1
|
EditorConfig==0.17.1
|
||||||
emoji==2.14.1
|
|
||||||
et_xmlfile==2.0.0
|
|
||||||
factory-boy==3.2.1
|
|
||||||
Faker==37.4.0
|
Faker==37.4.0
|
||||||
fastapi==0.115.12
|
|
||||||
filelock==3.18.0
|
|
||||||
fire==0.7.0
|
|
||||||
fleming==0.7.0
|
fleming==0.7.0
|
||||||
fonttools==4.58.5
|
fonttools==4.58.5
|
||||||
fpdf==1.7.2
|
fpdf==1.7.2
|
||||||
fpdf2==2.8.3
|
fpdf2==2.8.3
|
||||||
frozenlist==1.5.0
|
|
||||||
fsspec==2025.3.0
|
|
||||||
gprof2dot==2024.6.6
|
|
||||||
graphqlclient==0.2.4
|
|
||||||
greenlet==3.2.3
|
greenlet==3.2.3
|
||||||
|
gunicorn==23.0.0
|
||||||
h11==0.16.0
|
h11==0.16.0
|
||||||
h2==4.2.0
|
h2==4.2.0
|
||||||
hpack==4.1.0
|
hpack==4.1.0
|
||||||
hstspreload==2025.1.1
|
|
||||||
html5lib==1.1
|
|
||||||
htmx==0.0.0
|
|
||||||
httpcore==1.0.9
|
httpcore==1.0.9
|
||||||
httptools==0.6.4
|
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
httpx-ws==0.7.2
|
|
||||||
hyperframe==6.1.0
|
hyperframe==6.1.0
|
||||||
|
hyperlink==21.0.0
|
||||||
icalendar==6.3.1
|
icalendar==6.3.1
|
||||||
idna==3.10
|
idna==3.10
|
||||||
ifaddr==0.2.0
|
incremental==24.7.2
|
||||||
imageio==2.37.0
|
iron-core==1.2.1
|
||||||
imagesize==1.4.1
|
iron-mq==0.9
|
||||||
imgaug==0.4.0
|
|
||||||
iso4217==1.12.20240625
|
|
||||||
isodate==0.7.2
|
|
||||||
isort==6.0.1
|
|
||||||
itsdangerous==2.2.0
|
|
||||||
Jinja2==3.1.6
|
|
||||||
jiter==0.10.0
|
jiter==0.10.0
|
||||||
joblib==1.4.2
|
|
||||||
jsbeautifier==1.15.4
|
jsbeautifier==1.15.4
|
||||||
json5==0.12.0
|
json5==0.12.0
|
||||||
jsonfield==3.1.0
|
|
||||||
jsonpatch==1.33
|
jsonpatch==1.33
|
||||||
jsonpointer==3.0.0
|
jsonpointer==3.0.0
|
||||||
jwt==1.4.0
|
jwt==1.4.0
|
||||||
kiwisolver==1.4.8
|
|
||||||
langchain==0.3.26
|
langchain==0.3.26
|
||||||
langchain-core==0.3.68
|
langchain-core==0.3.68
|
||||||
langchain-ollama==0.3.4
|
langchain-ollama==0.3.4
|
||||||
langchain-text-splitters==0.3.8
|
langchain-text-splitters==0.3.8
|
||||||
langsmith==0.4.4
|
langsmith==0.4.4
|
||||||
lazy_loader==0.4
|
|
||||||
ledger==1.0.1
|
|
||||||
libretranslatepy==2.1.4
|
|
||||||
lmdb==1.6.2
|
|
||||||
lmstudio==1.4.1
|
|
||||||
luhnchecker==0.0.12
|
luhnchecker==0.0.12
|
||||||
lxml==5.3.1
|
|
||||||
Markdown==3.8.2
|
Markdown==3.8.2
|
||||||
markdown-it-py==3.0.0
|
markdown-it-py==3.0.0
|
||||||
markdown2==2.5.3
|
|
||||||
MarkupSafe==3.0.2
|
|
||||||
marshmallow==3.26.1
|
|
||||||
matplotlib==3.10.1
|
|
||||||
mccabe==0.7.0
|
|
||||||
mdurl==0.1.2
|
mdurl==0.1.2
|
||||||
MouseInfo==0.1.3
|
|
||||||
mpmath==1.3.0
|
|
||||||
msgspec==0.19.0
|
|
||||||
multidict==6.2.0
|
|
||||||
mypy-extensions==1.0.0
|
|
||||||
networkx==3.4.2
|
|
||||||
newrelic==10.7.0
|
|
||||||
nicegui==2.13.0
|
|
||||||
nltk==3.9.1
|
|
||||||
num2words==0.5.14
|
num2words==0.5.14
|
||||||
numpy==2.3.1
|
numpy==2.3.1
|
||||||
nvidia-cublas-cu12==12.4.5.8
|
|
||||||
nvidia-cuda-cupti-cu12==12.4.127
|
|
||||||
nvidia-cuda-nvrtc-cu12==12.4.127
|
|
||||||
nvidia-cuda-runtime-cu12==12.4.127
|
|
||||||
nvidia-cudnn-cu12==9.1.0.70
|
|
||||||
nvidia-cufft-cu12==11.2.1.3
|
|
||||||
nvidia-curand-cu12==10.3.5.147
|
|
||||||
nvidia-cusolver-cu12==11.6.1.9
|
|
||||||
nvidia-cusparse-cu12==12.3.1.170
|
|
||||||
nvidia-cusparselt-cu12==0.6.2
|
|
||||||
nvidia-nccl-cu12==2.21.5
|
|
||||||
nvidia-nvjitlink-cu12==12.4.127
|
|
||||||
nvidia-nvtx-cu12==12.4.127
|
|
||||||
oauthlib==3.2.2
|
|
||||||
ofxtools==0.9.5
|
ofxtools==0.9.5
|
||||||
ollama==0.5.1
|
ollama==0.5.1
|
||||||
openai==1.93.3
|
openai==1.93.3
|
||||||
opencv-contrib-python==4.11.0.86
|
|
||||||
opencv-python==4.11.0.86
|
opencv-python==4.11.0.86
|
||||||
opencv-python-headless==4.11.0.86
|
|
||||||
openpyxl==3.1.5
|
|
||||||
opt_einsum==3.4.0
|
|
||||||
orjson==3.10.18
|
orjson==3.10.18
|
||||||
outcome==1.3.0.post0
|
|
||||||
packaging==24.2
|
packaging==24.2
|
||||||
pandas==2.3.1
|
pandas==2.3.1
|
||||||
pango==0.0.1
|
|
||||||
passlib==1.7.4
|
|
||||||
pathspec==0.12.1
|
pathspec==0.12.1
|
||||||
pdfkit==1.0.0
|
|
||||||
phonenumbers==8.13.42
|
phonenumbers==8.13.42
|
||||||
pilkit==3.0
|
pilkit==3.0
|
||||||
pillow==10.4.0
|
pillow==10.4.0
|
||||||
pipenv==2024.4.1
|
priority==1.3.0
|
||||||
platformdirs==4.3.7
|
prometheus_client==0.22.1
|
||||||
prometheus_client==0.21.1
|
|
||||||
propcache==0.3.0
|
|
||||||
protobuf==6.30.1
|
|
||||||
pscript==0.7.7
|
|
||||||
psycopg-binary==3.2.6
|
|
||||||
psycopg2-binary==2.9.10
|
psycopg2-binary==2.9.10
|
||||||
purl==1.6
|
|
||||||
py-moneyed==3.0
|
|
||||||
pyasn1==0.6.1
|
pyasn1==0.6.1
|
||||||
PyAutoGUI==0.9.54
|
pyasn1_modules==0.4.2
|
||||||
pyclipper==1.3.0.post6
|
|
||||||
pycodestyle==2.12.1
|
|
||||||
pycountry==24.6.1
|
|
||||||
pycparser==2.22
|
pycparser==2.22
|
||||||
pydantic==2.11.7
|
pydantic==2.11.7
|
||||||
pydantic_core==2.33.2
|
pydantic_core==2.33.2
|
||||||
pydotplus==2.0.2
|
|
||||||
pydyf==0.11.0
|
|
||||||
PyGetWindow==0.0.9
|
|
||||||
Pygments==2.19.2
|
Pygments==2.19.2
|
||||||
PyJWT==2.9.0
|
pymongo==4.14.1
|
||||||
pylint==3.3.5
|
pyOpenSSL==25.1.0
|
||||||
PyMsgBox==1.0.9
|
|
||||||
pyparsing==3.2.1
|
|
||||||
pypdf==5.4.0
|
|
||||||
PyPDF2==3.0.1
|
|
||||||
pyperclip==1.9.0
|
|
||||||
pyphen==0.17.2
|
|
||||||
pypng==0.20220715.0
|
|
||||||
PyRect==0.2.0
|
|
||||||
PyScreeze==1.0.1
|
|
||||||
pyserial==3.5
|
|
||||||
PySocks==1.7.1
|
|
||||||
python-bidi==0.6.6
|
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
python-docx==1.1.2
|
python-dotenv==1.1.1
|
||||||
python-dotenv==1.0.1
|
|
||||||
python-engineio==4.11.2
|
|
||||||
python-ipware==3.0.0
|
|
||||||
python-jose==3.5.0
|
|
||||||
python-multipart==0.0.20
|
|
||||||
python-openid==2.2.5
|
|
||||||
python-slugify==8.0.4
|
python-slugify==8.0.4
|
||||||
python-socketio==5.12.1
|
|
||||||
python-stdnum==2.1
|
python-stdnum==2.1
|
||||||
python3-saml==1.16.0
|
|
||||||
python3-xlib==0.15
|
|
||||||
pytweening==1.2.0
|
|
||||||
pytz==2025.2
|
pytz==2025.2
|
||||||
pyvin==0.0.2
|
pyvin==0.0.2
|
||||||
PyYAML==6.0.2
|
PyYAML==6.0.2
|
||||||
pyzbar==0.1.9
|
pyzbar==0.1.9
|
||||||
qrcode==8.0
|
|
||||||
RapidFuzz==3.12.2
|
|
||||||
redis==6.2.0
|
redis==6.2.0
|
||||||
regex==2024.11.6
|
regex==2024.11.6
|
||||||
reportlab==4.3.1
|
|
||||||
requests==2.32.4
|
requests==2.32.4
|
||||||
requests-oauthlib==2.0.0
|
|
||||||
requests-toolbelt==1.0.0
|
requests-toolbelt==1.0.0
|
||||||
rfc3986==2.0.0
|
|
||||||
rich==14.0.0
|
rich==14.0.0
|
||||||
rsa==4.9.1
|
|
||||||
rubicon-objc==0.5.0
|
|
||||||
ruff==0.12.2
|
ruff==0.12.2
|
||||||
sacremoses==0.1.1
|
service-identity==24.2.0
|
||||||
scikit-image==0.25.2
|
|
||||||
scikit-learn==1.6.1
|
|
||||||
scipy==1.15.2
|
|
||||||
selenium==4.29.0
|
|
||||||
sentencepiece==0.2.0
|
|
||||||
setuptools==80.9.0
|
setuptools==80.9.0
|
||||||
shapely==2.0.7
|
|
||||||
simple-websocket==1.1.0
|
|
||||||
simsimd==6.2.1
|
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
snowballstemmer==2.2.0
|
|
||||||
sorl-thumbnail==12.9.0
|
|
||||||
sortedcontainers==2.4.0
|
|
||||||
soupsieve==2.7
|
soupsieve==2.7
|
||||||
SQLAlchemy==2.0.41
|
SQLAlchemy==2.0.41
|
||||||
sqlparse==0.5.3
|
sqlparse==0.5.3
|
||||||
stanza==1.10.1
|
|
||||||
starlette==0.46.1
|
|
||||||
stringzilla==3.12.3
|
|
||||||
suds==1.2.0
|
suds==1.2.0
|
||||||
svglib==1.5.1
|
|
||||||
swapper==1.3.0
|
swapper==1.3.0
|
||||||
sympy==1.13.1
|
|
||||||
tablib==3.8.0
|
tablib==3.8.0
|
||||||
tenacity==9.1.2
|
tenacity==9.1.2
|
||||||
termcolor==2.5.0
|
|
||||||
text-unidecode==1.3
|
text-unidecode==1.3
|
||||||
threadpoolctl==3.6.0
|
|
||||||
tifffile==2025.3.13
|
|
||||||
tinycss2==1.4.0
|
|
||||||
tinyhtml5==2.0.0
|
|
||||||
tomli==2.2.1
|
|
||||||
tomlkit==0.13.2
|
|
||||||
torch==2.6.0
|
|
||||||
tqdm==4.67.1
|
tqdm==4.67.1
|
||||||
trio==0.29.0
|
Twisted==25.5.0
|
||||||
trio-websocket==0.12.2
|
txaio==25.6.1
|
||||||
triton==3.2.0
|
|
||||||
types-python-dateutil==2.9.0.20250708
|
types-python-dateutil==2.9.0.20250708
|
||||||
typing-inspect==0.9.0
|
|
||||||
typing-inspection==0.4.1
|
typing-inspection==0.4.1
|
||||||
typing_extensions==4.14.1
|
typing_extensions==4.14.1
|
||||||
tzdata==2025.2
|
tzdata==2025.2
|
||||||
Unidecode==1.3.8
|
|
||||||
upgrade-requirements==1.7.0
|
|
||||||
urllib3==2.5.0
|
urllib3==2.5.0
|
||||||
uv==0.6.14
|
uvicorn==0.35.0
|
||||||
uvicorn==0.34.0
|
uvicorn-worker==0.3.0
|
||||||
uvloop==0.21.0
|
|
||||||
vbuild==0.8.2
|
|
||||||
virtualenv==20.30.0
|
|
||||||
vishap==0.1.5
|
|
||||||
vpic-api==0.7.4
|
|
||||||
watchfiles==1.0.4
|
|
||||||
wcwidth==0.2.13
|
wcwidth==0.2.13
|
||||||
weasyprint==64.1
|
|
||||||
webencodings==0.5.1
|
|
||||||
websocket-client==1.8.0
|
|
||||||
websockets==15.0.1
|
|
||||||
Werkzeug==3.1.3
|
|
||||||
whitenoise==6.9.0
|
whitenoise==6.9.0
|
||||||
wikipedia==1.4.0
|
zope.interface==7.2
|
||||||
wsproto==1.2.0
|
|
||||||
xmlsec==1.3.15
|
|
||||||
yarl==1.18.3
|
|
||||||
zopfli==0.2.3.post1
|
|
||||||
zstandard==0.23.0
|
zstandard==0.23.0
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
@ -1,4 +1,4 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "allauth/layouts/entrance.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load allauth %}
|
{% load allauth %}
|
||||||
{% block head_title %}
|
{% block head_title %}
|
||||||
|
|||||||
@ -265,13 +265,74 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-auto">
|
||||||
|
<div class="d-sm-block d-inline-flex d-md-flex flex-xl-column flex-xxl-row align-items-center align-items-xl-start align-items-xxl-center border-start-sm ps-sm-5 border-translucent">
|
||||||
|
<div class="d-flex bg-success-subtle rounded flex-center me-3 mb-sm-3 mb-md-0 mb-xl-3 mb-xxl-0"
|
||||||
|
style="width:32px;
|
||||||
|
height:32px">
|
||||||
|
<span class="text-success-dark icon-saudi_riyal"
|
||||||
|
style="width:24px;
|
||||||
|
height:24px"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="fw-bold mb-1">{{ _("Expected Revenue") }}</p>
|
||||||
|
<h4 class="fw-bolder text-nowrap">{{ opportunity.expected_revenue }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-xl-4 mb-7">
|
<div class="px-xl-4 mb-7">
|
||||||
<div class="row mx-0 mx-sm-3 mx-lg-0 px-lg-0">
|
<div class="row mx-0 mx-sm-3 mx-lg-0 px-lg-0">
|
||||||
|
<div class="col-sm-12 col-xxl-6 border-bottom border-end-xxl border-translucent py-3">
|
||||||
|
<table class="w-100 table-stats table-stats">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="py-2">
|
||||||
|
<div class="d-inline-flex align-items-center">
|
||||||
|
<div class="d-flex bg-success-subtle rounded-circle flex-center me-3"
|
||||||
|
style="width:24px;
|
||||||
|
height:24px">
|
||||||
|
<span class="text-success-dark"
|
||||||
|
data-feather="bar-chart-2"
|
||||||
|
style="width:16px;
|
||||||
|
height:16px"></span>
|
||||||
|
</div>
|
||||||
|
<p class="fw-bold mb-0">{% trans "Probability (%)" %}</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-2 d-none d-sm-block pe-sm-2">:</td>
|
||||||
|
<td class="py-2">
|
||||||
|
<p class="ps-6 ps-sm-0 fw-semibold mb-0 mb-0 pb-3 pb-sm-0">{{ opportunity.probability }} (%)</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="py-2">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="d-flex bg-success-subtle rounded flex-center me-3 mb-sm-3 mb-md-0 mb-xl-3 mb-xxl-0"
|
||||||
|
style="width:32px;
|
||||||
|
height:32px">
|
||||||
|
<span class="text-info-dark icon-saudi_riyal"
|
||||||
|
style="width:24px;
|
||||||
|
height:24px"></span>
|
||||||
|
</div>
|
||||||
|
<p class="fw-bold mb-0">{{ _("Estimated Revenue") }}</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-2 d-none d-sm-block pe-sm-2">:</td>
|
||||||
|
<td class="py-2">
|
||||||
|
<p class="ps-6 ps-sm-0 fw-semibold mb-0">
|
||||||
|
<span class="icon-saudi_riyal"></span>{{ opportunity.expected_revenue }}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
<div class="col-sm-12 col-xxl-6 border-bottom border-translucent py-3">
|
<div class="col-sm-12 col-xxl-6 border-bottom border-translucent py-3">
|
||||||
<table class="w-100 table-stats">
|
<table class="w-100 table-stats">
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@ -60,6 +60,17 @@
|
|||||||
<span>{{ _("Phone Number") }}</span>
|
<span>{{ _("Phone Number") }}</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
|
<th class="sort align-middle ps-4 pe-5 text-uppercase border-end border-translucent"
|
||||||
|
scope="col"
|
||||||
|
data-sort="contact"
|
||||||
|
style="width:15%">
|
||||||
|
<div class="d-inline-flex flex-center">
|
||||||
|
<div class="d-flex align-items-center px-1 py-1 bg-info-subtle rounded me-2">
|
||||||
|
<span class="text-info-dark" data-feather="user"></span>
|
||||||
|
</div>
|
||||||
|
<span>{{ _("National ID") |capfirst }}</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
<th class="sort align-middle ps-4 pe-5 text-uppercase border-end border-translucent"
|
<th class="sort align-middle ps-4 pe-5 text-uppercase border-end border-translucent"
|
||||||
scope="col"
|
scope="col"
|
||||||
data-sort="company"
|
data-sort="company"
|
||||||
@ -113,6 +124,9 @@
|
|||||||
<td class="phone align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent">
|
<td class="phone align-middle white-space-nowrap fw-semibold ps-4 border-end border-translucent">
|
||||||
<a class="text-body-highlight" href="tel:{{ customer.phone }}">{{ customer.phone_number }}</a>
|
<a class="text-body-highlight" href="tel:{{ customer.phone }}">{{ customer.phone_number }}</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="contact align-middle white-space-nowrap ps-4 border-end border-translucent fw-semibold text-body-highlight">
|
||||||
|
{{ customer.national_id }}
|
||||||
|
</td>
|
||||||
<td class="company align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 border-end border-translucent fw-semibold text-body-highlight">
|
<td class="company align-middle white-space-nowrap text-body-tertiary text-opacity-85 ps-4 border-end border-translucent fw-semibold text-body-highlight">
|
||||||
{{ customer.address }}
|
{{ customer.address }}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load humanize %}
|
|
||||||
{% load i18n static crispy_forms_filters custom_filters %}
|
{% load i18n static crispy_forms_filters custom_filters %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ _("View Customer") }}
|
{{ _("View Customer") }}
|
||||||
@ -110,50 +109,23 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table fs-9 mb-0">
|
<table class="table table-hover table-striped mb-0">
|
||||||
<thead>
|
<thead class="bg-body-tertiary">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="align-middle pe-6 text-uppercase text-start"
|
<th scope="col" style="width: 60%;">{% trans 'Note' %}</th>
|
||||||
scope="col"
|
<th scope="col" style="width: 15%;">{% trans 'Date' %}</th>
|
||||||
style="width:40%">{{ _("Note") }}</th>
|
|
||||||
<th class="align-middle text-start text-uppercase white-space-nowrap"
|
|
||||||
scope="col"
|
|
||||||
style="width:40%">{{ _("Created On") }}</th>
|
|
||||||
<th class="align-middle text-start text-uppercase white-space-nowrap"
|
|
||||||
scope="col"
|
|
||||||
style="width:40%">{{ _("Last Updated") }}</th>
|
|
||||||
<th class="align-middle pe-0 text-end" scope="col" style="width:10%;"></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="notesTable">
|
<tbody>
|
||||||
{% for note in notes %}
|
{% for note in notes %}
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="align-middle">
|
||||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{ note.note }}</td>
|
<td class="text-body-secondary">{{ note.note|default_if_none:""|linebreaksbr }}</td>
|
||||||
<td class="align-middle text-body-tertiary text-start white-space-nowrap">{{ note.created|naturalday|capfirst }}</td>
|
<td class="text-body-secondary text-nowrap">{{ note.created|date:"d M Y" }}</td>
|
||||||
<td class="align-middle text-body-tertiary text-start white-space-nowrap">{{ note.updated|naturalday|capfirst }}</td>
|
</tr>
|
||||||
<td class="align-middle text-end white-space-nowrap pe-0 action py-2">
|
{% empty %}
|
||||||
{% if note.created_by == request.user %}
|
<tr>
|
||||||
<a id="updateBtn"
|
<td colspan="4" class="text-center text-body-secondary">
|
||||||
href="#"
|
<i class="fas fa-info-circle me-2"></i>{% trans 'No notes found for this customer.' %}
|
||||||
onclick="updateNote(this)"
|
|
||||||
class="btn btn-sm btn-phoenix-primary me-2"
|
|
||||||
data-pk="{{ note.pk }}"
|
|
||||||
data-note="{{ note.note|escapejs }}"
|
|
||||||
data-url="{% url 'update_note' request.dealer.slug note.pk %}"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#noteModal"
|
|
||||||
data-note-title="{{ _("Update") }}">
|
|
||||||
<i class='fas fa-pen-square text-primary ms-2'></i>
|
|
||||||
{{ _("Update") }}
|
|
||||||
</a>
|
|
||||||
<button class="btn btn-phoenix-danger btn-sm delete-btn"
|
|
||||||
data-url="{% url 'delete_note_to_lead' request.dealer.slug note.pk %}"
|
|
||||||
data-message="Are you sure you want to delete this note?"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#deleteModal">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -168,6 +168,7 @@
|
|||||||
<h4 class="fw-bolder me-1">
|
<h4 class="fw-bolder me-1">
|
||||||
{{ dealer.user.userplan.plan.planpricing_set.first.price }} <span class="icon-saudi_riyal"></span>
|
{{ dealer.user.userplan.plan.planpricing_set.first.price }} <span class="icon-saudi_riyal"></span>
|
||||||
</h4>
|
</h4>
|
||||||
|
<h5 class="fs-9 fw-normal text-body-tertiary ms-1">{{ _("Per month") }}</h5>
|
||||||
</div>
|
</div>
|
||||||
<ul class="list-unstyled mb-4">
|
<ul class="list-unstyled mb-4">
|
||||||
{% for line in dealer.user.userplan.plan.description|splitlines %}
|
{% for line in dealer.user.userplan.plan.description|splitlines %}
|
||||||
|
|||||||
@ -50,7 +50,7 @@
|
|||||||
<section id="how-to-request" class="policy-section">
|
<section id="how-to-request" class="policy-section">
|
||||||
<h2>{% trans "4. How to Request" %}</h2>
|
<h2>{% trans "4. How to Request" %}</h2>
|
||||||
<p>
|
<p>
|
||||||
{% trans "Email our Billing and Support team at" %} <a href="mailto:haikal@support.sa">haikal@tehnal.sa</a> {% trans "with your company name, account ID, invoice number, and a detailed reason for the refund." %}
|
{% trans "Email our Billing and Support team at" %} <a href="mailto:haikal@support.sa">haikal@support.sa</a> {% trans "with your company name, account ID, invoice number, and a detailed reason for the refund." %}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@ -259,7 +259,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive scrollbar mb-3">
|
<div class="table-responsive scrollbar mb-3">
|
||||||
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
<table class="table table-sm fs-9 mb-0 overflow-hidden">
|
||||||
{% if car.marked_price and request.is_accountant or request.is_dealer or request.is_manager %}
|
{% if car.marked_price %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Cost Price"|capfirst %}</th>
|
<th>{% trans "Cost Price"|capfirst %}</th>
|
||||||
<td>{{ car.cost_price|floatformat:2 }}</td>
|
<td>{{ car.cost_price|floatformat:2 }}</td>
|
||||||
|
|||||||
@ -223,7 +223,7 @@
|
|||||||
name="go_to_stats"
|
name="go_to_stats"
|
||||||
value="true"
|
value="true"
|
||||||
class="btn btn-lg btn-phoenix-primary">
|
class="btn btn-lg btn-phoenix-primary">
|
||||||
{% trans "Save" %}
|
{% trans "Save and Go to Inventory" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if accounts or request.GET.q %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class="">
|
<h3 class="">
|
||||||
@ -198,7 +199,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% url 'account_create' request.dealer.slug coa_pk as create_account_url %}
|
||||||
|
{% include "empty-illustration-page.html" with value="account" url=create_account_url %}
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block customerJS %}
|
{% block customerJS %}
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
<div class="fs-10 d-block">{{ task.scheduled_type|capfirst }}</div>
|
<div class="fs-10 d-block">{{ task.scheduled_type|capfirst }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="sent align-middle white-space-nowrap text-start fw-thin text-body-tertiary py-2">{{ task.notes }}</td>
|
<td class="sent align-middle white-space-nowrap text-start fw-thin text-body-tertiary py-2">{{ task.notes }}</td>
|
||||||
<td class="date align-middle white-space-nowrap text-body py-2">{{ task.scheduled_at|naturaltime }}</td>
|
<td class="date align-middle white-space-nowrap text-body py-2">{{ task.scheduled_at|naturaltime|capfirst }}</td>
|
||||||
<td class="date align-middle white-space-nowrap text-body py-2">
|
<td class="date align-middle white-space-nowrap text-body py-2">
|
||||||
{% if task.completed %}
|
{% if task.completed %}
|
||||||
<span class="badge badge-phoenix fs-10 badge-phoenix-success"><i class="fa-solid fa-check"></i></span>
|
<span class="badge badge-phoenix fs-10 badge-phoenix-success"><i class="fa-solid fa-check"></i></span>
|
||||||
|
|||||||
@ -315,7 +315,7 @@
|
|||||||
class="form-control"
|
class="form-control"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
maxlength="10"
|
maxlength="10"
|
||||||
pattern="^(\+9665|05|9665)[0-9]{8}$"
|
pattern="^05\d{8}$"
|
||||||
inputmode="numeric"
|
inputmode="numeric"
|
||||||
placeholder='{{ _("05xxxxxxxx") }}'
|
placeholder='{{ _("05xxxxxxxx") }}'
|
||||||
value="{{ request.dealer.phone_number }}"
|
value="{{ request.dealer.phone_number }}"
|
||||||
|
|||||||
@ -141,7 +141,7 @@
|
|||||||
<h3 class="h6 mb-3">
|
<h3 class="h6 mb-3">
|
||||||
{{ plan.planpricing_set.first.price }}
|
{{ plan.planpricing_set.first.price }}
|
||||||
<span class="icon-saudi_riyal"></span>
|
<span class="icon-saudi_riyal"></span>
|
||||||
<span class="fs-8 fw-normal">/{{plan.planpricing_set.first.pricing.period}} {{ _("Days") }}</span>
|
<span class="fs-8 fw-normal">/{{plan.planpricing_set.first.pricing.period}} {{ _("month") }}</span>
|
||||||
</h3>
|
</h3>
|
||||||
<h5 class="mb-3 h6">{{ _("Included") }}</h5>
|
<h5 class="mb-3 h6">{{ _("Included") }}</h5>
|
||||||
<ul class="fa-ul ps-3 m-0">
|
<ul class="fa-ul ps-3 m-0">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user