This commit is contained in:
gitea 2025-02-13 08:49:53 +00:00
commit 631da4d9b6
25 changed files with 531 additions and 506 deletions

BIN
.DS_Store vendored

Binary file not shown.

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ db.sqlite
db.sqlite3 db.sqlite3
media media
car_inventory/settings.py car_inventory/settings.py
scripts/dsrpipe.py
# Backup files # # Backup files #
*.bak *.bak

Binary file not shown.

View File

@ -3,4 +3,5 @@
# #
# websocket_urlpatterns = [ # websocket_urlpatterns = [
# re_path(r'ws/vin_scan/$', consumers.VINScanConsumer.as_asgi()), # re_path(r'ws/vin_scan/$', consumers.VINScanConsumer.as_asgi()),
# ] # ]

View File

@ -1,5 +1,6 @@
from rest_framework import serializers
from . import models from . import models
from rest_framework import serializers
from inventory import models as inventory_models
class CarVINSerializer(serializers.ModelSerializer): class CarVINSerializer(serializers.ModelSerializer):
@ -12,3 +13,71 @@ class CarVINSerializer(serializers.ModelSerializer):
return models.CarVIN.objects.create(vin=vin, **validated_data) return models.CarVIN.objects.create(vin=vin, **validated_data)
class CarMakeSerializer(serializers.ModelSerializer):
car_models = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='carmodel_set')
class Meta:
model = inventory_models.CarMake
fields = '__all__'
class CarModelSerializer(serializers.ModelSerializer):
car_series = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='carserie_set')
class Meta:
model = inventory_models.CarModel
fields = '__all__'
class CarSerieSerializer(serializers.ModelSerializer):
car_trims = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='cartrim_set')
class Meta:
model = inventory_models.CarSerie
fields = '__all__'
class CarTrimSerializer(serializers.ModelSerializer):
car_equipments = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='carequipment_set')
car_specification_values = serializers.PrimaryKeyRelatedField(many=True, read_only=True,
source='carspecificationvalue_set')
class Meta:
model = inventory_models.CarTrim
fields = '__all__'
class CarEquipmentSerializer(serializers.ModelSerializer):
car_option_values = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='caroptionvalue_set')
class Meta:
model = inventory_models.CarEquipment
fields = '__all__'
class CarSpecificationSerializer(serializers.ModelSerializer):
child_specifications = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='carspecification_set')
class Meta:
model = inventory_models.CarSpecification
fields = '__all__'
class CarSpecificationValueSerializer(serializers.ModelSerializer):
class Meta:
model = inventory_models.CarSpecificationValue
fields = '__all__'
class CarOptionSerializer(serializers.ModelSerializer):
child_options = serializers.PrimaryKeyRelatedField(many=True, read_only=True, source='caroption_set')
class Meta:
model = inventory_models.CarOption
fields = '__all__'
class CarOptionValueSerializer(serializers.ModelSerializer):
class Meta:
model = inventory_models.CarOptionValue
fields = '__all__'

View File

@ -1,8 +1,22 @@
from django.urls import path from django.urls import path, include
from rest_framework import routers
from api import views from api import views
router = routers.DefaultRouter()
router.register(r'car-makes', views.CarMakeViewSet)
router.register(r'car-models', views.CarModelViewSet)
router.register(r'car-series', views.CarSerieViewSet)
router.register(r'car-trims', views.CarTrimViewSet)
router.register(r'car-equipments', views.CarEquipmentViewSet)
router.register(r'car-specifications', views.CarSpecificationViewSet)
router.register(r'car-specification-values', views.CarSpecificationValueViewSet)
router.register(r'car-options', views.CarOptionViewSet)
router.register(r'car-option-values', views.CarOptionValueViewSet)
urlpatterns = [ urlpatterns = [
path('', include(router.urls)),
path('cars/vin/', views.CarVINViewSet.as_view(), name='car_vin'), path('cars/vin/', views.CarVINViewSet.as_view(), name='car_vin'),
path('login/', views.LoginView.as_view(), name='login'), path('login/', views.LoginView.as_view(), name='login'),
] ]

View File

@ -1,5 +1,6 @@
from django.db.models import Q
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from rest_framework import permissions, status, viewsets from rest_framework import permissions, status, viewsets, generics
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
@ -7,8 +8,9 @@ from django.shortcuts import render
from . import models, serializers from . import models, serializers
from .services import get_car_data, get_from_cardatabase from .services import get_car_data, get_from_cardatabase
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
# from inventory.models import CustomUser
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from inventory import models as inventory_models
class LoginView(APIView): class LoginView(APIView):
@ -51,3 +53,46 @@ class CarVINViewSet(APIView):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class CarMakeViewSet(viewsets.ModelViewSet):
queryset = inventory_models.CarMake.objects.filter(is_sa_import=True)
serializer_class = serializers.CarMakeSerializer
class CarModelViewSet(viewsets.ModelViewSet):
queryset = inventory_models.CarModel.objects.filter(id_car_make__is_sa_import=True)
serializer_class = serializers.CarModelSerializer
class CarSerieViewSet(viewsets.ModelViewSet):
queryset = inventory_models.CarSerie.objects.all()
serializer_class = serializers.CarSerieSerializer
class CarTrimViewSet(viewsets.ModelViewSet):
queryset = inventory_models.CarTrim.objects.all()
serializer_class = serializers.CarTrimSerializer
class CarEquipmentViewSet(viewsets.ModelViewSet):
queryset = inventory_models.CarEquipment.objects.all()
serializer_class = serializers.CarEquipmentSerializer
class CarSpecificationViewSet(viewsets.ModelViewSet):
queryset = inventory_models.CarSpecification.objects.all()
serializer_class = serializers.CarSpecificationSerializer
class CarSpecificationValueViewSet(viewsets.ModelViewSet):
queryset = inventory_models.CarSpecificationValue.objects.all()
serializer_class = serializers.CarSpecificationValueSerializer
class CarOptionViewSet(viewsets.ModelViewSet):
queryset = inventory_models.CarOption.objects.all()
serializer_class = serializers.CarOptionSerializer
class CarOptionValueViewSet(viewsets.ModelViewSet):
queryset = inventory_models.CarOptionValue.objects.all()
serializer_class = serializers.CarOptionValueSerializer

View File

@ -1,6 +1,6 @@
from openai import OpenAI from openai import OpenAI
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from inventory.models import CarSerie, CarModel, CarMake, CarTrim, CarOption, CarSpecificationValue from inventory.models import CarSerie, CarModel, CarMake, CarTrim, CarOption, CarSpecification
from django.conf import settings from django.conf import settings
@ -9,28 +9,35 @@ class Command(BaseCommand):
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
client = OpenAI(api_key=settings.OPENAI_API_KEY) client = OpenAI(api_key=settings.OPENAI_API_KEY)
car_option = CarOption.objects.all()[10300:] en_value = CarModel.objects.all()
total = car_option.count()
total = en_value.count()
print(f'Translating {total} names...') print(f'Translating {total} names...')
for index, car_option in enumerate(car_option, start=1): for index, en_value in enumerate(en_value, start=1):
try: if not en_value.arabic_name:
completion = client.chat.completions.create( try:
model="gpt-4o", completion = client.chat.completions.create(
messages=[ model="gpt-4o",
{ messages=[
"role": "system", {
"content": "You are an assistant that translates English to Arabic." "role": "system",
}, "content": (
{ "You are an assistant that translates English to Arabic."
"role": "user", "You are an assistant specialized in cars and automotive terms."
"content": car_option.name "If the model name is a number just write it as is"
} "You can get the arabic names for makes, models, series, trims, options, and specifications."
], )
temperature=0.2, },
) {
translation = completion.choices[0].message.content.strip() "role": "user",
car_option.arabic_name = translation "content": en_value.name
car_option.save() }
print(f"[{index}/{total}] Translated '{car_option.name}' to '{translation}'") ],
except Exception as e: temperature=0.2,
print(f"Error translating '{car_option.name}': {e}") )
translation = completion.choices[0].message.content.strip()
en_value.arabic_name = translation
en_value.save()
print(f"[{index}/{total}] .. Done")
except Exception as e:
print(f"Error translating '{en_value.name}': {e}")

View File

