import itertools from uuid import uuid4 from django.conf import settings from django.db import models, transaction from django.db.models import Sum, F, Count from django.contrib.auth.models import User from django.db.models.signals import pre_save, post_save from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from django_ledger.models import ( VendorModel, EntityModel, EntityUnitModel, ItemModel, AccountModel, ItemModelAbstract, UnitOfMeasureModel, CustomerModel, ItemModelQuerySet, ) from django.db.models import Sum from decimal import Decimal, InvalidOperation from django.core.exceptions import ValidationError from phonenumber_field.modelfields import PhoneNumberField from django.contrib.contenttypes.models import ContentType from django.utils.timezone import now from .utilities.financials import get_financial_value, get_total, get_total_financials from django.db.models import FloatField from .mixins import LocalizedNameMixin from django_ledger.models import EntityModel class CarMake(models.Model, LocalizedNameMixin): id_car_make = models.AutoField(primary_key=True) name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255) logo = models.ImageField(_("logo"), upload_to="car_make", blank=True, null=True) is_sa_import = models.BooleanField(default=False) def __str__(self): return self.name class Meta: verbose_name = "Make" class CarModel(models.Model, LocalizedNameMixin): id_car_model = models.AutoField(primary_key=True) id_car_make = models.ForeignKey(CarMake, models.DO_NOTHING, db_column="id_car_make") name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255) def __str__(self): return self.name class Meta: verbose_name = "Model" class CarSerie(models.Model, LocalizedNameMixin): id_car_serie = models.AutoField(primary_key=True) id_car_model = models.ForeignKey( CarModel, models.DO_NOTHING, db_column="id_car_model" ) name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255) year_begin = models.IntegerField(blank=True, null=True) year_end = models.IntegerField(blank=True, null=True) def __str__(self): return self.name class Meta: verbose_name = "Series" class CarTrim(models.Model, LocalizedNameMixin): id_car_trim = models.AutoField(primary_key=True) id_car_serie = models.ForeignKey( CarSerie, models.DO_NOTHING, db_column="id_car_serie" ) name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255) start_production_year = models.IntegerField(blank=True, null=True) end_production_year = models.IntegerField(blank=True, null=True) id_car_model = models.ForeignKey(CarModel, models.DO_NOTHING, db_column='id_car_model', blank=True, null=True) def __str__(self): return self.name class Meta: verbose_name = "Trim" class CarSpecification(models.Model, LocalizedNameMixin): id_car_specification = models.AutoField(primary_key=True) name = models.CharField(max_length=255) arabic_name = models.CharField(max_length=255) id_parent = models.ForeignKey( "self", models.DO_NOTHING, db_column="id_parent", blank=True, null=True ) def __str__(self): return self.name class Meta: verbose_name = "Specification" class CarSpecificationValue(models.Model): id_car_specification_value = models.AutoField(primary_key=True) id_car_trim = models.ForeignKey(CarTrim, models.DO_NOTHING, db_column="id_car_trim") id_car_specification = models.ForeignKey( CarSpecification, models.DO_NOTHING, db_column="id_car_specification" ) value = models.CharField(max_length=500) unit = models.CharField(max_length=255, blank=True, null=True) def __str__(self): return f"{self.id_car_specification.name}: {self.value} {self.unit}" class Meta: verbose_name = "Specification Value" # Car Model class CarStatusChoices(models.TextChoices): AVAILABLE = "available", _("Available") SOLD = "sold", _("Sold") HOLD = "hold", _("Hold") DAMAGED = "damaged", _("Damaged") RESERVED = "reserved", _("Reserved") class CarStockTypeChoices(models.TextChoices): NEW = "new", _("New") USED = "used", _("Used") class DEALER_TYPES(models.TextChoices): Owner = "Owner", _("Owner") Inventory = "Inventory", _("Inventory") Accountent = "Accountent", _("Accountent") Sales = "sales", _("Sales") class AdditionalServices(models.Model): name = models.CharField(max_length=255, verbose_name=_("Name")) display_name = models.CharField(max_length=255, verbose_name=_("Display Name")) description = models.TextField(verbose_name=_("Description")) price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Price")) class Meta: verbose_name = _("Additional Services") verbose_name_plural = _("Additional Services") def __str__(self): return self.name + " - " + str(self.price) class Car(models.Model): vin = models.CharField(max_length=17, unique=True, verbose_name=_("VIN")) dealer = models.ForeignKey( "Dealer", models.DO_NOTHING, related_name="cars", verbose_name=_("Dealer") ) vendor = models.ForeignKey( "Vendor", models.DO_NOTHING, null=True, blank=True, related_name="cars", verbose_name=_("Vendor"), ) id_car_make = models.ForeignKey( CarMake, models.DO_NOTHING, db_column="id_car_make", null=True, blank=True, verbose_name=_("Make"), ) id_car_model = models.ForeignKey( CarModel, models.DO_NOTHING, db_column="id_car_model", null=True, blank=True, verbose_name=_("Model"), ) year = models.IntegerField(verbose_name=_("Year")) id_car_serie = models.ForeignKey( CarSerie, models.DO_NOTHING, db_column="id_car_serie", null=True, blank=True, verbose_name=_("Series"), ) id_car_trim = models.ForeignKey( CarTrim, models.DO_NOTHING, db_column="id_car_trim", null=True, blank=True, verbose_name=_("Trim"), ) status = models.CharField( max_length=10, choices=CarStatusChoices.choices, default=CarStatusChoices.AVAILABLE, verbose_name=_("Status"), ) stock_type = models.CharField( max_length=10, choices=CarStockTypeChoices.choices, default=CarStockTypeChoices.NEW, verbose_name=_("Stock Type"), ) remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks")) mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage")) receiving_date = models.DateTimeField(verbose_name=_("Receiving Date")) class Meta: verbose_name = _("Car") verbose_name_plural = _("Cars") def __str__(self): make = self.id_car_make.name if self.id_car_make else "Unknown Make" model = self.id_car_model.name if self.id_car_model else "Unknown Model" trim = self.id_car_trim.name if self.id_car_trim else "Unknown Trim" return f"{self.year} - {make} - {model} - {trim}" def is_reserved(self): active_reservations = self.reservations.filter(reserved_until__gt=now()) return active_reservations.exists() # class CarData(models.Model): # vin = models.CharField(max_length=17, unique=True, verbose_name=_("VIN")) # make = models.CharField(max_length=255, verbose_name=_("Make")) # make_ar = models.CharField(max_length=255, verbose_name=_("Make Arabic")) # model = models.CharField(max_length=255, verbose_name=_("Model")) # model_ar = models.CharField(max_length=255, verbose_name=_("Model Arabic")) # year = models.IntegerField(verbose_name=_("Year")) # series = models.CharField(max_length=255,verbose_name=_("Series")) # trim = models.CharField(max_length=255,verbose_name=_("Trim")) # specs = models.JsonField # status = models.CharField( # max_length=10, # choices=CarStatusChoices, # default=CarStatusChoices.AVAILABLE, # verbose_name=_("Status") # ) # stock_type = models.CharField( # max_length=10, # choices=CarStockTypeChoices, # default=CarStockTypeChoices.NEW, # verbose_name=_("Stock Type") # ) # remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks")) # mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage")) # receiving_date = models.DateTimeField(verbose_name=_("Receiving Date")) class CarReservation(models.Model): car = models.ForeignKey('Car', on_delete=models.CASCADE, related_name='reservations', verbose_name=_("Car")) reserved_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reservations', verbose_name=_("Reserved By")) reserved_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Reserved At")) reserved_until = models.DateTimeField(verbose_name=_("Reserved Until")) def is_active(self): return self.reserved_until > now() class Meta: unique_together = ('car', 'reserved_until') ordering = ['-reserved_at'] verbose_name = _("Car Reservation") verbose_name_plural = _("Car Reservations") # Car Finance Model class CarFinance(models.Model): additional_services = models.ManyToManyField(AdditionalServices, related_name="additional_finances",blank=True) car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name='finances') cost_price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Cost Price")) selling_price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Selling Price")) discount_amount = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Discount Amount"), default=Decimal('0.00')) # profit_margin = models.DecimalField(max_digits=14, # decimal_places=2, # verbose_name=_("Profit Margin"), # editable=False) # vat_amount = models.DecimalField(max_digits=14, # decimal_places=2, # verbose_name=_("Vat Amount"), # editable=False,default=Decimal('0.00')) # registration_fee = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Registration Fee"), # default=Decimal('0.00')) # administration_fee = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Administration Fee"), # default=Decimal('0.00')) # transportation_fee = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Transportation Fee"), # default=Decimal('0.00')) # custom_card_fee = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Custom Card Fee"), # default=Decimal('0.00')) @property def total(self): """Calculate the total amount including VAT.""" return get_total(self) def __str__(self): return f"Car: {self.car}, Selling Price: {self.selling_price}" # def save(self, *args, **kwargs): # self.full_clean() # try: # price_after_discount = self.selling_price - self.discount_amount # self.profit_margin = price_after_discount - self.cost_price # self.vat_amount = settings.VAT_RATE # except InvalidOperation as e: # raise ValidationError(_("Invalid decimal operation: %s") % str(e)) # super().save(*args, **kwargs) class Meta: verbose_name = _("Car Financial Details") verbose_name_plural = _("Car Financial Details") class ExteriorColors(models.Model, LocalizedNameMixin): name = models.CharField(max_length=255, verbose_name=_("Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) rgb = models.CharField(max_length=24, blank=True, null=True, verbose_name=_("RGB")) class Meta: verbose_name = _("Exterior Colors") verbose_name_plural = _("Exterior Colors") def __str__(self): return f"{self.name} ({self.rgb})" class InteriorColors(models.Model, LocalizedNameMixin): name = models.CharField(max_length=255, verbose_name=_("Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) rgb = models.CharField(max_length=24, blank=True, null=True, verbose_name=_("RGB")) class Meta: verbose_name = _("Interior Colors") verbose_name_plural = _("Interior Colors") def __str__(self): return f"{self.name} ({self.rgb})" # Colors Model class CarColors(models.Model): car = models.ForeignKey("Car", on_delete=models.CASCADE, related_name="colors") exterior = models.ForeignKey( "ExteriorColors", on_delete=models.CASCADE, related_name="colors" ) interior = models.ForeignKey( "InteriorColors", on_delete=models.CASCADE, related_name="colors" ) class Meta: verbose_name = _("Color") verbose_name_plural = _("Colors") unique_together = ("car", "exterior", "interior") def __str__(self): return f"{self.car} ({self.exterior.name}) ({self.interior.name})" # Custom Card Model class CustomCard(models.Model): car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name='custom_cards', verbose_name=_("Car")) custom_number = models.CharField(max_length=255, verbose_name=_("Custom Number")) custom_date = models.DateField(verbose_name=_("Custom Date")) class Meta: verbose_name = _("Custom Card") verbose_name_plural = _("Custom Cards") def __str__(self): return f"{self.car} - {self.custom_number}" class CarLocation(models.Model): car = models.OneToOneField( Car, on_delete=models.CASCADE, related_name='location', verbose_name=_("Car") ) owner = models.ForeignKey( 'Dealer', on_delete=models.CASCADE, related_name='owned_cars', verbose_name=_("Owner"), help_text=_("Dealer who owns the car.") ) showroom = models.ForeignKey( 'Dealer', on_delete=models.CASCADE, related_name='showroom_cars', verbose_name=_("Showroom"), help_text=_("Dealer where the car is displayed (can be the owner).") ) description = models.TextField( blank=True, null=True, verbose_name=_("Description"), help_text=_("Optional description about the showroom placement.") ) created_at = models.DateTimeField( auto_now_add=True, verbose_name=_("Created At") ) updated_at = models.DateTimeField( auto_now=True, verbose_name=_("Last Updated") ) class Meta: verbose_name = _("Car Location") verbose_name_plural = _("Car Locations") def __str__(self): return f"Car: {self.car}, Showroom: {self.showroom}, Owner: {self.owner}" def is_owner_showroom(self): """ Returns True if the showroom is the same as the owner. """ return self.owner == self.showroom # Car Registration Model class CarRegistration(models.Model): car = models.ForeignKey( Car, on_delete=models.CASCADE, related_name="registrations", verbose_name=_("Car"), ) plate_number = models.IntegerField(verbose_name=_("Plate Number")) text1 = models.CharField(max_length=1, verbose_name=_("Text 1")) text2 = models.CharField(max_length=1, verbose_name=_("Text 2")) text3 = models.CharField(max_length=1, verbose_name=_("Text 3")) registration_date = models.DateTimeField(verbose_name=_("Registration Date")) class Meta: verbose_name = _("Registration") verbose_name_plural = _("Registrations") def __str__(self): return f"{self.plate_number} - {self.text1} {self.text2} {self.text3}" # TimestampedModel Abstract Class class TimestampedModel(models.Model): created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) class Meta: abstract = True #subscription class Subscription(models.Model): plan = models.CharField(max_length=255) # e.g. "basic", "premium" start_date = models.DateField() end_date = models.DateField() max_users = models.IntegerField() # maximum number of users per account users = models.ManyToManyField(User, through='SubscriptionUser') # many-to-many relationship with User model is_active = models.BooleanField(default=True) def __str__(self): return self.plan class SubscriptionUser(models.Model): subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): return f"{self.subscription} - {self.user}" class SubscriptionPlan(models.Model): name = models.CharField(max_length=255) description = models.TextField() price = models.DecimalField(max_digits=10, decimal_places=2) max_users = models.IntegerField() # maximum number of users per account def __str__(self): return f"{self.name} - {self.price}" # Dealer Model class Dealer(models.Model, LocalizedNameMixin): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="dealer") crn = models.CharField( max_length=10, verbose_name=_("Commercial Registration Number"),null=True,blank=True ) vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number"),null=True,blank=True) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) name = models.CharField(max_length=255, verbose_name=_("English Name")) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) address = models.CharField( max_length=200, blank=True, null=True, verbose_name=_("Address") ) logo = models.ImageField( upload_to="logos/users", blank=True, null=True, verbose_name=_("Logo") ) parent_dealer = models.ForeignKey( "self", on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_("Parent Dealer"), related_name="sub_dealers", ) dealer_type = models.CharField( max_length=255, choices=DEALER_TYPES.choices, verbose_name=_("Dealer Type"), default=DEALER_TYPES.Owner, ) @property def get_active_plan(self): try: return self.user.subscription_set.filter(is_active=True).first() except SubscriptionPlan.DoesNotExist: return None class Meta: verbose_name = _("Dealer") verbose_name_plural = _("Dealers") permissions = [ ('change_dealer_type', 'Can change dealer type'), ] def __str__(self): return self.name @property def get_sub_dealers(self): if self.dealer_type == "Owner": return self.sub_dealers.all() return None @property def is_parent(self): return self.dealer_type == "Owner" @property def get_root_dealer(self): return self.parent_dealer if self.parent_dealer else self # Vendor Model class Vendor(models.Model, LocalizedNameMixin): dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="vendors") crn = models.CharField( max_length=10, unique=True, verbose_name=_("Commercial Registration Number") ) vrn = models.CharField( max_length=15, unique=True, verbose_name=_("VAT Registration Number") ) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) name = models.CharField(max_length=255, verbose_name=_("English Name")) contact_person = models.CharField(max_length=100, verbose_name=_("Contact Person")) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) address = models.CharField( max_length=200, blank=True, null=True, verbose_name=_("Address") ) logo = models.ImageField( upload_to="logos/vendors", blank=True, null=True, verbose_name=_("Logo") ) class Meta: verbose_name = _("Vendor") verbose_name_plural = _("Vendors") def __str__(self): return self.name # Customer Model class Customer(models.Model): dealer = models.ForeignKey( Dealer, on_delete=models.CASCADE, related_name="customers" ) first_name = models.CharField(max_length=50, verbose_name=_("First Name")) middle_name = models.CharField( max_length=50, blank=True, null=True, verbose_name=_("Middle Name") ) last_name = models.CharField(max_length=50, verbose_name=_("Last Name")) email = models.EmailField(unique=True, verbose_name=_("Email")) national_id = models.CharField( max_length=10, unique=True, verbose_name=_("National ID") ) phone_number = PhoneNumberField( region="SA", unique=True, verbose_name=_("Phone Number") ) address = models.CharField( max_length=200, blank=True, null=True, verbose_name=_("Address") ) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) class Meta: verbose_name = _("Customer") verbose_name_plural = _("Customers") def __str__(self): middle = f" {self.middle_name}" if self.middle_name else "" return f"{self.first_name}{middle} {self.last_name}" @property def get_full_name(self): return f"{self.first_name} {self.middle_name} {self.last_name}" class Organization(models.Model, LocalizedNameMixin): dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='organizations') name = models.CharField(max_length=255, verbose_name=_("Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) crn = models.CharField(max_length=15, verbose_name=_("Commercial Registration Number")) vrn = models.CharField(max_length=15, verbose_name=_("VAT Registration Number")) phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number")) address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address")) logo = models.ImageField(upload_to="logos", blank=True, null=True, verbose_name=_("Logo")) class Meta: verbose_name = _("Organization") verbose_name_plural = _("Organizations") def __str__(self): return self.name class Representative(models.Model, LocalizedNameMixin): dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='representatives') name = models.CharField(max_length=255, verbose_name=_("Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) id_number = models.CharField(max_length=10, verbose_name=_("ID Number")) phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number")) address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address")) organization = models.ManyToManyField(Organization, related_name='representatives') class Meta: verbose_name = _("Representative") verbose_name_plural = _("Representatives") def __str__(self): return self.name class SaleQuotation(models.Model): quotation_number = models.CharField(max_length=10, unique=True) STATUS_CHOICES = [ ("DRAFT", _("Draft")), ("CONFIRMED", _("Confirmed")), ("CANCELED", _("Canceled")), ] dealer = models.ForeignKey( Dealer, on_delete=models.CASCADE, related_name="sales", null=True ) entity = models.ForeignKey(EntityModel, on_delete=models.CASCADE) customer = models.ForeignKey( Customer, on_delete=models.CASCADE, related_name="quotations", verbose_name=_("Customer"), ) amount = models.DecimalField( decimal_places=2, default=Decimal("0.00"), max_digits=10, verbose_name=_("Amount"), ) remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks")) is_approved = models.BooleanField(default=False) # status = models.CharField( # max_length=10, choices=STATUS_CHOICES, default="DRAFT", verbose_name=_("Status") # ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) @property def total_quantity(self): total_quantity = self.quotation_cars.aggregate(total=Sum('quantity'))['total'] return total_quantity or 0 @property def total(self): total = self.quotation_cars.aggregate(total_price=Sum(F('car__finances__selling_price') * F('quantity'))) if not total: return 0 return total["total_price"] @property def total_vat(self): if self.total: return float(self.total) * 0.15 + float(self.total) return 0 # def confirm(self): # """Confirm the quotation and lock financial details.""" # if self.status != "DRAFT": # raise ValueError(_("Only draft quotations can be confirmed.")) # self.status = "CONFIRMED" # self.save() # def cancel(self): # """Cancel the quotation.""" # if self.status == "CONFIRMED": # raise ValueError(_("Cannot cancel a confirmed quotation.")) # self.status = "CANCELED" # self.save() def __str__(self): return f"Quotation #{self.quotation_number} for {self.customer}" @property def display_quotation_number(self): return f"QN-{self.quotation_number}" def save(self, *args, **kwargs): if not self.quotation_number: self.quotation_number = str(next(self._get_quotation_number())).zfill(6) super().save(*args, **kwargs) @classmethod def _get_quotation_number(cls): last_quotation = cls.objects.all().order_by('id').last() if last_quotation: last_quotation_number = int(last_quotation.quotation_number) else: last_quotation_number = 0 return itertools.count(last_quotation_number + 1) class SaleQuotationCar(models.Model): quotation = models.ForeignKey( SaleQuotation, on_delete=models.CASCADE, related_name="quotation_cars", verbose_name=_("Quotation"), ) car = models.ForeignKey( Car, on_delete=models.CASCADE, verbose_name=_("Car") ) quantity = models.PositiveIntegerField(default=1, verbose_name=_("Quantity")) @property def finance(self): return self.car.finances @property def financial_details(self): """ Retrieve financial details dynamically from CarFinance. Returns a dictionary with all financial fields for better access. """ car_finance = self.car.finances if not car_finance: return None return { "selling_price": car_finance.selling_price, "administration_fee": car_finance.administration_fee, "transportation_fee": car_finance.transportation_fee, "custom_card_fee": car_finance.custom_card_fee, "registration_fee": car_finance.registration_fee, "vat_amount": car_finance.vat_amount, # "total_amount": car_finance.total, } @property def total(self): """ Calculate total price dynamically based on quantity and selling price. """ if not self.car.finances: return Decimal("0.00") return self.car.finances.selling_price * self.quantity @property def total_vat(self): """ Calculate total price dynamically based on quantity and selling price. """ if not self.car.finances: return Decimal("0.00") price = float(self.car.finances.selling_price * self.quantity) return (price * 0.15) + price def __str__(self): return f"{self.car} - Quotation #{self.quotation.id}" class SalesOrder(models.Model): quotation = models.OneToOneField( SaleQuotation, on_delete=models.CASCADE, related_name="sales_order", verbose_name=_("Quotation"), ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) total_amount = models.DecimalField( max_digits=14, decimal_places=2, verbose_name=_("Total Amount") ) def __str__(self): return f"Sales Order #{self.id} from Quotation #{self.quotation.id}"