update the add colors visuals + more..

This commit is contained in:
ismail 2025-05-05 14:40:13 +03:00
parent 2070125e0f
commit 32437a2ec0
11 changed files with 220 additions and 98 deletions

View File

@ -1,3 +1,4 @@
import re
from django.core.cache import cache from django.core.cache import cache
from datetime import datetime from datetime import datetime
from luhnchecker.luhn import Luhn from luhnchecker.luhn import Luhn
@ -104,6 +105,21 @@ class StaffForm(forms.ModelForm):
widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}), widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
queryset=Service.objects.all(), queryset=Service.objects.all(),
required=False,) required=False,)
phone_number = forms.CharField(
required=False,
max_length=10,
min_length=10,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('Your Phone Number'),
'id': 'phone'
}),
label=_('Phone Number'),
validators=[RegexValidator(
regex=r'^05[0-9]{8}$',
message=_('Enter a valid phone number (8-15 digits, starting with 05)')
)]
)
class Meta: class Meta:
model = Staff model = Staff
fields = ["name", "arabic_name", "phone_number", "staff_type"] fields = ["name", "arabic_name", "phone_number", "staff_type"]
@ -182,21 +198,21 @@ class CustomerForm(forms.Form):
last_name = forms.CharField() last_name = forms.CharField()
arabic_name = forms.CharField() arabic_name = forms.CharField()
email = forms.EmailField() email = forms.EmailField()
phone_number = PhoneNumberField( # phone_number = PhoneNumberField(
label=_("Phone Number"), # label=_("Phone Number"),
widget=forms.TextInput( # widget=forms.TextInput(
attrs={ # attrs={
"placeholder": _("Phone"), # "placeholder": _("Phone"),
} # }
), # ),
region="SA", # region="SA",
error_messages={ # error_messages={
"required": _("This field is required."), # "required": _("This field is required."),
"invalid": _("Phone number must be in the format 05xxxxxxxx"), # "invalid": _("Phone number must be in the format 05xxxxxxxx"),
}, # },
required=True, # required=True,
) # )
phone_number = forms.CharField(label=_("Phone Number"),min_length=10,max_length=10,validators=[RegexValidator(regex='^05[0-9]{8}$')], required=True)
national_id = forms.CharField(max_length=10,required=False) national_id = forms.CharField(max_length=10,required=False)
crn = forms.CharField(required=False) crn = forms.CharField(required=False)
vrn = forms.CharField(required=False) vrn = forms.CharField(required=False)
@ -466,6 +482,8 @@ class VendorForm(forms.ModelForm):
:ivar Meta: Inner class to define metadata for the Vendor form. :ivar Meta: Inner class to define metadata for the Vendor form.
:type Meta: Type[VendorForm.Meta] :type Meta: Type[VendorForm.Meta]
""" """
phone_number = forms.CharField(label=_("Phone Number"),min_length=10,max_length=10,validators=[RegexValidator(regex='^05[0-9]{8}$')], required=True)
contact_person = forms.CharField(label=_("Phone Number"),min_length=10,max_length=10,validators=[RegexValidator(regex='^05[0-9]{8}$')], required=True)
class Meta: class Meta:
model = Vendor model = Vendor
@ -755,19 +773,34 @@ class WizardForm2(forms.Form):
}, },
) )
phone_number = PhoneNumberField( # phone_number = PhoneNumberField(
label=_("Phone Number"), # label=_("Phone Number"),
widget=forms.TextInput( # widget=forms.TextInput(
attrs={ # attrs={
"placeholder": _("Phone"), # "placeholder": _("Phone"),
} # }
), # ),
region="SA", # region="SA",
error_messages={ # error_messages={
"required": _("This field is required."), # "required": _("This field is required."),
"invalid": _("Phone number must be in the format 05xxxxxxxx"), # "invalid": _("Phone number must be in the format 05xxxxxxxx"),
}, # },
required=True, # required=True,
# )
phone = forms.CharField(
required=False,
max_length=10,
min_length=10,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('Your Phone Number'),
'id': 'phone'
}),
label=_('Phone Number'),
validators=[RegexValidator(
regex=r'^05[0-9]{8}$',
message=_('Enter a valid phone number (8-15 digits, starting with 05)')
)]
) )
@ -1550,7 +1583,8 @@ class PaymentPlanForm(forms.Form):
phone = forms.CharField( phone = forms.CharField(
required=False, required=False,
max_length=20, max_length=10,
min_length=10,
widget=forms.TextInput(attrs={ widget=forms.TextInput(attrs={
'class': 'form-control', 'class': 'form-control',
'placeholder': _('Your Phone Number'), 'placeholder': _('Your Phone Number'),
@ -1558,8 +1592,8 @@ class PaymentPlanForm(forms.Form):
}), }),
label=_('Phone Number'), label=_('Phone Number'),
validators=[RegexValidator( validators=[RegexValidator(
regex=r'^\+?[0-9]{8,15}$', regex=r'^05[0-9]{8}$',
message=_('Enter a valid phone number (8-15 digits, + optional)') message=_('Enter a valid phone number (8-15 digits, starting with 05)')
)] )]
) )

