Merge branch 'main' of http://10.10.1.120:3000/tenhal_admin/haikal
This commit is contained in:
commit
631da4d9b6
1
.gitignore
vendored
1
.gitignore
vendored
@ -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.
Binary file not shown.
Binary file not shown.
@ -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()),
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
|
|||||||
@ -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__'
|
||||||
18
api/urls.py
18
api/urls.py
@ -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'),
|
||||||
]
|
]
|
||||||
|
|||||||
49
api/views.py
49
api/views.py
@ -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
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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}")
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
140
scripts/dsrpipe.py
Normal 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 ""
|
||||||
@ -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">
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 %}
|
||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
39
templates/vendors/vendors_list.html
vendored
39
templates/vendors/vendors_list.html
vendored
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user