forms validations for vat rate

This commit is contained in:
Faheed 2025-09-10 12:22:54 +03:00
commit acf335e2ec
33 changed files with 2316 additions and 1416 deletions

View File

@ -45,20 +45,4 @@ 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}")

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
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 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
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

View File

@ -2118,6 +2118,15 @@ 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( rate = forms.DecimalField(

View File

@ -17,6 +17,7 @@ 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
@ -29,7 +30,13 @@ 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
coa = entity.get_default_coa() if coa_slug:
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

View File

@ -0,0 +1,39 @@
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"
)
)

View File

@ -57,7 +57,7 @@ class Command(BaseCommand):
}, },
) )
send_email( send_email(
"noreply@yourdomain.com", settings.DEFAULT_FROM_EMAIL,
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(
"noreply@yourdomain.com", settings.DEFAULT_FROM_EMAIL,
inv.customer.email, inv.customer.email,
subject, subject,
message, message,

View File

@ -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,9 +58,19 @@ 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__lt=timezone.now().date() active=True, expire__lte=timezone.now() - timedelta(days=7)
) )
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):

View File

@ -0,0 +1,14 @@
# 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}'))

View File

@ -169,7 +169,7 @@ class DealerSlugMiddleware:
"/ar/help_center/", "/ar/help_center/",
"/en/help_center/", "/en/help_center/",
] ]
if request.path in paths: if request.path in paths:
return None return None

View File

@ -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 from django.core.validators import MinValueValidator,MaxValueValidator
import hashlib import hashlib
from django.db import models from django.db import models
from datetime import timedelta from datetime import timedelta
@ -206,12 +206,25 @@ 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(max_digits=5, decimal_places=2, default=Decimal("0.15")) rate = models.DecimalField(
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}%" return f"Rate: {self.rate * 100}%"
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
class CarType(models.IntegerChoices): class CarType(models.IntegerChoices):
@ -1365,7 +1378,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 EntityModel, on_delete=models.SET_NULL, null=True, blank=True,related_name="dealers"
) )
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"))
@ -1397,6 +1410,13 @@ 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):
@ -1424,6 +1444,8 @@ 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):

View File

@ -20,6 +20,7 @@ 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
@ -71,13 +72,12 @@ User = get_user_model()
# instance.save() # instance.save()
# check with marwan @receiver(post_save, sender=models.Car)
# @receiver(post_save, sender=models.Car) def create_dealers_make(sender, instance, created, **kwargs):
# def create_dealers_make(sender, instance, created, **kwargs): if created:
# if created: models.DealersMake.objects.get_or_create(
# models.DealersMake.objects.get_or_create( dealer=instance.dealer, car_make=instance.id_car_make
# dealer=instance.dealer, car_make=instance.id_car_make )
# )
@receiver(post_save, sender=models.Car) @receiver(post_save, sender=models.Car)
@ -1425,3 +1425,20 @@ 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}")

View File

@ -72,23 +72,32 @@ 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}")
# Get fresh instance from database instance = Dealer.objects.get(pk=dealer_id)
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
coa = entity.get_default_coa() if coa_slug:
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.id}") logger.error(f"No COA found for entity {entity.pk}")
return False return False
logger.info("Creating default accounts") logger.info("Creating default accounts")
@ -112,11 +121,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):

View File

@ -25,6 +25,7 @@ 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
@ -2391,15 +2392,17 @@ 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:
# Check if account already exists # existing_account = AccountModel.objects.filter(coa_model=coa,code=account_data["code"])
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"],
@ -2408,6 +2411,7 @@ 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"]

View File