View File

@ -32,7 +32,7 @@ class Command(BaseCommand):
ten_users_quota = Quota.objects.create(name='10 users', codename='10 users', unit='number') ten_users_quota = Quota.objects.create(name='10 users', codename='10 users', unit='number')
# Create plans # Create plans
basic_plan = Plan.objects.create(name='Basic', description='Basic plan', available=True, visible=True) basic_plan = Plan.objects.create(name='Basic', description='basic plan', available=True, visible=True)
pro_plan = Plan.objects.create(name='Pro', description='Pro plan', available=True, visible=True) pro_plan = Plan.objects.create(name='Pro', description='Pro plan', available=True, visible=True)
enterprise_plan = Plan.objects.create(name='Enterprise', description='Enterprise plan', available=True, visible=True) enterprise_plan = Plan.objects.create(name='Enterprise', description='Enterprise plan', available=True, visible=True)

View File

@ -3,15 +3,22 @@ from django.core.mail import send_mail
from allauth.account.models import EmailConfirmation from allauth.account.models import EmailConfirmation
from inventory.tasks import send_email from inventory.tasks import send_email
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
import re
from inventory.tasks import create_coa_accounts
from inventory.models import Dealer
User = get_user_model() User = get_user_model()
class Command(BaseCommand): class Command(BaseCommand):
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
user = User.objects.last() # user = User.objects.last()
print(user.email) # print(user.email)
# 2. Force email confirmation # # 2. Force email confirmation
# email = user.emailaddress_set.first() # # email = user.emailaddress_set.first()
confirmation = EmailConfirmation.create(user.email) # confirmation = EmailConfirmation.create(user.email)
confirmation.send() # confirmation.send()
# result = re.match(r'^05\d{8}$', '0625252522')
# print(result)
dealer = Dealer.objects.last()
create_coa_accounts(dealer.pk)

View File

@ -0,0 +1,5 @@
from inventory.models import VatRate
from decimal import Decimal
class Command:
def handle(self, *args, **kwargs):
VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True)

View File

@ -630,23 +630,23 @@ def create_dealer_settings(sender, instance, created, **kwargs):
# current_staff_count = instance.dealer.staff.count() # current_staff_count = instance.dealer.staff.count()
# if current_staff_count > allowed_users: # if current_staff_count > allowed_users:
# raise ValidationError(_("You have reached the maximum number of staff users allowed for your plan.")) # raise ValidationError(_("You have reached the maximum number of staff users allowed for your plan."))
@receiver(post_save, sender=models.Dealer) # @receiver(post_save, sender=models.Dealer)
def create_vat(sender, instance, created, **kwargs): # def create_vat(sender, instance, created, **kwargs):
""" # """
Signal receiver that listens to the `post_save` signal for the `Dealer` model # Signal receiver that listens to the `post_save` signal for the `Dealer` model
and handles the creation of a `VatRate` instance if it does not already exist. # and handles the creation of a `VatRate` instance if it does not already exist.
This function ensures that a default VAT rate is created with a specified rate # This function ensures that a default VAT rate is created with a specified rate
and is marked as active. It is connected to the Django signals framework and # and is marked as active. It is connected to the Django signals framework and
automatically executes whenever a `Dealer` instance is saved. # automatically executes whenever a `Dealer` instance is saved.
:param sender: The model class that triggered the signal (in this case, `Dealer`). # :param sender: The model class that triggered the signal (in this case, `Dealer`).
:param instance: The instance of the model being saved. # :param instance: The instance of the model being saved.
:param created: Boolean indicating whether a new instance was created. # :param created: Boolean indicating whether a new instance was created.
:param kwargs: Additional keyword arguments passed by the signal. # :param kwargs: Additional keyword arguments passed by the signal.
:return: None # :return: None
""" # """
VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True) # VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True)
@receiver(post_save, sender=models.Dealer) @receiver(post_save, sender=models.Dealer)
def create_make_ledger_accounts(sender, instance, created, **kwargs): def create_make_ledger_accounts(sender, instance, created, **kwargs):

View File