@ -452,6 +452,10 @@ class Car(models.Model):
"id": self.id, "id": self.id,
} }
def get_specifications(self):
specs = CarSpecificationValue.objects.filter(id_car_trim=self.id_car_trim)
return specs
class CarTransfer(models.Model): class CarTransfer(models.Model):
car = models.ForeignKey( car = models.ForeignKey(
"Car", "Car",

View File

@ -115,6 +115,28 @@ from django.core.files.storage import default_storage
from plans.models import Plan,PlanPricing from plans.models import Plan,PlanPricing
from django_ledger.utils import accruable_net_summary from django_ledger.utils import accruable_net_summary
from appointment.views_admin import fetch_user_appointments from appointment.views_admin import fetch_user_appointments
from django_ledger.views.financial_statement import (
FiscalYearBalanceSheetView,
BaseIncomeStatementRedirectView,
FiscalYearIncomeStatementView,
BaseCashFlowStatementRedirectView,
FiscalYearCashFlowStatementView,
)
from django_ledger.views.entity import EntityModelDetailBaseView,EntityModelDetailHandlerView
from django.views.generic import DetailView, RedirectView
from django_ledger.io.io_core import get_localdate
from django_ledger.models import EntityModel, EntityUnitModel
from django_ledger.views.mixins import (
QuarterlyReportMixIn,
YearlyReportMixIn,
MonthlyReportMixIn,
DateReportMixIn,
DjangoLedgerSecurityMixIn,
EntityUnitMixIn,
BaseDateNavigationUrlMixIn,
PDFReportMixIn,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -2649,10 +2671,11 @@ class PaymentRequest(LoginRequiredMixin, DetailView):
models.Car.objects.get(vin=car.item_model.name) models.Car.objects.get(vin=car.item_model.name)
for car in context["estimate"].get_itemtxs_data()[0].all() for car in context["estimate"].get_itemtxs_data()[0].all()
] ]
return context return context
class EstimatePreviewView(LoginRequiredMixin, DetailView): class EstimatePreviewView(DetailView):
model = EstimateModel model = EstimateModel
context_object_name = "estimate" context_object_name = "estimate"
template_name = "sales/estimates/estimate_preview.html" template_name = "sales/estimates/estimate_preview.html"
@ -2674,30 +2697,31 @@ class EstimatePreviewView(LoginRequiredMixin, DetailView):
@login_required @login_required
def estimate_mark_as(request, pk): def estimate_mark_as(request, pk):
estimate = get_object_or_404(EstimateModel, pk=pk) estimate = get_object_or_404(EstimateModel, pk=pk)
entity = estimate.entity dealer = get_user_type(request.user)
entity = dealer.entity
mark = request.GET.get("mark") mark = request.GET.get("mark")
if mark: if mark:
if mark == "review": if mark == "review":
if not estimate.can_review(): if not estimate.can_review():
messages.error(request, "Estimate is not ready for review") messages.error(request, _("Estimate is not ready for review"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_review() estimate.mark_as_review()
elif mark == "approved": elif mark == "approved":
if not estimate.can_approve(): if not estimate.can_approve():
messages.error(request, "Estimate is not ready for approval") messages.error(request, _("Estimate is not ready for approval"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_approved() estimate.mark_as_approved()
messages.success(request, "Estimate approved successfully.") messages.success(request, _("Estimate approved successfully."))
elif mark == "rejected": elif mark == "rejected":
if not estimate.can_cancel(): if not estimate.can_cancel():
messages.error(request, "Estimate is not ready for rejection") messages.error(request, _("Estimate is not ready for rejection"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_canceled() estimate.mark_as_canceled()
messages.success(request, "Estimate canceled successfully.") messages.success(request, _("Estimate canceled successfully."))
elif mark == "completed": elif mark == "completed":
if not estimate.can_complete(): if not estimate.can_complete():
messages.error(request, "Estimate is not ready for completion") messages.error(request, _("Estimate is not ready for completion"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
estimate.save() estimate.save()
messages.success(request, "Estimate marked as " + mark.upper()) messages.success(request, "Estimate marked as " + mark.upper())
@ -2957,7 +2981,7 @@ def PaymentCreateView(request, pk):
try: try:
if invoice: if invoice:
set_invoice_payment(dealer, entity, invoice, amount, payment_method) set_invoice_payment(dealer, entity, invoice, amount, payment_method)
elif bill: elif bill:
set_bill_payment(dealer, entity, bill, amount, payment_method) set_bill_payment(dealer, entity, bill, amount, payment_method)
messages.success(request, "Payment created successfully!") messages.success(request, "Payment created successfully!")
return redirect(redirect_url, pk=model.pk) return redirect(redirect_url, pk=model.pk)
@ -3094,6 +3118,7 @@ def lead_create(request):
return render(request, "crm/leads/lead_form.html", {"form": form}) return render(request, "crm/leads/lead_form.html", {"form": form})
class LeadUpdateView(UpdateView): class LeadUpdateView(UpdateView):
model = models.Lead model = models.Lead
form_class = forms.LeadForm form_class = forms.LeadForm
@ -3105,6 +3130,8 @@ class LeadUpdateView(UpdateView):
form.fields["id_car_model"].queryset = form.instance.id_car_make.carmodel_set.all() form.fields["id_car_model"].queryset = form.instance.id_car_make.carmodel_set.all()
return form return form
@login_required
def LeadDeleteView(request,pk): def LeadDeleteView(request,pk):
lead = get_object_or_404(models.Lead, pk=pk) lead = get_object_or_404(models.Lead, pk=pk)
lead.delete() lead.delete()
@ -3231,7 +3258,7 @@ def send_lead_email(request, pk):
request.POST.get("subject"), request.POST.get("subject"),
request.POST.get("message"), request.POST.get("message"),
) )
messages.success(request, "Email sent successfully!") messages.success(request, _("Email sent successfully!"))
return redirect("lead_list") return redirect("lead_list")
msg = f""" msg = f"""
السلام عليكم السلام عليكم
@ -3278,7 +3305,7 @@ def add_activity_to_lead(request, pk):
return render(request, "crm/add_activity.html", {"form": form, "lead": lead}) return render(request, "crm/add_activity.html", {"form": form, "lead": lead})
class OpportunityCreateView(CreateView): class OpportunityCreateView(CreateView, LoginRequiredMixin):
model = models.Opportunity model = models.Opportunity
form_class = forms.OpportunityForm form_class = forms.OpportunityForm
template_name = "crm/opportunities/opportunity_form.html" template_name = "crm/opportunities/opportunity_form.html"
@ -3309,7 +3336,7 @@ class OpportunityCreateView(CreateView):
return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk}) return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk})
class OpportunityUpdateView(UpdateView): class OpportunityUpdateView(UpdateView, LoginRequiredMixin):
model = models.Opportunity model = models.Opportunity
form_class = forms.OpportunityForm form_class = forms.OpportunityForm
template_name = "crm/opportunities/opportunity_form.html" template_name = "crm/opportunities/opportunity_form.html"
@ -3432,7 +3459,8 @@ class ItemServiceCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView)
def form_valid(self, form): def form_valid(self, form):
vat = models.VatRate.objects.get(is_active=True) vat = models.VatRate.objects.get(is_active=True)
form.instance.dealer = get_user_type(self.request.user.dealer) dealer = get_user_type(self.request)
form.instance.dealer = dealer
if form.instance.taxable: if form.instance.taxable:
form.instance.price = (form.instance.price * vat.rate) + form.instance.price form.instance.price = (form.instance.price * vat.rate) + form.instance.price
return super().form_valid(form) return super().form_valid(form)
@ -3448,13 +3476,14 @@ class ItemServiceUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView)
def form_valid(self, form): def form_valid(self, form):
vat = models.VatRate.objects.get(is_active=True) vat = models.VatRate.objects.get(is_active=True)
form.instance.dealer = get_user_type(self.request.user.dealer) dealer = get_user_type(self.request)
form.instance.dealer = dealer
if form.instance.taxable: if form.instance.taxable:
form.instance.price = (form.instance.price * vat.rate) + form.instance.price form.instance.price = (form.instance.price * vat.rate) + form.instance.price
return super().form_valid(form) return super().form_valid(form)
class ItemServiceListView(ListView): class ItemServiceListView(ListView, LoginRequiredMixin):
model = models.AdditionalServices model = models.AdditionalServices
template_name = "items/service/service_list.html" template_name = "items/service/service_list.html"
context_object_name = "services" context_object_name = "services"
@ -3465,7 +3494,7 @@ class ItemServiceListView(ListView):
return models.AdditionalServices.objects.filter(dealer=dealer).all() return models.AdditionalServices.objects.filter(dealer=dealer).all()
class ItemExpenseCreateView(CreateView): class ItemExpenseCreateView(CreateView, LoginRequiredMixin):
model = ItemModel model = ItemModel
form_class = ExpenseItemCreateForm form_class = ExpenseItemCreateForm
template_name = "items/expenses/expense_create.html" template_name = "items/expenses/expense_create.html"
@ -3484,7 +3513,7 @@ class ItemExpenseCreateView(CreateView):
return super().form_valid(form) return super().form_valid(form)
class ItemExpenseUpdateView(UpdateView): class ItemExpenseUpdateView(UpdateView, LoginRequiredMixin):
model = ItemModel model = ItemModel
form_class = ExpenseItemUpdateForm form_class = ExpenseItemUpdateForm
template_name = "items/expenses/expense_update.html" template_name = "items/expenses/expense_update.html"
@ -3503,7 +3532,7 @@ class ItemExpenseUpdateView(UpdateView):
return super().form_valid(form) return super().form_valid(form)
class ItemExpenseListView(ListView): class ItemExpenseListView(ListView, LoginRequiredMixin):
model = ItemModel model = ItemModel
template_name = "items/expenses/expenses_list.html" template_name = "items/expenses/expenses_list.html"
context_object_name = "expenses" context_object_name = "expenses"
@ -3514,7 +3543,7 @@ class ItemExpenseListView(ListView):
return dealer.entity.get_items_expenses() return dealer.entity.get_items_expenses()
class BillListView(ListView): class BillListView(ListView, LoginRequiredMixin):
model = BillModel model = BillModel
template_name = "ledger/bills/bill_list.html" template_name = "ledger/bills/bill_list.html"
context_object_name = "bills" context_object_name = "bills"
@ -3606,6 +3635,7 @@ class ApprovedBillModelView(LoginRequiredMixin, UpdateView):
return super().form_valid(form) return super().form_valid(form)
@login_required
def bill_mark_as_approved(request, pk): def bill_mark_as_approved(request, pk):
bill = get_object_or_404(BillModel, pk=pk) bill = get_object_or_404(BillModel, pk=pk)
if request.method == "POST": if request.method == "POST":
@ -3619,6 +3649,7 @@ def bill_mark_as_approved(request, pk):
return redirect("bill_detail", pk=bill.pk) return redirect("bill_detail", pk=bill.pk)
@login_required
def bill_mark_as_paid(request, pk): def bill_mark_as_paid(request, pk):
bill = get_object_or_404(BillModel, pk=pk) bill = get_object_or_404(BillModel, pk=pk)
if request.method == "POST": if request.method == "POST":
@ -3668,6 +3699,7 @@ def bill_mark_as_paid(request, pk):
# ) # )
# form.instance.ledger = ledger # form.instance.ledger = ledger
# return super().form_valid(form) # return super().form_valid(form)
@login_required @login_required
def bill_create(request): def bill_create(request):
dealer = get_user_type(request) dealer = get_user_type(request)
@ -3788,21 +3820,20 @@ def bill_create(request):
return render(request, "ledger/bills/bill_form.html", context) return render(request, "ledger/bills/bill_form.html", context)
@login_required
def BillDeleteView(request, pk): def BillDeleteView(request, pk):
bill = get_object_or_404(BillModel, pk=pk) bill = get_object_or_404(BillModel, pk=pk)
bill.delete() bill.delete()
return redirect("bill_list") return redirect("bill_list")
class SubscriptionPlans(ListView): class SubscriptionPlans(ListView, LoginRequiredMixin):
model = models.SubscriptionPlan model = models.SubscriptionPlan
template_name = "subscriptions/subscription_plan.html" template_name = "subscriptions/subscription_plan.html"
context_object_name = "plans" context_object_name = "plans"
# orders # orders
class OrderListView(ListView): class OrderListView(ListView):
model = models.SaleOrder model = models.SaleOrder
template_name = "sales/orders/order_list.html" template_name = "sales/orders/order_list.html"
@ -3810,6 +3841,7 @@ class OrderListView(ListView):
# email # email
@login_required
def send_email_view(request, pk): def send_email_view(request, pk):
estimate = get_object_or_404(EstimateModel, pk=pk) estimate = get_object_or_404(EstimateModel, pk=pk)
if request.method == "POST": if request.method == "POST":
@ -3817,7 +3849,7 @@ def send_email_view(request, pk):
# messages.error(request, "Estimate is not ready for review") # messages.error(request, "Estimate is not ready for review")
# return redirect("estimate_detail", pk=estimate.pk) # return redirect("estimate_detail", pk=estimate.pk)
if not estimate.get_itemtxs_data()[0]: if not estimate.get_itemtxs_data()[0]:
messages.error(request, "Estimate has no items") messages.error(request, _("Estimate has no items"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
send_email( send_email(
@ -3827,7 +3859,7 @@ def send_email_view(request, pk):
request.POST.get("message"), request.POST.get("message"),
) )
estimate.mark_as_review() estimate.mark_as_review()
messages.success(request, "Email sent successfully!") messages.success(request, _("Email sent successfully!"))
return redirect("estimate_detail", pk=estimate.pk) return redirect("estimate_detail", pk=estimate.pk)
link = reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk}) link = reverse_lazy("estimate_preview", kwargs={"pk": estimate.pk})
msg = f""" msg = f"""
@ -3879,34 +3911,7 @@ def custom_bad_request_view(request, exception=None):
return render(request, "errors/400.html", {}) return render(request, "errors/400.html", {})
# from django_ledger.io.io_core import get_localdate # BALANCE SHEET
# from django_ledger.views.mixins import (DjangoLedgerSecurityMixIn)
# from django.views.generic import RedirectView
from django_ledger.views.financial_statement import (
FiscalYearBalanceSheetView,
BaseIncomeStatementRedirectView,
FiscalYearIncomeStatementView,
BaseCashFlowStatementRedirectView,
FiscalYearCashFlowStatementView,
)
from django_ledger.views.entity import EntityModelDetailBaseView,EntityModelDetailHandlerView
from django.views.generic import DetailView, RedirectView
from django_ledger.io.io_core import get_localdate
from django_ledger.models import EntityModel, EntityUnitModel
from django_ledger.views.mixins import (
QuarterlyReportMixIn,
YearlyReportMixIn,
MonthlyReportMixIn,
DateReportMixIn,
DjangoLedgerSecurityMixIn,
EntityUnitMixIn,
BaseDateNavigationUrlMixIn,
PDFReportMixIn,
)
# BALANCE SHEET -----------
class BaseBalanceSheetRedirectView(DjangoLedgerSecurityMixIn, RedirectView): class BaseBalanceSheetRedirectView(DjangoLedgerSecurityMixIn, RedirectView):
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
year = get_localdate().year year = get_localdate().year
@ -3916,23 +3921,23 @@ class BaseBalanceSheetRedirectView(DjangoLedgerSecurityMixIn, RedirectView):
) )
class FiscalYearBalanceSheetViewBase(FiscalYearBalanceSheetView): class FiscalYearBalanceSheetViewBase(FiscalYearBalanceSheetView,DjangoLedgerSecurityMixIn):
template_name = "ledger/reports/balance_sheet.html" template_name = "ledger/reports/balance_sheet.html"
class QuarterlyBalanceSheetView(FiscalYearBalanceSheetViewBase, QuarterlyReportMixIn): class QuarterlyBalanceSheetView(FiscalYearBalanceSheetViewBase, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn):
""" """
Quarter Balance Sheet View. Quarter Balance Sheet View.
""" """
class MonthlyBalanceSheetView(FiscalYearBalanceSheetViewBase, MonthlyReportMixIn): class MonthlyBalanceSheetView(FiscalYearBalanceSheetViewBase, MonthlyReportMixIn, DjangoLedgerSecurityMixIn):
""" """
Monthly Balance Sheet View. Monthly Balance Sheet View.
""" """
class DateBalanceSheetView(FiscalYearBalanceSheetViewBase, DateReportMixIn): class DateBalanceSheetView(FiscalYearBalanceSheetViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn):
""" """
Date Balance Sheet View. Date Balance Sheet View.
""" """
@ -3941,7 +3946,7 @@ class DateBalanceSheetView(FiscalYearBalanceSheetViewBase, DateReportMixIn):
# Income Statement ----------- # Income Statement -----------
class BaseIncomeStatementRedirectViewBase(BaseIncomeStatementRedirectView): class BaseIncomeStatementRedirectViewBase(BaseIncomeStatementRedirectView, DjangoLedgerSecurityMixIn):
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
year = get_localdate().year year = get_localdate().year
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
@ -3950,25 +3955,25 @@ class BaseIncomeStatementRedirectViewBase(BaseIncomeStatementRedirectView):
) )
class FiscalYearIncomeStatementViewBase(FiscalYearIncomeStatementView): class FiscalYearIncomeStatementViewBase(FiscalYearIncomeStatementView, DjangoLedgerSecurityMixIn):
template_name = "ledger/reports/income_statement.html" template_name = "ledger/reports/income_statement.html"
class QuarterlyIncomeStatementView( class QuarterlyIncomeStatementView(
FiscalYearIncomeStatementViewBase, QuarterlyReportMixIn FiscalYearIncomeStatementViewBase, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn
): ):
""" """
Quarter Income Statement View. Quarter Income Statement View.
""" """
class MonthlyIncomeStatementView(FiscalYearIncomeStatementViewBase, MonthlyReportMixIn): class MonthlyIncomeStatementView(FiscalYearIncomeStatementViewBase, MonthlyReportMixIn, DjangoLedgerSecurityMixIn):
""" """
Monthly Income Statement View. Monthly Income Statement View.
""" """
class DateModelIncomeStatementView(FiscalYearIncomeStatementViewBase, DateReportMixIn): class DateModelIncomeStatementView(FiscalYearIncomeStatementViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn):
""" """
Date Income Statement View. Date Income Statement View.
""" """
@ -3977,7 +3982,7 @@ class DateModelIncomeStatementView(FiscalYearIncomeStatementViewBase, DateReport
# Cash Flow ----------- # Cash Flow -----------
class BaseCashFlowStatementRedirectViewBase(BaseCashFlowStatementRedirectView): class BaseCashFlowStatementRedirectViewBase(BaseCashFlowStatementRedirectView, DjangoLedgerSecurityMixIn):
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
year = get_localdate().year year = get_localdate().year
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
@ -3986,12 +3991,12 @@ class BaseCashFlowStatementRedirectViewBase(BaseCashFlowStatementRedirectView):
) )
class FiscalYearCashFlowStatementViewBase(FiscalYearCashFlowStatementView): class FiscalYearCashFlowStatementViewBase(FiscalYearCashFlowStatementView, DjangoLedgerSecurityMixIn):
template_name = "ledger/reports/cash_flow_statement.html" template_name = "ledger/reports/cash_flow_statement.html"
class QuarterlyCashFlowStatementView( class QuarterlyCashFlowStatementView(
FiscalYearCashFlowStatementViewBase, QuarterlyReportMixIn FiscalYearCashFlowStatementViewBase, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn
): ):
""" """
Quarter Cash Flow Statement View. Quarter Cash Flow Statement View.
@ -3999,14 +4004,14 @@ class QuarterlyCashFlowStatementView(
class MonthlyCashFlowStatementView( class MonthlyCashFlowStatementView(
FiscalYearCashFlowStatementViewBase, MonthlyReportMixIn FiscalYearCashFlowStatementViewBase, MonthlyReportMixIn, DjangoLedgerSecurityMixIn
): ):
""" """
Monthly Cash Flow Statement View. Monthly Cash Flow Statement View.
""" """
class DateCashFlowStatementView(FiscalYearCashFlowStatementViewBase, DateReportMixIn): class DateCashFlowStatementView(FiscalYearCashFlowStatementViewBase, DateReportMixIn, DjangoLedgerSecurityMixIn):
""" """
Date Cash Flow Statement View. Date Cash Flow Statement View.
""" """
@ -4014,7 +4019,7 @@ class DateCashFlowStatementView(FiscalYearCashFlowStatementViewBase, DateReportM
# Dashboard # Dashboard
class EntityModelDetailHandlerViewBase(EntityModelDetailHandlerView): class EntityModelDetailHandlerViewBase(EntityModelDetailHandlerView, DjangoLedgerSecurityMixIn):
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
loc_date = get_localdate() loc_date = get_localdate()
@ -4036,7 +4041,7 @@ class EntityModelDetailHandlerViewBase(EntityModelDetailHandlerView):
}) })
class EntityModelDetailBaseViewBase(EntityModelDetailBaseView): class EntityModelDetailBaseViewBase(EntityModelDetailBaseView, DjangoLedgerSecurityMixIn):
template_name = "ledger/reports/dashboard.html" template_name = "ledger/reports/dashboard.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -4066,31 +4071,30 @@ class EntityModelDetailBaseViewBase(EntityModelDetailBaseView):
return context return context
class FiscalYearEntityModelDashboardView(EntityModelDetailBaseViewBase): class FiscalYearEntityModelDashboardView(EntityModelDetailBaseViewBase, DjangoLedgerSecurityMixIn):
""" """
Entity Fiscal Year Dashboard View. Entity Fiscal Year Dashboard View.
""" """
class QuarterlyEntityDashboardView(FiscalYearEntityModelDashboardView, QuarterlyReportMixIn): class QuarterlyEntityDashboardView(FiscalYearEntityModelDashboardView, QuarterlyReportMixIn, DjangoLedgerSecurityMixIn):
""" """
Entity Quarterly Dashboard View. Entity Quarterly Dashboard View.
""" """
class MonthlyEntityDashboardView(FiscalYearEntityModelDashboardView, MonthlyReportMixIn): class MonthlyEntityDashboardView(FiscalYearEntityModelDashboardView, MonthlyReportMixIn, DjangoLedgerSecurityMixIn):
""" """
Monthly Entity Dashboard View. Monthly Entity Dashboard View.
""" """
class DateEntityDashboardView(FiscalYearEntityModelDashboardView, DateReportMixIn): class DateEntityDashboardView(FiscalYearEntityModelDashboardView, DateReportMixIn, DjangoLedgerSecurityMixIn):
""" """
Date-specific Entity Dashboard View. Date-specific Entity Dashboard View.
""" """
class PayableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): class PayableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View):
http_method_names = ['get'] http_method_names = ['get']
@ -4120,6 +4124,7 @@ class PayableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View):
'message': 'Unauthorized' 'message': 'Unauthorized'
}, status=401) }, status=401)
class ReceivableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): class ReceivableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View):
http_method_names = ['get'] http_method_names = ['get']
@ -4150,6 +4155,7 @@ class ReceivableNetAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View):
'message': 'Unauthorized' 'message': 'Unauthorized'
}, status=401) }, status=401)
class PnLAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View): class PnLAPIView(DjangoLedgerSecurityMixIn, EntityUnitMixIn, View):
http_method_names = ['get'] http_method_names = ['get']