@ -3640,17 +3640,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(user__email=email).exists(): if models.Staff.objects.filter(dealer=dealer,user__email=email).exists() or models.Dealer.objects.filter(user__email=email).exists():
messages.error( messages.error(
self.request, self.request,
_( _(
@ -4201,7 +4201,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_all_accounts().filter( account_qs = dealer.entity.get_default_coa_accounts().filter(
role__in=[ role__in=[
roles.ASSET_CA_CASH, roles.ASSET_CA_CASH,
roles.LIABILITY_CL_ACC_PAYABLE, roles.LIABILITY_CL_ACC_PAYABLE,
@ -4296,7 +4296,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_all_accounts().filter( account_qs = dealer.entity.get_default_coa_accounts().filter(
role__in=[ role__in=[
roles.ASSET_CA_CASH, roles.ASSET_CA_CASH,
roles.LIABILITY_CL_ACC_PAYABLE, roles.LIABILITY_CL_ACC_PAYABLE,
@ -5926,6 +5926,9 @@ 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
@ -6789,8 +6792,10 @@ 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)
print(note) if isinstance(note.content_object, models.Customer):
if isinstance(note.content_object, models.Lead): url = "customer_detail"
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"):
@ -9810,7 +9815,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 dealer.active_plan: if not hasattr(dealer.user,'userplan') or dealer.is_plan_expired:
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()
@ -9887,7 +9892,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:
@ -9923,7 +9928,16 @@ 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"
@ -10618,7 +10632,9 @@ 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().get(name="Unit") uom = entity.get_uom_all().filter(name="Unit").first()
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,
@ -11474,15 +11490,12 @@ 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, _('Invalid password. Please try again.')) messages.error(request, _(f'Invalid password. {str(form.errors)}'))
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})

View File

@ -26,4 +26,7 @@ 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

162
requirements.prod.txt Normal file
View File

@ -0,0 +1,162 @@
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

View File

@ -1,162 +1,374 @@
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
autobahn==24.4.2 autopep8==2.3.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
channels==4.2.2 chardet==5.2.0
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
constantly==23.10.4 commonmark==0.9.1
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
daphne==4.2.1 cssselect2==0.8.0
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-prometheus==2.4.1 django-polymorphic==3.1.0
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
incremental==24.7.2 ifaddr==0.2.0
iron-core==1.2.1 imageio==2.37.0
iron-mq==0.9 imagesize==1.4.1
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
priority==1.3.0 pipenv==2024.4.1
prometheus_client==0.22.1 platformdirs==4.3.7
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
pyasn1_modules==0.4.2 PyAutoGUI==0.9.54
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
pymongo==4.14.1 PyJWT==2.9.0
pyOpenSSL==25.1.0 pylint==3.3.5
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-dotenv==1.1.1 python-docx==1.1.2
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
service-identity==24.2.0 sacremoses==0.1.1
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
Twisted==25.5.0 trio==0.29.0
txaio==25.6.1 trio-websocket==0.12.2
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
uvicorn==0.35.0 uv==0.6.14
uvicorn-worker==0.3.0 uvicorn==0.34.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
zope.interface==7.2 wikipedia==1.4.0
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.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -1,4 +1,4 @@
{% extends "allauth/layouts/entrance.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% load allauth %} {% load allauth %}
{% block head_title %} {% block head_title %}

View File

@ -265,74 +265,13 @@
</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>

View File

@ -60,17 +60,6 @@
<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"
@ -124,9 +113,6 @@
<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>

View File

@ -1,4 +1,5 @@
{% 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") }}
@ -109,23 +110,50 @@
{% endif %} {% endif %}
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-striped mb-0"> <table class="table fs-9 mb-0">
<thead class="bg-body-tertiary"> <thead>
<tr> <tr>
<th scope="col" style="width: 60%;">{% trans 'Note' %}</th> <th class="align-middle pe-6 text-uppercase text-start"
<th scope="col" style="width: 15%;">{% trans 'Date' %}</th> scope="col"
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> <tbody id="notesTable">
{% for note in notes %} {% for note in notes %}
<tr class="align-middle"> <tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="text-body-secondary">{{ note.note|default_if_none:""|linebreaksbr }}</td> <td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{ note.note }}</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.created|naturalday|capfirst }}</td>
</tr> <td class="align-middle text-body-tertiary text-start white-space-nowrap">{{ note.updated|naturalday|capfirst }}</td>
{% empty %} <td class="align-middle text-end white-space-nowrap pe-0 action py-2">
<tr> {% if note.created_by == request.user %}
<td colspan="4" class="text-center text-body-secondary"> <a id="updateBtn"
<i class="fas fa-info-circle me-2"></i>{% trans 'No notes found for this customer.' %} href="#"
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 %}

View File

@ -168,7 +168,6 @@
<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 %}
@ -222,7 +221,7 @@
</div> </div>
<div class="d-flex justify-content-between text-body-secondary fs-9 mt-2"> <div class="d-flex justify-content-between text-body-secondary fs-9 mt-2">
<span>{{ _("Used") }}: {{ dealer.staff_count }}</span> <span>{{ _("Used") }}: {{ dealer.staff_count }}</span>
</div> </div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
@ -237,7 +236,7 @@
</div> </div>
<div class="d-flex justify-content-between text-body-secondary fs-9 mt-2"> <div class="d-flex justify-content-between text-body-secondary fs-9 mt-2">
<span>{{ _("Used") }}: {{ cars_count }}</span> <span>{{ _("Used") }}: {{ cars_count }}</span>
</div> </div>
</div> </div>
<small class="text-body-secondary mt-auto">{{ _("Contact support to increase your limits") }}</small> <small class="text-body-secondary mt-auto">{{ _("Contact support to increase your limits") }}</small>

View File

@ -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@support.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@tehnal.sa</a> {% trans "with your company name, account ID, invoice number, and a detailed reason for the refund." %}
</p> </p>
</section> </section>

View File

@ -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 %} {% if car.marked_price and request.is_accountant or request.is_dealer or request.is_manager %}
<tr> <tr>
<th>{% trans "Cost Price"|capfirst %}</th> <th>{% trans "Cost Price"|capfirst %}</th>
{% if request.is_dealer or request.is_accountant or request.manager%} {% if request.is_dealer or request.is_accountant or request.manager%}

View File

@ -10,7 +10,6 @@
</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="">
@ -199,10 +198,7 @@
</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>

View File

@ -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|capfirst }}</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"> <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>

View File

@ -315,7 +315,7 @@
class="form-control" class="form-control"
dir="ltr" dir="ltr"
maxlength="10" maxlength="10"
pattern="^05\d{8}$" pattern="^(\+9665|05|9665)[0-9]{8}$"
inputmode="numeric" inputmode="numeric"
placeholder='{{ _("05xxxxxxxx") }}' placeholder='{{ _("05xxxxxxxx") }}'
value="{{ request.dealer.phone_number }}" value="{{ request.dealer.phone_number }}"
@ -668,7 +668,7 @@
function formatCardNumber(e) { function formatCardNumber(e) {
let val = this.value.replace(/\D/g, "").substring(0, 16); let val = this.value.replace(/\D/g, "").substring(0, 16);
this.value = val.replace(/(.{4})/g, "$1 ").trim(); this.value = val.replace(/(.{4})/g, "$1 ").trim();
// Validate as user types // Validate as user types
validateCardNumber(this); validateCardNumber(this);
} }

View File

@ -336,7 +336,7 @@
<form action="{% url 'update_estimate_additionals' request.dealer.slug estimate.pk %}" <form action="{% url 'update_estimate_additionals' request.dealer.slug estimate.pk %}"
method="post"> method="post">
{% csrf_token %} {% csrf_token %}
{{ additionals_form|crispy }} {{ additionals_form|crispy }}
<button type="submit" class="btn btn-phoenix-primary">{% trans 'Update' %}</button> <button type="submit" class="btn btn-phoenix-primary">{% trans 'Update' %}</button>
</form> </form>
</div> </div>

View File

@ -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}} {{ _("month") }}</span> <span class="fs-8 fw-normal">/{{plan.planpricing_set.first.pricing.period}} {{ _("Days") }}</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">
@ -233,6 +233,6 @@
</div> </div>
</div> </div>
</section> </section>
</div> </div>
{% endblock %} {% endblock %}