@ -545,7 +545,7 @@ def create_coa_accounts(pk):
entity.create_account(coa_model=coa, code="6303", role=roles.EXPENSE_OTHER, name=_("Foreign Currency Translation"), balance_type="debit", active=True) entity.create_account(coa_model=coa, code="6303", role=roles.EXPENSE_OTHER, name=_("Foreign Currency Translation"), balance_type="debit", active=True)
entity.create_account(coa_model=coa, code="6304", role=roles.EXPENSE_OTHER, name=_("Interest Expenses"), balance_type="debit", active=True) entity.create_account(coa_model=coa, code="6304", role=roles.EXPENSE_OTHER, name=_("Interest Expenses"), balance_type="debit", active=True)
# create_settings(instance.pk) create_settings(instance.pk)
# @background # @background
# def create_groups(instance): # def create_groups(instance):
@ -556,26 +556,33 @@ def create_coa_accounts(pk):
# group_manager.set_default_permissions() # group_manager.set_default_permissions()
# instance.user.groups.add(group) # instance.user.groups.add(group)
@background # @background
def create_accounts_for_make(pk): def create_accounts_for_make(dealer,makes):
instance = Dealer.objects.get(pk=pk) entity = dealer.entity
entity = instance.entity
coa = entity.get_default_coa() coa = entity.get_default_coa()
for make in CarMake.objects.all(): for make in makes:
last_account = entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES).order_by('-created').first() last_account = entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES).order_by('-created').first()
if len(last_account.code) == 4: if len(last_account.code) == 4:
code = f"{int(last_account.code)}{1:03d}" code = f"{int(last_account.code)}{1:03d}"
elif len(last_account.code) > 4: elif len(last_account.code) > 4:
code = f"{int(last_account.code)+1}" code = f"{int(last_account.code)+1}"
entity.create_account(
if not entity.get_all_accounts().filter(
name=make.name, name=make.name,
code=code,
role=roles.ASSET_CA_RECEIVABLES, role=roles.ASSET_CA_RECEIVABLES,
coa_model=coa, coa_model=coa,
balance_type="credit", balance_type="credit",
active=True active=True
) ).exists():
entity.create_account(
name=make.name,
code=code,
role=roles.ASSET_CA_RECEIVABLES,
coa_model=coa,
balance_type="credit",
active=True
)

View File

@ -170,7 +170,7 @@ from .utils import (
set_invoice_payment, set_invoice_payment,
CarTransfer, CarTransfer,
) )
from .tasks import send_email from .tasks import create_accounts_for_make, send_email
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -6167,7 +6167,7 @@ class OrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# email # email
@login_required @login_required
@permission_required("django_ledger.view_estimate", raise_exception=True) @permission_required("django_ledger.view_estimatemodel", raise_exception=True)
def send_email_view(request, pk): def send_email_view(request, pk):
""" """
View function to send an email for an estimate. This function allows authenticated and View function to send an email for an estimate. This function allows authenticated and
@ -6217,11 +6217,11 @@ def send_email_view(request, pk):
{dealer.phone_number} {dealer.phone_number}
هيكل | Haikal هيكل | Haikal
""" """
subject = _("Quotation")
send_email( send_email(
settings.DEFAULT_FROM_EMAIL, str(settings.DEFAULT_FROM_EMAIL),
estimate.customer.email, estimate.customer.email,
_("Quotation"), subject,
msg, msg,
) )
@ -7188,6 +7188,8 @@ def assign_car_makes(request):
if request.method == "POST": if request.method == "POST":
form = forms.DealersMakeForm(request.POST, dealer=dealer) form = forms.DealersMakeForm(request.POST, dealer=dealer)
if form.is_valid(): if form.is_valid():
makes = form.cleaned_data["car_makes"]
create_accounts_for_make(dealer, makes)
form.save() form.save()
return redirect("dealer_detail", pk=dealer.pk) return redirect("dealer_detail", pk=dealer.pk)
else: else:

View File

@ -22,4 +22,8 @@ python3 manage.py loaddata --app carequipment carequipment_backup.json
echo "Populating colors" echo "Populating colors"
python3 manage.py populate_colors python3 manage.py populate_colors
python3 manage.py tenhal_plan
python3 manage.py set_vat
echo "Done" echo "Done"

View File

@ -66,3 +66,8 @@ tqdm==4.67.1
typing_extensions==4.13.0 typing_extensions==4.13.0
tzdata==2025.2 tzdata==2025.2
urllib3==2.3.0 urllib3==2.3.0
fpdf2
luhnchecker
requests
django-ckeditor
django-cors-headers

View File