140
scripts/dsrpipe.py Normal file
View File

@ -0,0 +1,140 @@
"""
title: Deepseek R1 Reasoner and Chat with Realtime Thinking Preview
authors: Ethan Copping
author_url: https://github.com/CoppingEthan
version: 0.3.0
required_open_webui_version: 0.5.5
license: MIT
# Acknowledgments
Code used from MCode-Team & Zgccrui
"""
import json
import httpx
from typing import AsyncGenerator, Callable, Awaitable
from pydantic import BaseModel, Field
class Pipe:
class Valves(BaseModel):
DEEPSEEK_API_BASE_URL: str = Field(
default="https://api.deepseek.com/v1", description="Base API endpoint URL"
)
DEEPSEEK_API_KEY: str = Field(
default="", description="Authentication key for API access"
)
def __init__(self):
self.valves = self.Valves()
self.thinking = -1
self._emitter = None
self.data_prefix = "data: "
def pipes(self):
try:
headers = {"Authorization": f"Bearer {self.valves.DEEPSEEK_API_KEY}"}
resp = httpx.get(
f"{self.valves.DEEPSEEK_API_BASE_URL}/models",
headers=headers,
timeout=10,
)
if resp.status_code == 200:
return [
{"id": m["id"], "name": m["id"]}
for m in resp.json().get("data", [])
]
except Exception:
pass
return [
{"id": "deepseek-chat", "name": "deepseek-chat"},
{"id": "deepseek-reasoner", "name": "deepseek-reasoner"},
]
async def pipe(
self, body: dict, __event_emitter__: Callable[[dict], Awaitable[None]] = None
) -> AsyncGenerator[str, None]:
self.thinking = -1
self._emitter = __event_emitter__
if not self.valves.DEEPSEEK_API_KEY:
yield json.dumps({"error": "Missing API credentials"})
return
req_headers = {
"Authorization": f"Bearer {self.valves.DEEPSEEK_API_KEY}",
"Content-Type": "application/json",
}
try:
request_data = body.copy()
model_id = request_data["model"].split(".", 1)[-1]
request_data["model"] = model_id
is_reasoner = "reasoner" in model_id.lower()
messages = request_data["messages"]
for i in reversed(range(1, len(messages))):
if messages[i - 1]["role"] == messages[i]["role"]:
alt_role = (
"user" if messages[i]["role"] == "assistant" else "assistant"
)
messages.insert(
i, {"role": alt_role, "content": "[Unfinished thinking]"}
)
async with httpx.AsyncClient(http2=True) as client:
async with client.stream(
"POST",
f"{self.valves.DEEPSEEK_API_BASE_URL}/chat/completions",
json=request_data,
headers=req_headers,
timeout=20,
) as resp:
if resp.status_code != 200:
error_content = (await resp.aread()).decode()[:200]
yield json.dumps(
{"error": f"API error {resp.status_code}: {error_content}"}
)
return
async for line in resp.aiter_lines():
if not line.startswith(self.data_prefix):
continue
stream_data = json.loads(line[6:])
choice = stream_data.get("choices", [{}])[0]
if choice.get("finish_reason"):
return
delta = choice.get("delta", {})
if is_reasoner:
state_marker = self._handle_state(delta)
if state_marker:
yield state_marker
if state_marker == "<think>":
yield "\n"
content = delta.get("reasoning_content", "") or delta.get(
"content", ""
)
if content:
yield content
else:
content = delta.get("content", "")
if content:
yield content
except Exception as e:
yield json.dumps({"error": f"{type(e).__name__}: {str(e)}"})
def _handle_state(self, delta: dict) -> str:
if self.thinking == -1 and delta.get("reasoning_content"):
self.thinking = 0
return "<think>"
if self.thinking == 0 and not delta.get("reasoning_content"):
self.thinking = 1
return "\n</think>\n\n"
return ""

View File

@ -7,16 +7,16 @@
<h2 class="mb-0">{{ _("Opportunity details")}}</h2> <h2 class="mb-0">{{ _("Opportunity details")}}</h2>
</div> </div>
<div class="col-12 col-md-auto d-flex"> <div class="col-12 col-md-auto d-flex">
<button class="btn btn-phoenix-secondary px-3 px-sm-5 me-2"><span class="fa-solid fa-edit me-sm-2"></span><span class="d-none d-sm-inline">Edit</span></button> <button class="btn btn-phoenix-secondary px-3 px-sm-5 me-2"><span class="fa-solid fa-edit me-sm-2"></span><span class="d-none d-sm-inline">{{ _("Edit") }}</span></button>
<button class="btn btn-phoenix-danger me-2"><span class="fa-solid fa-trash me-2"></span><span>Delete</span></button> <button class="btn btn-phoenix-danger me-2"><span class="fa-solid fa-trash me-2"></span><span>{{ _("Delete") }}</span></button>
<div> <div>
<button class="btn px-3 btn-phoenix-secondary" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fa-solid fa-ellipsis"></span></button> <button class="btn px-3 btn-phoenix-secondary" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fa-solid fa-ellipsis"></span></button>
<ul class="dropdown-menu dropdown-menu-end p-0" style="z-index: 9999;"> <ul class="dropdown-menu dropdown-menu-end p-0" style="z-index: 9999;">
<li> <li>
{% if opportunity.estimate %} {% if opportunity.estimate %}
<a class="dropdown-item" href="{% url 'estimate_detail' opportunity.estimate.pk %}">View Estimate</a> <a class="dropdown-item" href="{% url 'estimate_detail' opportunity.estimate.pk %}">{{ _("View Quotation")}}</a>
{% else %} {% else %}
<a class="dropdown-item" href="{% url 'estimate_create_from_opportunity' opportunity.pk %}">Create Estimate</a> <a class="dropdown-item" href="{% url 'estimate_create_from_opportunity' opportunity.pk %}">{{ _("Create Quotation")}}</a>
{% endif %} {% endif %}
</li> </li>
<li><a class="dropdown-item" href="">Report</a></li> <li><a class="dropdown-item" href="">Report</a></li>
@ -58,8 +58,8 @@
<div class="card-body p-0"> <div class="card-body p-0">
<div class="mx-3"> <div class="mx-3">
<div class="text-end"> <div class="text-end">
<button class="btn btn-link text-danger" type="button">Cancel</button> <button class="btn btn-link text-danger" type="button">{{ _("Cancel") }}</button>
<button class="btn btn-sm btn-primary px-5" type="button">Save</button> <button class="btn btn-sm btn-primary px-5" type="button">{{ _("Save") }}</button>
</div> </div>
</div> </div>
</div> </div>
@ -83,18 +83,20 @@
</div> </div>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h4 class="mb-5">Others Information</h4> <h4 class="mb-5">{{ _("Other Information")}}</h4>
<div class="row g-3"> <div class="row g-3">
<div class="col-12"> <div class="col-12">
<div class="mb-4"> <div class="mb-4">
<div class="d-flex flex-wrap justify-content-between mb-2"> <div class="d-flex flex-wrap justify-content-between mb-2">
<h5 class="mb-0 text-body-highlight me-2">Status</h5><a href="#" class="fw-bold fs-9" hx-on:click="htmx.find('#id_status').disabled = !htmx.find('#id_status').disabled;this.text = htmx.find('#id_status').disabled ? 'Update Status' : 'Cancel'">Update Status</a> <h5 class="mb-0 text-body-highlight me-2">{{ _("Status") }}</h5>
<a href="#" class="fw-bold fs-9" hx-on:click="htmx.find('#id_status').disabled = !htmx.find('#id_status').disabled;this.text = htmx.find('#id_status').disabled ? 'Update Status' : 'Cancel'">{{ _("Update Status")}}</a>
</div> </div>
{{status_form.status}} {{status_form.status}}
</div> </div>
<div class="mb-4"> <div class="mb-4">
<div class="d-flex flex-wrap justify-content-between mb-2"> <div class="d-flex flex-wrap justify-content-between mb-2">
<h5 class="mb-0 text-body-highlight me-2">Stage</h5><a href="#" class="fw-bold fs-9" hx-on:click="htmx.find('#id_stage').disabled = !htmx.find('#id_stage').disabled;this.text = htmx.find('#id_stage').disabled ? 'Update Stage' : 'Cancel'">Update Stage</a> <h5 class="mb-0 text-body-highlight me-2">{{ _("Stage") }}</h5>
<a href="#" class="fw-bold fs-9" hx-on:click="htmx.find('#id_stage').disabled = !htmx.find('#id_stage').disabled;this.text = htmx.find('#id_stage').disabled ? 'Update Stage' : 'Cancel'">{{ _("Update Stage")}}</a>
</div> </div>
{{status_form.stage}} {{status_form.stage}}
</div> </div>
@ -112,10 +114,10 @@
<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"> <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">
<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" data-feather="dollar-sign" style="width:24px; height:24px"></span></div> <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" data-feather="dollar-sign" style="width:24px; height:24px"></span></div>
<div> <div>
<p class="fw-bold mb-1">{{ _("Estimated Amount") }}</p> <p class="fw-bold mb-1">{{ _("Quotation Amount") }}</p>
<h4 class="fw-bolder text-nowrap"> <h4 class="fw-bolder text-nowrap">
{% if opportunity.estimate %} {% if opportunity.estimate %}
{{ opportunity.estimate.get_cost_estimate }} {{ opportunity.estimate.get_invoiced_amount.invoice_amount_paid__sum }}
{% endif %} {% endif %}
</h4> </h4>
</div> </div>
@ -495,18 +497,7 @@
<img class="rounded-circle " src="../../assets/img/team/11.webp" alt="" /> <img class="rounded-circle " src="../../assets/img/team/11.webp" alt="" />
</div> </div>
<div class="avatar avatar-s rounded-circle">
<img class="rounded-circle " src="../../assets/img/team/26.webp" alt="" />
</div>
<div class="avatar avatar-s rounded-circle">
<img class="rounded-circle " src="../../assets/img/team/33.webp" alt="" />
</div>
<div class="avatar avatar-s rounded-circle">
<img class="rounded-circle " src="../../assets/img/team/30.webp" alt="" />
</div>
<div class="avatar avatar-s rounded-circle"> <div class="avatar avatar-s rounded-circle">
<div class="avatar-name rounded-circle "><span>+1</span></div> <div class="avatar-name rounded-circle "><span>+1</span></div>
</div> </div>
@ -531,18 +522,6 @@
<div class="avatar avatar-s rounded-circle"> <div class="avatar avatar-s rounded-circle">
<div class="avatar-name rounded-circle"><span>R</span></div> <div class="avatar-name rounded-circle"><span>R</span></div>
</div> </div>
<div class="avatar avatar-s rounded-circle">
<img class="rounded-circle " src="../../assets/img/team/12.webp" alt="" />
</div>
<div class="avatar avatar-s rounded-circle">
<img class="rounded-circle " src="../../assets/img/team/28.webp" alt="" />
</div>
<div class="avatar avatar-s rounded-circle">
<img class="rounded-circle " src="../../assets/img/team/22.webp" alt="" />
</div>
<div class="avatar avatar-s rounded-circle"> <div class="avatar avatar-s rounded-circle">
<div class="avatar-name rounded-circle "><span>+2</span></div> <div class="avatar-name rounded-circle "><span>+2</span></div>
</div> </div>
@ -565,23 +544,7 @@
</div> </div>
<div class="avatar-group avatar-group-dense"> <div class="avatar-group avatar-group-dense">
<div class="avatar avatar-s rounded-circle"> <div class="avatar avatar-s rounded-circle">
<img class="rounded-circle " src="../../assets/img/team/13.webp" alt="" /> <img class="rounded-circle " src="{% static 'images/team/13.webp' %}" alt="" />
</div>
<div class="avatar avatar-s rounded-circle">
<img class="rounded-circle " src="../../assets/img/team/24.webp" alt="" />
</div>
<div class="avatar avatar-s rounded-circle">
<img class="rounded-circle " src="../../assets/img/team/62.webp" alt="" />
</div>
<div class="avatar avatar-s rounded-circle">
<img class="rounded-circle " src="../../assets/img/team/34.webp" alt="" />
</div>
<div class="avatar avatar-s rounded-circle">
<div class="avatar-name rounded-circle "><span>+4</span></div>
</div> </div>
</div> </div>
</div> </div>
@ -831,7 +794,7 @@
</div> </div>
</td> </td>
<td class="name align-middle white-space-nowrap py-2 ps-0"><a class="d-flex align-items-center text-body-highlight" href="#!"> <td class="name align-middle white-space-nowrap py-2 ps-0"><a class="d-flex align-items-center text-body-highlight" href="#!">
<div class="avatar avatar-m me-3 status-online"><img class="rounded-circle" src="../../assets/img/team/35.webp" alt="" /> <div class="avatar avatar-m me-3 status-online"><img class="rounded-circle" src="{% static 'images/team/35.webp' %}" alt="" />
</div> </div>
<h6 class="mb-0 text-body-highlight fw-bold">Ansolo Lazinatov</h6> <h6 class="mb-0 text-body-highlight fw-bold">Ansolo Lazinatov</h6>
</a></td> </a></td>
@ -850,136 +813,7 @@
</div> </div>
</td> </td>
</tr> </tr>
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="fs-9 align-middle px-0 py-3">
<div class="form-check mb-0 fs-8">
<input class="form-check-input" type="checkbox" data-bulk-select-row='{"Name":{"avatar":"/team/9.webp","name":"Jackson Pollock","status":"offline"},"description":"Based on emails sent rate, the top 10 users","date":"Mar 27, 2021","creatBy":"Jackson Pollock","lastActivity":{"iconColor":"text-body-quaternary","label":"6 hours ago"}}' />
</div>
</td>
<td class="name align-middle white-space-nowrap py-2 ps-0"><a class="d-flex align-items-center text-body-highlight" href="#!">
<div class="avatar avatar-m me-3 status-offline"><img class="rounded-circle" src="../../assets/img/team/9.webp" alt="" />
</div>
<h6 class="mb-0 text-body-highlight fw-bold">Jackson Pollock</h6>
</a></td>
<td class="description align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2 pe-6">Based on emails sent rate, the top 10 users</td>
<td class="create_date text-end align-middle white-space-nowrap text-body py-2">Mar 27, 2021</td>
<td class="create_by align-middle white-space-nowrap fw-semibold text-body-highlight">Jackson Pollock</td>
<td class="last_activity align-middle text-center py-2">
<div class="d-flex align-items-center flex-1"><span class="fa-solid fa-clock me-1 text-body-quaternary" data-fa-transform="shrink-2 up-1"></span><span class="fw-bold fs-9 text-body">6 hours ago</span></div>
</td>
<td class="align-middle text-end white-space-nowrap pe-0 action py-2">
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">View</a><a class="dropdown-item" href="#!">Export</a>
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
</div>
</div>
</td>
</tr>
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="fs-9 align-middle px-0 py-3">
<div class="form-check mb-0 fs-8">
<input class="form-check-input" type="checkbox" data-bulk-select-row='{"Name":{"avatar":"/team/35.webp","name":"Ansolo Lazinatov","status":"online"},"description":"Based on the percentage of recipients","date":"Jun 24, 2021","creatBy":"Ansolo Lazinarov","lastActivity":{"iconColor":"text-success","label":"Active"}}' />
</div>
</td>
<td class="name align-middle white-space-nowrap py-2 ps-0"><a class="d-flex align-items-center text-body-highlight" href="#!">
<div class="avatar avatar-m me-3 status-online"><img class="rounded-circle" src="../../assets/img/team/35.webp" alt="" />
</div>
<h6 class="mb-0 text-body-highlight fw-bold">Ansolo Lazinatov</h6>
</a></td>
<td class="description align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2 pe-6">Based on the percentage of recipients</td>
<td class="create_date text-end align-middle white-space-nowrap text-body py-2">Jun 24, 2021</td>
<td class="create_by align-middle white-space-nowrap fw-semibold text-body-highlight">Ansolo Lazinarov</td>
<td class="last_activity align-middle text-center py-2">
<div class="d-flex align-items-center flex-1"><span class="fa-solid fa-clock me-1 text-success" data-fa-transform="shrink-2 up-1"></span><span class="fw-bold fs-9 text-body">Active</span></div>
</td>
<td class="align-middle text-end white-space-nowrap pe-0 action py-2">
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">View</a><a class="dropdown-item" href="#!">Export</a>
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
</div>
</div>
</td>
</tr>
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="fs-9 align-middle px-0 py-3">
<div class="form-check mb-0 fs-8">
<input class="form-check-input" type="checkbox" data-bulk-select-row='{"Name":{"avatar":"/team/9.webp","name":"Jackson Pollock","status":"offline"},"description":"Obtaining leads today","date":"May 19, 2024","creatBy":"Jackson Pollock","lastActivity":{"iconColor":"text-body-quaternary","label":"6 hours ago"}}' />
</div>
</td>
<td class="name align-middle white-space-nowrap py-2 ps-0"><a class="d-flex align-items-center text-body-highlight" href="#!">
<div class="avatar avatar-m me-3 status-offline"><img class="rounded-circle" src="../../assets/img/team/9.webp" alt="" />
</div>
<h6 class="mb-0 text-body-highlight fw-bold">Jackson Pollock</h6>
</a></td>
<td class="description align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2 pe-6">Obtaining leads today</td>
<td class="create_date text-end align-middle white-space-nowrap text-body py-2">May 19, 2024</td>
<td class="create_by align-middle white-space-nowrap fw-semibold text-body-highlight">Jackson Pollock</td>
<td class="last_activity align-middle text-center py-2">
<div class="d-flex align-items-center flex-1"><span class="fa-solid fa-clock me-1 text-body-quaternary" data-fa-transform="shrink-2 up-1"></span><span class="fw-bold fs-9 text-body">6 hours ago</span></div>
</td>
<td class="align-middle text-end white-space-nowrap pe-0 action py-2">
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">View</a><a class="dropdown-item" href="#!">Export</a>
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
</div>
</div>
</td>
</tr>
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="fs-9 align-middle px-0 py-3">
<div class="form-check mb-0 fs-8">
<input class="form-check-input" type="checkbox" data-bulk-select-row='{"Name":{"avatar":"/team/35.webp","name":"Ansolo Lazinatov","status":"online"},"description":"Sums up the many phases of new and existing businesses.","date":"Aug 19, 2024","creatBy":"Ansolo Lazinarov","lastActivity":{"iconColor":"text-success","label":"Active"}}' />
</div>
</td>
<td class="name align-middle white-space-nowrap py-2 ps-0"><a class="d-flex align-items-center text-body-highlight" href="#!">
<div class="avatar avatar-m me-3 status-online"><img class="rounded-circle" src="../../assets/img/team/35.webp" alt="" />
</div>
<h6 class="mb-0 text-body-highlight fw-bold">Ansolo Lazinatov</h6>
</a></td>
<td class="description align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2 pe-6">Sums up the many phases of new and existing businesses.</td>
<td class="create_date text-end align-middle white-space-nowrap text-body py-2">Aug 19, 2024</td>
<td class="create_by align-middle white-space-nowrap fw-semibold text-body-highlight">Ansolo Lazinarov</td>
<td class="last_activity align-middle text-center py-2">
<div class="d-flex align-items-center flex-1"><span class="fa-solid fa-clock me-1 text-success" data-fa-transform="shrink-2 up-1"></span><span class="fw-bold fs-9 text-body">Active</span></div>
</td>
<td class="align-middle text-end white-space-nowrap pe-0 action py-2">
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">View</a><a class="dropdown-item" href="#!">Export</a>
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
</div>
</div>
</td>
</tr>
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="fs-9 align-middle px-0 py-3">
<div class="form-check mb-0 fs-8">
<input class="form-check-input" type="checkbox" data-bulk-select-row='{"Name":{"avatar":"/team/35.webp","name":"Ansolo Lazinatov","status":"online"},"description":"Purchasing-Related Vendors","date":"Aug 19, 2024","creatBy":"Ansolo Lazinarov","lastActivity":{"iconColor":"text-success","label":"Active"}}' />
</div>
</td>
<td class="name align-middle white-space-nowrap py-2 ps-0"><a class="d-flex align-items-center text-body-highlight" href="#!">
<div class="avatar avatar-m me-3 status-online"><img class="rounded-circle" src="../../assets/img/team/35.webp" alt="" />
</div>
<h6 class="mb-0 text-body-highlight fw-bold">Ansolo Lazinatov</h6>
</a></td>
<td class="description align-middle white-space-nowrap text-start fw-bold text-body-tertiary py-2 pe-6">Purchasing-Related Vendors</td>
<td class="create_date text-end align-middle white-space-nowrap text-body py-2">Aug 19, 2024</td>
<td class="create_by align-middle white-space-nowrap fw-semibold text-body-highlight">Ansolo Lazinarov</td>
<td class="last_activity align-middle text-center py-2">
<div class="d-flex align-items-center flex-1"><span class="fa-solid fa-clock me-1 text-success" data-fa-transform="shrink-2 up-1"></span><span class="fw-bold fs-9 text-body">Active</span></div>
</td>
<td class="align-middle text-end white-space-nowrap pe-0 action py-2">
<div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">View</a><a class="dropdown-item" href="#!">Export</a>
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a>
</div>
</div>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -1354,7 +1188,7 @@
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">Edit</a><a class="dropdown-item text-danger" href="#!">Delete</a><a class="dropdown-item" href="#!">Download</a><a class="dropdown-item" href="#!">Report abuse</a></div> <div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">Edit</a><a class="dropdown-item text-danger" href="#!">Delete</a><a class="dropdown-item" href="#!">Download</a><a class="dropdown-item" href="#!">Report abuse</a></div>
</div> </div>
</div> </div>
<p class="fs-9 text-body-tertiary mb-3"><span>768kB</span><span class="text-body-quaternary mx-1">| </span><a href="#!">Shantinan Mekalan </a><span class="text-body-quaternary mx-1">| </span><span class="text-nowrap">21st Dec, 12:56 PM</span></p><img class="rounded-2" src="../../assets/img/generic/40.png" alt="" /> <p class="fs-9 text-body-tertiary mb-3"><span>768kB</span><span class="text-body-quaternary mx-1">| </span><a href="#!">Shantinan Mekalan </a><span class="text-body-quaternary mx-1">| </span><span class="text-nowrap">21st Dec, 12:56 PM</span></p><img class="rounded-2" src="{% static 'images/generic/40.png' %}" alt="" />
</div> </div>
<div class="border-top border-dashed py-4"> <div class="border-top border-dashed py-4">
<div class="d-flex flex-between-center"> <div class="d-flex flex-between-center">