@ -8,54 +8,110 @@
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<!-- Exterior Colors --> <!-- Exterior Colors -->
<div class="row g-4"> <div class="row g-4">
<p class="fs-5 mb-0">{% trans 'Exterior Colors' %}</p> <p class="fs-5 mb-0">{% trans 'Exterior Colors' %}</p>
{% for color in form.fields.exterior.queryset %} {% for color in form.fields.exterior.queryset %}
<div class="col-lg-4 col-xl-2"> <div class="col-lg-4 col-xl-2">
<div class="card rounded shadow"> <div class="card rounded shadow-sm color-card">
<div class="card-body" style="background-color: rgb({{ color.rgb }});"> <label class="color-option">
<div class="form-check"> <input class="color-radio"
<input class="form-check-input" type="radio"
type="radio" name="exterior"
name="exterior" value="{{ color.id }}">
id="id_exterior" value="{{ color.id }}" > <div class="card-body color-display" style="background-color: rgb({{ color.rgb }});">
<label class="form-check-label" for="id_exterior"> <div class="">
<small>{{ color.get_local_name }}</small> <small>{{ color.get_local_name }}</small>
</label> </div>
</div> </div>
</div> </label>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
<!-- Interior Colors --> <!-- Interior Colors -->
<p class="fs-5 mt-3 mb-0">{% trans 'Interior Colors' %}</p> <p class="fs-5 mt-3 mb-0">{% trans 'Interior Colors' %}</p>
{% for color in form.fields.interior.queryset %} {% for color in form.fields.interior.queryset %}
<div class="col-lg-4 col-xl-2"> <div class="col-lg-4 col-xl-2">
<div class="card rounded shadow"> <div class="card rounded shadow-sm color-card">
<div class="card-body" style="background-color: rgb({{ color.rgb }});"> <label class="color-option">
<div class="form-check"> <input class="color-radio"
<input class="form-check-input" type="radio"
type="radio" name="interior"
name="interior" value="{{ color.id }}">
id="id_interior" value="{{ color.id }}" > <div class="card-body color-display" style="background-color: rgb({{ color.rgb }});">
<label class="form-check-label" for="id_interior"> <div class="">
<small>{{ color.get_local_name }}</small> <small>{{ color.get_local_name }}</small>
</label> </div>
</div> </div>
</div> </label>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<!-- Save and Cancel Buttons --> <!-- Save and Cancel Buttons -->
<div class="row g-1 mt-4"> <div class="row g-1 mt-4">
<div class="btn-group"> <div class="btn-group">
<button type="submit" class="btn btn-success btn-sm me-1">{% trans "Save" %}</button> <button type="submit" class="btn btn-success btn-sm me-1">{% trans "Save" %}</button>
<a href="{% url 'car_detail' car.pk %}" class="btn btn-secondary btn-sm">{% trans "Cancel" %}</a> <a href="{% url 'car_detail' car.pk %}" class="btn btn-secondary btn-sm">{% trans "Cancel" %}</a>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
<style>
.color-card {
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.color-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.color-option {
display: block;
position: relative;
margin: 0;
padding: 0;
}
.color-radio {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.color-radio:checked + .color-display {
border: 2px solid #0d6efd;
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
}
.color-radio:focus + .color-display {
border-color: #86b7fe;
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
}
.color-display {
height: 100px;
display: flex;
align-items: flex-end;
justify-content: center;
padding: 10px;
border-radius: 0.25rem;
transition: all 0.2s ease;
}
.color-name {
background-color: rgba(255, 255, 255, 0.8);
padding: 2px 5px;
border-radius: 3px;
text-align: center;
width: 100%;
}
</style>
{% endblock %} {% endblock %}

View File

@ -23,6 +23,7 @@
<tr> <tr>
<th>{% trans 'name'|capfirst %}</th> <th>{% trans 'name'|capfirst %}</th>
<th>{% trans 'arabic name'|capfirst %}</th> <th>{% trans 'arabic name'|capfirst %}</th>
<th>{% trans 'email'|capfirst %}</th>
<th>{% trans 'phone number'|capfirst %}</th> <th>{% trans 'phone number'|capfirst %}</th>
<th>{% trans 'role'|capfirst %}</th> <th>{% trans 'role'|capfirst %}</th>
<th>{% trans 'groups'|capfirst %}</th> <th>{% trans 'groups'|capfirst %}</th>
@ -34,6 +35,7 @@
<tr> <tr>
<td>{{ user.name }}</td> <td>{{ user.name }}</td>
<td>{{ user.arabic_name }}</td> <td>{{ user.arabic_name }}</td>
<td>{{ user.email }}</td>
<td>{{ user.phone_number }}</td> <td>{{ user.phone_number }}</td>
<td> <td>
<span class="badge badge-sm bg-primary"><i class="fa-solid fa-scroll"></i> {% trans user.staff_type|title %}</span> <span class="badge badge-sm bg-primary"><i class="fa-solid fa-scroll"></i> {% trans user.staff_type|title %}</span>
@ -61,7 +63,7 @@
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
{% endif %} {% endif %}
</div> </div>
</div> </div>
</section> </section>
{% endblock %} {% endblock %}