View File

@ -1,28 +1,30 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% block title %}{{ _("View Estimate") }}{% endblock title %} {% block title %}{{ _("View Quotation") }}{% endblock title %}
{% block content %} {% block content %}
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true"> <div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
<div class="modal-content"> <div class="modal-content ">
<div class="modal-header bg-primary"> <div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
<h5 class="modal-title text-light" id="confirmModalLabel">{% trans 'Confirm' %}</h5> <h5 class="mmb-0 me-2 text-warning-dark" id="confirmModalLabel">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <i class="fas fa-exclamation-circle text-warning-dark ms-2"></i>
{% trans 'Confirm' %}</h5>
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close">
<span class="fas fa-times"></span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
{% trans 'Are you sure' %} {% trans 'Are you sure' %}
<div class="modal-footer"> <div class="modal-footer flex justify-content-center border-top-0">
<button type="button" <form id="confirmForm" method="POST" action="{% url 'estimate_mark_as' estimate.pk %}?mark=approved" class="form">
class="btn btn-sm btn-danger"
data-bs-dismiss="modal">
{% trans 'No' %}
</button>
<form id="confirmForm" method="POST" action="{% url 'estimate_mark_as' estimate.pk %}?mark=approved" class="d-inline">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-success btn-sm">{% trans "Yes" %}</button> <div class="container-fluid m-0 p-0">
</form> <button type="button" class="btn btn-danger btn-sm" data-bs-dismiss="modal">{% trans 'No' %}</button>
<button type="submit" class="btn btn-success btn-sm">{% trans "Yes" %}</button>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
@ -34,10 +36,10 @@
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top"> <section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
<div class="row-small mt-3"> <div class="row-small mt-3">
<div class="d-flex justify-content-between align-items-end mb-4 mx-3"> <div class="d-flex justify-content-between align-items-end mb-4 mx-3">
<h2 class="mb-0">{% trans 'Estimate' %}</h2> <h2 class="mb-0">{% trans 'Quotation' %}</h2>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
{% if estimate.status == 'draft' %} {% if estimate.status == 'draft' %}
<a href="{% url 'send_email' estimate.pk %}" class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Estimate' %}</span></a> <a href="{% url 'send_email' estimate.pk %}" class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a>
<button id="mark_as_sent_estimate" class="btn btn-phoenix-secondary" onclick="setFormAction('review')" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">{% trans 'Mark As Sent' %}</span></button> <button id="mark_as_sent_estimate" class="btn btn-phoenix-secondary" onclick="setFormAction('review')" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">{% trans 'Mark As Sent' %}</span></button>
{% elif estimate.status == 'in_review' %} {% elif estimate.status == 'in_review' %}
<button id="accept_estimate" onclick="setFormAction('approved')" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">{% trans 'Mark As Accept' %}</span></button> <button id="accept_estimate" onclick="setFormAction('approved')" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">{% trans 'Mark As Accept' %}</span></button>
@ -60,7 +62,7 @@
<div class="col-12 col-sm-6 col-lg-12"> <div class="col-12 col-sm-6 col-lg-12">
<div class="row align-items-center g-0"> <div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5"> <div class="col-auto col-lg-6 col-xl-5">
<h6 class="mb-0 me-3">{% trans 'Estimate Number' %} :</h6> <h6 class="mb-0 me-3">{% trans 'Quotation Number' %} :</h6>
</div> </div>
<div class="col-auto col-lg-6 col-xl-7"> <div class="col-auto col-lg-6 col-xl-7">
<p class="fs-9 text-body-secondary fw-semibold mb-0">#{{estimate.estimate_number}}</p> <p class="fs-9 text-body-secondary fw-semibold mb-0">#{{estimate.estimate_number}}</p>
@ -70,7 +72,7 @@
<div class="col-12 col-sm-6 col-lg-12"> <div class="col-12 col-sm-6 col-lg-12">
<div class="row align-items-center g-0"> <div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5"> <div class="col-auto col-lg-6 col-xl-5">
<h6 class="me-3">{% trans 'Estimate Date' %} :</h6> <h6 class="me-3">{% trans 'Quotation Date' %} :</h6>
</div> </div>
<div class="col-auto col-lg-6 col-xl-7"> <div class="col-auto col-lg-6 col-xl-7">
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{estimate.created}}</p> <p class="fs-9 text-body-secondary fw-semibold mb-0">{{estimate.created}}</p>
@ -94,7 +96,7 @@
<div class="col-12 col-sm-6 col-lg-4"> <div class="col-12 col-sm-6 col-lg-4">
<div class="row g-4"> <div class="row g-4">
<div class="col-12 col-lg-6"> <div class="col-12 col-lg-6">
<h6 class="mb-2"> {% trans "Estimate Status" %} :</h6> <h6 class="mb-2"> {% trans "Quotation Status" %} :</h6>
<div class="fs-9 text-body-secondary fw-semibold mb-0"> <div class="fs-9 text-body-secondary fw-semibold mb-0">
{% if estimate.status == 'draft' %} {% if estimate.status == 'draft' %}
<span class="badge text-bg-warning">{% trans "Draft" %}</span> <span class="badge text-bg-warning">{% trans "Draft" %}</span>

View File

@ -2,7 +2,7 @@
{% load crispy_forms_filters %} {% load crispy_forms_filters %}
{% load i18n static %} {% load i18n static %}
{% block title %}{{ _("Quotations") }}{% endblock title %} {% block title %}{{ _("Quotation") }}{% endblock title %}
{% block content %} {% block content %}
<div class="card email-content"> <div class="card email-content">
@ -30,7 +30,5 @@
</form> </form>
</div> </div>
</div> </div>
</div>
</div>
{% endblock content %} {% endblock content %}

View File

@ -1,21 +1,24 @@
{% load i18n custom_filters num2words_tags %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ar" dir="rtl"> <html lang="ar" dir="rtl">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>عرض سعر السيارة - Tenhall</title> <title>عرض سعر / Quotation</title>
<style> <style>
body { body {
font-family: 'Arial', sans-serif; font-family: Roboto, sans-serif;
line-height: 1.6; line-height: 1.6;
background-color: #f9f9f9; background-color: #f8f9fa;
color: #333; color: #333;
margin: 0; margin: 0;
padding: 20px; padding: 20px;
direction: rtl;
text-align: right;
} }
.row { .container {
max-width: 800px; max-width: 800px;
margin: 0 auto; margin: auto;
background-color: #fff; background-color: #fff;
padding: 30px; padding: 30px;
border: 1px solid #ddd; border: 1px solid #ddd;
@ -25,21 +28,22 @@
h1 { h1 {
text-align: center; text-align: center;
color: #2c3e50; color: #2c3e50;
margin-bottom: 20px;
font-size: 20px; font-size: 20px;
margin-bottom: 20px;
} }
p { p, label {
margin: 10px 0;
font-size: 14px; font-size: 14px;
margin-bottom: 5px;
} }
.row { input[type="text"] {
display: flex; border: none;
align-items: center; border-bottom: 1px solid #333;
gap: 10px; /* Space between elements */ text-align: start;
margin-bottom: 10px; width: 100%;
} font-size: 14px;
.row input { box-sizing: border-box;
flex: 1; /* Input fields take remaining space */ direction: rtl;
background: transparent;
} }
table { table {
width: 100%; width: 100%;
@ -49,39 +53,24 @@
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
table-layout: fixed;
} }
th, td { th, td {
padding: 10px; padding: 10px;
text-align: center; text-align: center;
border: 1px solid #ddd; border: 1px solid #ddd;
word-wrap: break-word;
font-size: 14px; font-size: 14px;
} }
th { th {
background-color: #2c3e50; background-color: #2c3e50;
color: #fff; color: #fff;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #f8f9fa;
} }
.footer { .footer {
margin-top: 30px; text-align: center;
font-size: 12px; font-size: 12px;
color: #777; color: #777;
text-align: center;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
padding-top: 20px; padding-top: 20px;
} }
.header {
text-align: center;
margin-bottom: 30px;
}
.header img {
width: 100px;
height: auto;
}
.signature-section { .signature-section {
margin-top: 30px; margin-top: 30px;
padding: 15px; padding: 15px;
@ -97,137 +86,96 @@
font-weight: bold; font-weight: bold;
color: #2c3e50; color: #2c3e50;
} }
.gap {
display: inline-block;
width: 150px;
border-bottom: 1px solid #333;
margin: 0 5px;
}
input[type="text"] {
border: none;
border-bottom: 1px solid #333;
text-align: start; /* Align text to the start (left for RTL) */
width: 100%;
margin: 0 5px;
box-sizing: border-box;
font-size: 14px;
direction: rtl; /* Ensure RTL direction for input */
}
</style> </style>
</head> </head>
<body> <body>
<div class="row"> <div class="container">
<div class="header"> <h1>عرض سعر السيارة - Tenhal</h1>
<h1>عرض سعر السيارة - Tenhal</h1>
</div>
<div class="row"> <p>المكرمين/</p>
<p>المكرمين/</p> <input type="text">
<input type="text">
</div>
<p>السلام عليكم و رحمة الله و بركاته،</p> <p>السلام عليكم ورحمة الله وبركاته،</p>
<p>بناء على طلبكم، نورد لكم عرض سعر للسيارة وهو يعد إيجابا منا بالبيع:</p> <p>بناءً على طلبكم، نورد لكم عرض سعر للسيارة وهو يعد إيجابًا منا بالبيع:</p>
<table> <table>
<tr> <thead>
<th style="width: 30%;">نوع السيارة</th> <tr>
<th style="width: 30%;">اللون الخارجي</th> <th>نوع السيارة</th>
<th style="width: 30%;">اللون الداخلي</th> <th>اللون الخارجي</th>
</tr> <th>اللون الداخلي</th>
<tr> <th>السعر</th>
<th>ملاحظات</th>
</tr>
</thead>
<tbody>
{% for car in cars %} {% for car in cars %}
<td><input type="text" style="text-align: center" value="{{ car.vin }}" disabled></td> <tr>
<td><input type="text" style="background-color: rgb({{ car.colors.first.interior.rgb }})" disabled></td> <td>{{ car.year }} - {{ car.id_car_make.name }} - {{ car.id_car_model.name }} - {{ car.id_car_trim.name }}</td>
<td><input type="text" style="background-color: rgb({{ car.colors.first.exterior.rgb }})" disabled></td> <td>{{ car.colors.first.exterior.name }}</td>
<td>{{ car.colors.first.interior.name }}</td>
<td>{{ car.finances.selling_price }}</td>
<td>{{ car.get_specifications }}</td>
</tr>
{% endfor %} {% endfor %}
</tr> </tbody>
</table> </table>
<div class="row"> <p>حمولة المركبة )<input type="text">) سنة الصنع (<input type="text">) (جديد / مستعملة) كلم/ميل</p>
<p>حمولة المركبة (</p>
<input type="text">
<p>) سنة الصنع (</p>
<input type="text">
<p>) (جديد / مستعملة) كلم/ميل</p>
</div>
<div class="row"> <p>مستوى اقتصاد الوقود )<input type="text">) رقم الهيكل (في حال كانت السيارة مستعملة فقط) (<input type="text">)</p>
<p>مستوى اقتصاد الوقود (</p>
<input type="text">
<p>) رقم الشاسيه "في حال كانت السيارة مستعملة فقط" (</p>
<input type="text">
<p>)</p>
</div>
<div class="row"> <p>مواصفات أخرى:</p>
<p>مواصفات أخرى:</p> <input type="text">
<input type="text">
</div>
<table> <table>
<thead>
<tr>
<th>سعر السيارة الأساسي</th>
<th>مبلغ ضريبة القيمة المضافة (15% VAT)</th>
<th>إجمالي سعر السيارة مع الضريبة</th>
</tr>
</thead>
<tbody>
<tr> <tr>
<th style="width: 30%;">سعر السيارة الأساسي</th> <td colspan="3">{{ cars }}</td>
<th style="width: 30%;">مبلغ ضريبة القيمة المضافة (15% VAT)</th>
<th style="width: 30%;">إجمالي سعر السيارة مع الضريبة</th>
</tr>
<tr>
<td><input type="text"></td>
<td><input type="text"></td>
<td><input type="text"></td>
</tr> </tr>
<tr>
<td>{{ estimate.get_cost_estimate }}</td>
<td></td>
<td>{{ estimate.get_invoiced_amount.invoice_amount_paid__sum }}</td>
</tr>
</tbody>
</table> </table>
<p>إجمالي سعر السيارة مع الضريبة كتابة: <span class="highlight">ريالا سعوديا فقط لا غير</span></p> <p>الإجمالي مع الضريبة كتابةً: <span class="highlight">{{ estimate.get_invoiced_amount.invoice_amount_paid__sum|num_to_words }} {{ CURRENCY }} فقط لا غير</span></p>
<div class="row"> <p>مدة الضمان: <input type="text"> شهراً، أو <input type="text"> كيلومتراً / ميلاً (أيهما يأتي أولاً)</p>
<p>مدة الضمان:</p>
<input type="text">
<p>شهرا، أو</p>
<input type="text">
<p>كيلومترا /ميل (أيهما يأتي أولا)</p>
</div>
<div class="row"> <p>ملاحظات:</p>
<p>ملاحظات:</p> <input type="text">
<input type="text">
</div>
<div class="row"> <p>اسم الشركة / الوكالة:</p>
<p>اسم الشركة/ الوكالة:</p> <input type="text">
<input type="text">
</div>
<div class="row"> <p>العنوان: المدينة - شارع:</p>
<p>العنوان: المدينة شارع</p> <input type="text">
<input type="text">
</div>
<div class="row"> <p>ص.ب / رمز بريدي / الهاتف:</p>
<p>ص.ب رمز بريدي الهاتف:</p> <input type="text">
<input type="text">
</div>
<div class="signature-section"> <div class="signature-section">
<div class="row"> <p>الموظف المسؤول:</p>
<p>الموظف المسؤول التوقيع:</p> <input type="text" style="width: 60%; display: inline-block; border-bottom: 1px solid #333;">
<span class="gap"></span> <p>التوقيع:</p>
</div> <input type="text" style="width: 60%; display: inline-block; border-bottom: 1px solid #333;">
<div class="row"> <p>التاريخ: <input type="text" style="width: 50px;"> / <input type="text" style="width: 50px;"> / <input type="text" style="width: 50px;"></p>
<p>التاريخ:</p>
<input type="text" style="width: 50px;">
<p>/</p>
<input type="text" style="width: 50px;">
<p>/</p>
<input type="text" style="width: 50px;">
<p>م الختم</p>
</div>
</div> </div>
<div class="footer"> <div class="footer">
<!-- Footer content here --> <p>Powered by <a href="https://tenhal.sa">Tenhal | تنحل</a></p>
</div> </div>
</div> </div>

View File

@ -46,7 +46,7 @@
<div class="spinner-border mx-3 htmx-indicator" role="status"><span class="visually-hidden">Loading...</span></div> <div class="spinner-border mx-3 htmx-indicator" role="status"><span class="visually-hidden">Loading...</span></div>
<div class="search-box me-3"> <div class="search-box me-3">
<form class="position-relative"> <form class="position-relative">
<input class="form-control search-input search" name='search' type="search" placeholder="Search" aria-label="Search" hx-get="{% url 'car_list' %}" hx-trigger='keyup changed delay:500ms' hx-target='.table-responsive' hx-select='.table-responsive' hx-swap="innerHTML show:window:top" hx-indicator=".htmx-indicator" <input class="form-control search-input search" name='search' type="search" placeholder="{{ _("Search") }}" aria-label="Search" hx-get="{% url 'car_list' %}" hx-trigger='keyup changed delay:500ms' hx-target='.table-responsive' hx-select='.table-responsive' hx-swap="innerHTML show:window:top" hx-indicator=".htmx-indicator"
hx-on::before-request="on_before_request()" hx-on::before-request="on_before_request()"
hx-on::after-request="on_after_request()" hx-on::after-request="on_after_request()"
/> />
@ -129,17 +129,18 @@
<table class="table fs-9 mb-0 border-top border-translucent"> <table class="table fs-9 mb-0 border-top border-translucent">
<thead> <thead>
<tr> <tr>
<th class="sort white-space-nowrap align-middle ps-0" scope="col" data-sort="projectName" style="width:10%;">Customer Name</th> <th class="sort white-space-nowrap align-middle ps-0" scope="col" data-sort="projectName" style="width:10%;">
<th class="sort align-middle ps-3" scope="col" data-sort="assignees" style="width:5%;">Customer Address</th> {{ _("Customer Name")}}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="start" style="width:5%;">Customer Phone</th> <th class="sort align-middle ps-3" scope="col" data-sort="assignees" style="width:5%;">{{ _("Customer Address")}}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="deadline" style="width:5%;">Make</th> <th class="sort align-middle ps-3" scope="col" data-sort="start" style="width:5%;">{{ _("Customer Phone")}}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:5%;">Model</th> <th class="sort align-middle ps-3" scope="col" data-sort="deadline" style="width:5%;">{{ _("Make") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:5%;">VIN</th> <th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:5%;">{{ _("Model") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:5%;">Trim</th> <th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:5%;">{{ _("VIN") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:5%;">Selling Price</th> <th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:5%;">{{ _("Trim") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:7%;">Estimate</th> <th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:5%;">{{ _("Price") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:7%;">Invoice</th> <th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:7%;">{{ _("Quotation") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:7%;">Sales Staff</th> <th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:7%;">{{ _("Invoice") }}</th>
<th class="sort align-middle ps-3" scope="col" data-sort="task" style="width:7%;">{{ _("Staff Member") }}</th>
<th class="sort align-middle text-end" scope="col" style="width:10%;"></th> <th class="sort align-middle text-end" scope="col" style="width:10%;"></th>
</tr> </tr>
</thead> </thead>
@ -205,7 +206,7 @@
{% elif tx.invoice.is_canceled %} {% elif tx.invoice.is_canceled %}
<span class="badge badge-phoenix badge-phoenix-danger">{{tx.invoice.invoice_status}}</span> <span class="badge badge-phoenix badge-phoenix-danger">{{tx.invoice.invoice_status}}</span>
{% elif tx.invoice.is_past_due %} {% elif tx.invoice.is_past_due %}
<span class="badge badge-phoenix badge-phoenix-danger">Past Due</span> <span class="badge badge-phoenix badge-phoenix-danger">{{ _("Past Due")}}</span>
{% endif %} {% endif %}
</p> </p>
{% endif %} {% endif %}
@ -225,8 +226,8 @@
<td class="align-middle text-end white-space-nowrap pe-0 action"> <td class="align-middle text-end white-space-nowrap pe-0 action">
<div class="btn-reveal-trigger position-static"> <div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button> <button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="#!">View</a><a class="dropdown-item" href="#!">Export</a> <div class="dropdown-menu dropdown-menu-end py-2"><a class="dropdown-item" href="">{{ _("View") }}</a><a class="dropdown-item" href="">{{ _("Export") }}</a>
<div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="#!">Remove</a> <div class="dropdown-divider"></div><a class="dropdown-item text-danger" href="">{{ _("Remove") }}</a>
</div> </div>
</div> </div>
</td> </td>
@ -237,19 +238,11 @@
<div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9 border-bottom border-translucent"> <div class="d-flex flex-wrap align-items-center justify-content-between py-3 pe-0 fs-9 border-bottom border-translucent">
<div class="d-flex"> <div class="d-flex">
{% if is_paginated %}
</div> {% include 'partials/pagination.html' %}
<div class="d-flex" hx-boost="true" hx-push-url='false' hx-include=".make,.model,.year,.car_status" hx-target=".table-responsive" hx-select=".table-responsive" hx-swap="innerHTML" hx-indicator=".htmx-indicator" {% endif %}
hx-on::before-request="on_before_request()"
hx-on::after-request="on_after_request()">
{% if page_obj.has_previous %}
<a href="{% url 'car_list' %}?page={{page_obj.previous_page_number}}" class="page-link" data-list-pagination="prev"><span class="fas fa-chevron-left"></span></a>
{% endif %}
<ul class="mb-0 pagination">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</ul>
{% if page_obj.has_next %}
<a href="{% url 'car_list' %}?page={{page_obj.next_page_number}}" class="page-link pe-0" data-list-pagination="next"><span class="fas fa-chevron-right"></span></a>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -114,7 +114,6 @@
<div class="avatar avatar-xl me-3"><img class="rounded-circle" src="{% static 'images/icons/picture.svg' %}" alt="" /> <div class="avatar avatar-xl me-3"><img class="rounded-circle" src="{% static 'images/icons/picture.svg' %}" alt="" />
{% endif %} {% endif %}
</div> </div>
<div><a class="fs-8 fw-bold" href="">{{ vendor.vendor_name }}</a> <div><a class="fs-8 fw-bold" href="">{{ vendor.vendor_name }}</a>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<p class="mb-0 text-body-highlight fw-semibold fs-9 me-2">{{ vendor.vendor_name }}</p><span class="badge badge-phoenix badge-phoenix-primary">{{ vendor.id}}</span> <p class="mb-0 text-body-highlight fw-semibold fs-9 me-2">{{ vendor.vendor_name }}</p><span class="badge badge-phoenix badge-phoenix-primary">{{ vendor.id}}</span>
@ -147,43 +146,7 @@
<div class="row align-items-center justify-content-end py-4 pe-0 fs-9"> <div class="row align-items-center justify-content-end py-4 pe-0 fs-9">
<!-- Optional: Pagination --> <!-- Optional: Pagination -->
{% if is_paginated %} {% if is_paginated %}
<nav aria-label="Page navigation"> {% include 'partials/pagination.html' %}
<ul class="pagination mb-0">
{% if page_obj.has_previous %}
<li class="page-item py-0">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true"><span class="fas fa-chevron-left"></span></span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true"><span class="fas fa-chevron-left"></span></span>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
<span aria-hidden="true"><span class="fas fa-chevron-right"></span></span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true"><span class="fas fa-chevron-right"></span></span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %} {% endif %}
</div> </div>
</div> </div>