From aa975148db5b659fe02a5d90fdbbb3ec0dc328a6 Mon Sep 17 00:00:00 2001 From: gitea Date: Wed, 22 Jan 2025 12:25:52 +0000 Subject: [PATCH 1/3] update repo --- .../__pycache__/settings.cpython-311.pyc | Bin 6916 -> 6859 bytes .../__pycache__/urls.cpython-311.pyc | Bin 2072 -> 2072 bytes inventory/__pycache__/forms.cpython-311.pyc | Bin 31062 -> 32060 bytes inventory/__pycache__/models.cpython-311.pyc | Bin 81944 -> 82058 bytes inventory/__pycache__/urls.cpython-311.pyc | Bin 21804 -> 21804 bytes inventory/__pycache__/utils.cpython-311.pyc | Bin 10566 -> 23190 bytes inventory/__pycache__/views.cpython-311.pyc | Bin 150225 -> 167313 bytes inventory/forms.py | 9 - .../migrations/0007_delete_cartransferlog.py | 16 + inventory/models.py | 731 ++++++------------ inventory/signals.py | 16 +- templates/crm/notifications_history.html | 2 +- templates/header.html | 2 +- templates/inventory/car_detail.html | 6 +- 14 files changed, 254 insertions(+), 528 deletions(-) create mode 100644 inventory/migrations/0007_delete_cartransferlog.py diff --git a/car_inventory/__pycache__/settings.cpython-311.pyc b/car_inventory/__pycache__/settings.cpython-311.pyc index 0b5df19d529a8332440f2df16ffd30a30acb03c3..92b03f6f87df37c9c1dca3356da043e29a0d9cc9 100644 GIT binary patch delta 526 zcmZ8dyG{a85Z!xO#el1cny4`r3WyI_S>7lhporjGyJutfU7(Kg^2-&(Zs8=N!2veAABZyEGQN$3x7R(9er0Jj-QWS46 zC9pwBY*Gqo+CYXj;lez!oVs-_I62E(=8!j~?GQ*k=n8c(+~wAB$QH+cDvoad6i}RV0p9%Rg(z`A^BiK;v-QmaN*+B zgXu+Jy~4qh?q5ML#v|dbXHTA;E*I(K_f6*aeKV7p%vJ7vUcFG&ki-@k`QZHis1AtA z*LYndXcVK4peUk_z!)Ru$~1;?iXl$pn4tJ>A105&lH`c?cTsHr5V8KLb6JwUFm8>W zAVE_dzqZ!Q8EJ7GYnNt_`1t*tzNfV2%>_?U#RrptR z6`Dhh=KT?BcSoQyvoh*k>;jg^giVWBCJQStSFwf$npj5!Pu*UsV1ufRn%{J@tGo_{ zmKZkgwG3rbnrwM50~bO)3a53g^R%>~wWo_$3|>nwHgk)@W^4*GzkL`+0T4uGyB^vYQR>CNdNV gedZ%{PVw&Y+rv*Hcs)GvQyTme^iCfoW#x>8e=P=!)c^nh diff --git a/car_inventory/__pycache__/urls.cpython-311.pyc b/car_inventory/__pycache__/urls.cpython-311.pyc index c3b2ce498774e15d45e8665b6d861361072bcf9f..8c3b05d27bf91820c1f9adcb0f578ca65fed71a5 100644 GIT binary patch delta 20 acmbOsFhhWQIWI340}x!R@7u`D%K-p0)dbrB delta 20 acmbOsFhhWQIWI340}#YN?Agf8%K-o~)&#@= diff --git a/inventory/__pycache__/forms.cpython-311.pyc b/inventory/__pycache__/forms.cpython-311.pyc index 121245025add609874d8f80418efdc64a35f2752..e8abe2878fd7fb54267396c753ffa0556f86a23e 100644 GIT binary patch delta 8386 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 Qvsu+Qc-7522=GPr|6vAQr~m)} delta 7935 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 diff --git a/inventory/__pycache__/models.cpython-311.pyc b/inventory/__pycache__/models.cpython-311.pyc index 0c155c750df0789ee31f60472ad8c61fb4743eec..70f9051c896f7cee84a752b7dd1488a343cfcadf 100644 GIT binary patch delta 4198 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 diff --git a/inventory/__pycache__/urls.cpython-311.pyc b/inventory/__pycache__/urls.cpython-311.pyc index ad43661e51202f5d4e100c5ae8d047e37d9f8065..f61334df641b604f04ea9fb6d520f3b6754b49ea 100644 GIT binary patch delta 22 ccmZ3pigC>n+a diff --git a/inventory/__pycache__/utils.cpython-311.pyc b/inventory/__pycache__/utils.cpython-311.pyc index 522f9ffc81b61657d5b9a8060d4256c0c6cb0a18..6aa2a8a8b53fe6329752558fa1f05525d522d14a 100644 GIT binary patch literal 23190 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 HQPBSdOhY@> delta 1855 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 delta 38489 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 SZAzZ3-KHb~ now() class Meta: - unique_together = ("car", "reserved_until") - ordering = ["-reserved_at"] + 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"), - ) - + 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')) + @property def total(self): if self.additional_services.count() > 0: - return self.selling_price + sum( - x.price for x in self.additional_services.all() - ) + return self.selling_price + sum(x.price for x in self.additional_services.all()) return self.selling_price - @property def total_discount(self): if self.discount_amount > 0: return self.total - self.discount_amount return self.total - + @property def total_vat(self): return self.total_discount + self.vat_amount - + + @property def vat_amount(self): vat = VatRate.objects.filter(is_active=True).first() if vat: - return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01")) - return Decimal("0.00") + return (self.total_discount * Decimal(vat.rate)).quantize(Decimal('0.01')) + return Decimal('0.00') @property def revenue(self): - return self.selling_price - self.cost_price - + return self.selling_price-self.cost_price + + def __str__(self): return f"Car: {self.car}, Selling Price: {self.selling_price}" - # def save(self, *args, **kwargs): + # def save(self, *args, **kwargs): # self.full_clean() # try: # price_after_discount = self.selling_price - self.discount_amount @@ -592,12 +446,7 @@ class CarColors(models.Model): class CustomCard(models.Model): - car = models.OneToOneField( - Car, - on_delete=models.CASCADE, - related_name="custom_cards", - verbose_name=_("Car"), - ) + 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")) @@ -611,30 +460,39 @@ class CustomCard(models.Model): class CarLocation(models.Model): car = models.OneToOneField( - Car, on_delete=models.CASCADE, related_name="location", verbose_name=_("Car") + Car, + on_delete=models.CASCADE, + related_name='location', + verbose_name=_("Car") ) owner = models.ForeignKey( - "Dealer", + 'Dealer', on_delete=models.CASCADE, - related_name="owned_cars", + related_name='owned_cars', verbose_name=_("Owner"), - help_text=_("Dealer who owns the car."), + help_text=_("Dealer who owns the car.") ) showroom = models.ForeignKey( - "Dealer", + 'Dealer', on_delete=models.CASCADE, - related_name="showroom_cars", + related_name='showroom_cars', verbose_name=_("Showroom"), - help_text=_("Dealer where the car is displayed (can be the owner)."), + 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."), + 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") ) - 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") @@ -661,14 +519,16 @@ class CarRegistration(models.Model): 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_type = registration_date = models.DateTimeField(verbose_name=_("Registration Date")) + registration_expiry_date = models.DateTimeField(verbose_name=_("Registration Expiry Date")) class Meta: verbose_name = _("Registration") verbose_name_plural = _("Registrations") def __str__(self): - return f"{self.plate_number} - {self.text1} {self.text2} {self.text3}" + return f"{self.text1} {self.text2} {self.text3} - {self.plate_number}" # TimestampedModel Abstract Class @@ -681,28 +541,19 @@ class TimestampedModel(models.Model): class Subscription(models.Model): - plan = models.ForeignKey( - "SubscriptionPlan", on_delete=models.CASCADE, related_name="subscriptions" - ) + plan = models.ForeignKey("SubscriptionPlan", on_delete=models.CASCADE, related_name="subscriptions") start_date = models.DateField(help_text="Date when the subscription starts") end_date = models.DateField(help_text="Date when the subscription ends") - users = models.ManyToManyField( - User, through="SubscriptionUser" - ) # many-to-many relationship with User model + users = models.ManyToManyField(User, through='SubscriptionUser') # many-to-many relationship with User model is_active = models.BooleanField(default=True) billing_cycle = models.CharField( max_length=10, - choices=[("monthly", "Monthly"), ("annual", "Annual")], - default="monthly", - help_text="Billing cycle for the subscription", + choices=[('monthly', 'Monthly'), ('annual', 'Annual')], + default='monthly', + help_text="Billing cycle for the subscription" ) - last_payment_date = models.DateField( - null=True, blank=True, help_text="Date of the last payment made" - ) - next_payment_date = models.DateField( - null=True, blank=True, help_text="Date of the next payment due" - ) - + last_payment_date = models.DateField(null=True, blank=True, help_text="Date of the last payment made") + next_payment_date = models.DateField(null=True, blank=True, help_text="Date of the next payment due") class Meta: verbose_name = _("Subscription") verbose_name_plural = _("Subscriptions") @@ -728,30 +579,18 @@ class SubscriptionUser(models.Model): class SubscriptionPlan(models.Model): - name = models.CharField( - max_length=100, unique=True, help_text=_("Name of the subscription plan") - ) + name = models.CharField(max_length=100, unique=True, help_text=_("Name of the subscription plan")) description = models.TextField() price = models.DecimalField(max_digits=10, decimal_places=2) - max_users = models.PositiveIntegerField( - help_text=_("Maximum number of users allowed"), default=1 - ) - max_inventory_size = models.PositiveIntegerField( - help_text=_("Maximum number of cars in inventory"), default=50 - ) + max_users = models.PositiveIntegerField(help_text=_("Maximum number of users allowed"), default=1) + max_inventory_size = models.PositiveIntegerField(help_text=_("Maximum number of cars in inventory"), default=50) support_level = models.CharField( max_length=50, - choices=[ - ("basic", "Basic Support"), - ("priority", "Priority Support"), - ("dedicated", "Dedicated Support"), - ], - default="basic", - help_text="Level of support provided", - ) - custom_features = models.JSONField( - blank=True, null=True, help_text=_("Additional features specific to this plan") + choices=[('basic', 'Basic Support'), ('priority', 'Priority Support'), ('dedicated', 'Dedicated Support')], + default='basic', + help_text="Level of support provided" ) + custom_features = models.JSONField(blank=True, null=True, help_text=_("Additional features specific to this plan")) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -765,27 +604,26 @@ class SubscriptionPlan(models.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 - ) + 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") - ) - entity = models.ForeignKey( - EntityModel, on_delete=models.SET_NULL, null=True, blank=True - ) + 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")) + entity = models.ForeignKey(EntityModel, on_delete=models.SET_NULL, null=True, blank=True) joined_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Joined At")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) @@ -811,14 +649,14 @@ class Dealer(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Dealer") - verbose_name_plural = _("Dealers") + 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": @@ -832,18 +670,18 @@ class Dealer(models.Model, LocalizedNameMixin): # def get_root_dealer(self): # return self.parent_dealer if self.parent_dealer else self - ############################## # Additional staff types for later -# COORDINATOR = "coordinator", _("Coordinator") -# RECEPTIONIST = "receptionist", _("Receptionist") -# AGENT = "agent", _("Agent") -# TECHNICIAN = "technician", _("Technician") -# DRIVER = "driver", _("Driver") + # COORDINATOR = "coordinator", _("Coordinator") + # RECEPTIONIST = "receptionist", _("Receptionist") + # AGENT = "agent", _("Agent") + # TECHNICIAN = "technician", _("Technician") + # DRIVER = "driver", _("Driver") ############################## + class StaffTypes(models.TextChoices): MANAGER = "manager", _("Manager") INVENTORY = "inventory", _("Inventory") @@ -860,9 +698,7 @@ class Staff(models.Model, LocalizedNameMixin): name = models.CharField(max_length=255, verbose_name=_("Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) - staff_type = models.CharField( - choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type") - ) + staff_type = models.CharField(choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) @@ -890,7 +726,6 @@ class Sources(models.TextChoices): YOUTUBE = "youtube", _("Youtube") CAMPAIGN = "campaign", _("Campaign") - class Channel(models.TextChoices): WALK_IN = "walk_in", _("Walk In") TOLL_FREE = "toll_free", _("Toll Free") @@ -906,7 +741,6 @@ class Status(models.TextChoices): QUALIFIED = "qualified", _("Qualified") CANCELED = "canceled", _("Canceled") - class Title(models.TextChoices): MR = "mr", _("Mr") MRS = "mrs", _("Mrs") @@ -919,7 +753,6 @@ class Title(models.TextChoices): COMPANY = "company", _("Company") NA = "na", _("N/A") - class ActionChoices(models.TextChoices): CALL = "call", _("Call") SMS = "sms", _("SMS") @@ -936,7 +769,6 @@ class ActionChoices(models.TextChoices): CREATE_INVOICE = "create_invoice", _("Create Invoice") CANCEL_INVOICE = "cancel_invoice", _("Cancel Invoice") - class Stage(models.TextChoices): PROSPECT = "prospect", _("Prospect") PROPOSAL = "proposal", _("Proposal") @@ -944,7 +776,6 @@ class Stage(models.TextChoices): CLOSED_WON = "closed_won", _("Closed Won") CLOSED_LOST = "closed_lost", _("Closed Lost") - class Priority(models.TextChoices): LOW = "low", _("Low") MEDIUM = "medium", _("Medium") @@ -952,33 +783,17 @@ class Priority(models.TextChoices): class Customer(models.Model): - dealer = models.ForeignKey( - Dealer, on_delete=models.CASCADE, related_name="customers" - ) - title = models.CharField( - choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title") - ) + dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE,related_name="customers") + title = models.CharField(choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title")) 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") - ) + 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")) - gender = models.CharField( - choices=[("m", _("Male")), ("f", _("Female"))], - max_length=1, - verbose_name=_("Gender"), - ) + gender = models.CharField(choices=[('m', _('Male')), ('f', _('Female'))], max_length=1, verbose_name=_("Gender")) dob = models.DateField(verbose_name=_("Date of Birth")) 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") - ) + 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")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) @@ -996,48 +811,33 @@ class Customer(models.Model): class Organization(models.Model, LocalizedNameMixin): - dealer = models.ForeignKey( - Dealer, on_delete=models.CASCADE, related_name="organizations" - ) + 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") - ) + 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") - ) + 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")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) 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" - ) + 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, unique=True, verbose_name=_("ID Number") - ) - phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) + id_number = models.CharField(max_length=10, unique=True, verbose_name=_("ID Number")) + phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number")) email = models.EmailField(max_length=255, verbose_name=_("Email Address")) - address = models.CharField( - max_length=200, blank=True, null=True, verbose_name=_("Address") - ) - organization = models.ManyToManyField(Organization, related_name="representatives") + 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") @@ -1049,57 +849,18 @@ class Representative(models.Model, LocalizedNameMixin): class Lead(models.Model): dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="leads") - customer = models.ForeignKey( - Customer, on_delete=models.CASCADE, related_name="leads" - ) - id_car_make = models.ForeignKey( - CarMake, - on_delete=models.DO_NOTHING, - blank=True, - null=True, - verbose_name=_("Make"), - ) - id_car_model = models.ForeignKey( - CarModel, - on_delete=models.DO_NOTHING, - blank=True, - null=True, - verbose_name=_("Model"), - ) - year = models.PositiveSmallIntegerField( - verbose_name=_("Year"), blank=True, null=True - ) - source = models.CharField( - max_length=50, choices=Sources.choices, verbose_name=_("Source") - ) - channel = models.CharField( - max_length=50, choices=Channel.choices, verbose_name=_("Channel") - ) + customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="leads") + id_car_make = models.ForeignKey(CarMake, on_delete=models.DO_NOTHING, blank=True, null=True, verbose_name=_("Make")) + id_car_model = models.ForeignKey(CarModel, on_delete=models.DO_NOTHING, blank=True, null=True, verbose_name=_("Model")) + year = models.PositiveSmallIntegerField(verbose_name=_("Year"), blank=True, null=True) + source = models.CharField(max_length=50, choices=Sources.choices, verbose_name=_("Source")) + channel = models.CharField(max_length=50, choices=Channel.choices, verbose_name=_("Channel")) city = models.CharField(max_length=50, verbose_name=_("City")) - staff = models.ForeignKey( - Staff, - on_delete=models.SET_NULL, - blank=True, - null=True, - related_name="assigned", - verbose_name=_("Assigned"), - ) - priority = models.CharField( - max_length=10, - choices=Priority.choices, - default=Priority.MEDIUM, - verbose_name=_("Priority"), - ) - status = models.CharField( - max_length=50, - choices=Status.choices, - verbose_name=_("Status"), - db_index=True, - default=Status.NEW, - ) - created = models.DateTimeField( - auto_now_add=True, verbose_name=_("Created"), db_index=True - ) + staff = models.ForeignKey(Staff, on_delete=models.SET_NULL, blank=True, null=True, related_name="assigned", verbose_name=_("Assigned")) + priority = models.CharField(max_length=10, choices=Priority.choices, default=Priority.MEDIUM, + verbose_name=_("Priority")) + status = models.CharField(max_length=50, choices=Status.choices, verbose_name=_("Status"), db_index=True, default=Status.NEW) + created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"), db_index=True) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) class Meta: @@ -1107,22 +868,14 @@ class Lead(models.Model): verbose_name_plural = _("Leads") def __str__(self): - return f"{self.first_name} {self.last_name}" + return self.customer.get_full_name class LeadStatusHistory(models.Model): - lead = models.ForeignKey( - Lead, on_delete=models.CASCADE, related_name="status_history" - ) - old_status = models.CharField( - max_length=50, choices=Status.choices, verbose_name=_("Old Status") - ) - new_status = models.CharField( - max_length=50, choices=Status.choices, verbose_name=_("New Status") - ) - changed_by = models.ForeignKey( - Staff, on_delete=models.DO_NOTHING, related_name="status_changes" - ) + lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name="status_history") + old_status = models.CharField(max_length=50, choices=Status.choices, verbose_name=_("Old Status")) + new_status = models.CharField(max_length=50, choices=Status.choices, verbose_name=_("New Status")) + changed_by = models.ForeignKey(Staff, on_delete=models.DO_NOTHING, related_name="status_changes") changed_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Changed At")) class Meta: @@ -1139,31 +892,12 @@ def validate_probability(value): class Opportunity(models.Model): - dealer = models.ForeignKey( - Dealer, on_delete=models.CASCADE, related_name="opportunities" - ) - customer = models.ForeignKey( - Customer, on_delete=models.CASCADE, related_name="opportunities" - ) - car = models.ForeignKey( - Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car") - ) - stage = models.CharField( - max_length=20, choices=Stage.choices, verbose_name=_("Stage") - ) - status = models.CharField( - max_length=20, - choices=Status.choices, - verbose_name=_("Status"), - default=Status.NEW, - ) - staff = models.ForeignKey( - Staff, - on_delete=models.SET_NULL, - null=True, - related_name="owner", - verbose_name=_("Owner"), - ) + dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="opportunities") + customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="opportunities") + car = models.ForeignKey(Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car")) + stage = models.CharField(max_length=20, choices=Stage.choices, verbose_name=_("Stage")) + status = models.CharField(max_length=20, choices=Status.choices, verbose_name=_("Status"), default=Status.NEW) + staff = models.ForeignKey(Staff, on_delete=models.SET_NULL, null=True, related_name="owner", verbose_name=_("Owner")) probability = models.PositiveIntegerField(validators=[validate_probability]) closing_date = models.DateField(verbose_name=_("Closing Date")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) @@ -1181,11 +915,9 @@ class Opportunity(models.Model): class Notes(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() - content_object = GenericForeignKey("content_type", "object_id") + content_object = GenericForeignKey('content_type', 'object_id') note = models.TextField(verbose_name=_("Note")) - created_by = models.ForeignKey( - User, on_delete=models.DO_NOTHING, related_name="notes_created" - ) + created_by = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name="notes_created") created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) @@ -1200,14 +932,10 @@ class Notes(models.Model): class Activity(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() - content_object = GenericForeignKey("content_type", "object_id") - activity_type = models.CharField( - max_length=50, choices=ActionChoices.choices, verbose_name=_("Activity Type") - ) + content_object = GenericForeignKey('content_type', 'object_id') + activity_type = models.CharField(max_length=50, choices=ActionChoices.choices, verbose_name=_("Activity Type")) notes = models.TextField(blank=True, null=True, verbose_name=_("Notes")) - created_by = models.ForeignKey( - User, on_delete=models.DO_NOTHING, related_name="activities_created" - ) + created_by = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name="activities_created") created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) @@ -1220,9 +948,7 @@ class Activity(models.Model): class Notification(models.Model): - user = models.ForeignKey( - User, on_delete=models.CASCADE, related_name="notifications" - ) + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notifications") message = models.CharField(max_length=255, verbose_name=_("Message")) is_read = models.BooleanField(default=False, verbose_name=_("Is Read")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) @@ -1230,7 +956,7 @@ class Notification(models.Model): class Meta: verbose_name = _("Notification") verbose_name_plural = _("Notifications") - ordering = ["-created"] + ordering = ['-created'] def __str__(self): return self.message @@ -1265,6 +991,7 @@ class Vendor(models.Model, LocalizedNameMixin): return self.name + class SaleQuotation(models.Model): quotation_number = models.CharField(max_length=10, unique=True) @@ -1276,7 +1003,7 @@ class SaleQuotation(models.Model): ] dealer = models.ForeignKey( Dealer, on_delete=models.CASCADE, related_name="sales", null=True - ) + ) customer = models.ForeignKey( Customer, on_delete=models.CASCADE, @@ -1296,47 +1023,35 @@ class SaleQuotation(models.Model): ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) - + posted = models.BooleanField(default=False) - payment_id = models.CharField( - max_length=255, null=True, blank=True, verbose_name=_("Payment ID") - ) + payment_id = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Payment ID")) is_paid = models.BooleanField(default=False) - date_draft = models.DateTimeField( - null=True, blank=True, verbose_name=_("Draft Date") - ) - date_in_review = models.DateTimeField( - null=True, blank=True, verbose_name=_("In Review Date") - ) - date_approved = models.DateTimeField( - null=True, blank=True, verbose_name=_("Approved Date") - ) - date_paid = models.DateTimeField(null=True, blank=True, verbose_name=_("Paid Date")) - date_void = models.DateTimeField(null=True, blank=True, verbose_name=_("Void Date")) - date_canceled = models.DateTimeField( - null=True, blank=True, verbose_name=_("Canceled Date") - ) + date_draft = models.DateTimeField(null=True, blank=True, verbose_name=_('Draft Date')) + date_in_review = models.DateTimeField(null=True, blank=True, verbose_name=_('In Review Date')) + date_approved = models.DateTimeField(null=True, blank=True, verbose_name=_('Approved Date')) + date_paid = models.DateTimeField(null=True, blank=True, verbose_name=_('Paid Date')) + date_void = models.DateTimeField(null=True, blank=True, verbose_name=_('Void Date')) + date_canceled = models.DateTimeField(null=True, blank=True, verbose_name=_('Canceled Date')) @property def total_quantity(self): - total_quantity = self.quotation_cars.aggregate(total=Sum("quantity"))["total"] + 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")) - ) + 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": @@ -1353,11 +1068,11 @@ class SaleQuotation(models.Model): 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) @@ -1365,7 +1080,7 @@ class SaleQuotation(models.Model): @classmethod def _get_quotation_number(cls): - last_quotation = cls.objects.all().order_by("id").last() + last_quotation = cls.objects.all().order_by('id').last() if last_quotation: last_quotation_number = int(last_quotation.quotation_number) else: @@ -1380,13 +1095,16 @@ class SaleQuotationCar(models.Model): related_name="quotation_cars", verbose_name=_("Quotation"), ) - car = models.ForeignKey(Car, on_delete=models.CASCADE, verbose_name=_("Car")) + 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): """ @@ -1415,7 +1133,6 @@ class SaleQuotationCar(models.Model): if not self.car.finances: return Decimal("0.00") return self.car.finances.selling_price * self.quantity - @property def total_vat(self): """ @@ -1448,24 +1165,16 @@ class SalesOrder(models.Model): class Payment(models.Model): METHOD_CHOICES = [ - ("cash", _("cash")), - ("credit", _("credit")), - ("transfer", _("transfer")), - ("debit", _("debit")), - ("SADAD", _("SADAD")), + ('cash', _('cash')), + ('credit', _('credit')), + ('transfer', _('transfer')), + ('debit', _('debit')), + ('SADAD', _('SADAD')), ] - quotation = models.ForeignKey( - SaleQuotation, on_delete=models.CASCADE, related_name="payments" - ) - amount = models.DecimalField( - max_digits=10, decimal_places=2, verbose_name=_("amount") - ) - payment_method = models.CharField( - choices=METHOD_CHOICES, max_length=50, verbose_name=_("method") - ) - reference_number = models.CharField( - max_length=100, null=True, blank=True, verbose_name=_("reference number") - ) + quotation = models.ForeignKey(SaleQuotation, on_delete=models.CASCADE, related_name="payments") + amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("amount")) + payment_method = models.CharField(choices=METHOD_CHOICES, max_length=50, verbose_name=_("method")) + reference_number = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("reference number")) payment_date = models.DateField(auto_now_add=True, verbose_name=_("date")) # def save(self, *args, **kwargs): @@ -1484,12 +1193,8 @@ class Payment(models.Model): class Refund(models.Model): - payment = models.OneToOneField( - Payment, on_delete=models.CASCADE, related_name="refund" - ) - amount = models.DecimalField( - max_digits=10, decimal_places=2, verbose_name=_("amount") - ) + payment = models.OneToOneField(Payment, on_delete=models.CASCADE, related_name="refund") + amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("amount")) reason = models.TextField(blank=True, verbose_name=_("reason")) refund_date = models.DateField(auto_now_add=True, verbose_name=_("refund date")) @@ -1509,7 +1214,9 @@ class UserActivityLog(models.Model): class Meta: verbose_name = "User Activity Log" verbose_name_plural = "User Activity Logs" - ordering = ["-timestamp"] + ordering = ['-timestamp'] def __str__(self): return f"{self.user.email} - {self.action} - {self.timestamp}" + + diff --git a/inventory/signals.py b/inventory/signals.py index 3d9f36c3..2cc18ff8 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -155,7 +155,19 @@ def create_ledger_entity(sender, instance, created, **kwargs): balance_type="debit", active=True, ) - + + # Inventory Account + asset_ca_inventory = entity.create_account( + coa_model=coa, + code="1106", + role=roles.ASSET_CA_INVENTORY, + name=_("Inventory"), + balance_type="debit", + active=True, + ) + asset_ca_inventory.role_default = True + asset_ca_inventory.save() + # VAT Payable Account liability_ltl_vat_receivable = entity.create_account( coa_model=coa, @@ -325,7 +337,7 @@ def create_ledger_entity(sender, instance, created, **kwargs): # Mortgage Payable Account liability_ltl_mortgage_payable = entity.create_account( coa_model=coa, - code="2203", + code="2202", role=roles.LIABILITY_LTL_MORTGAGE_PAYABLE, name=_("Mortgage Payable"), balance_type="credit", diff --git a/templates/crm/notifications_history.html b/templates/crm/notifications_history.html index 3d4b6bfa..bf86da1e 100644 --- a/templates/crm/notifications_history.html +++ b/templates/crm/notifications_history.html @@ -13,7 +13,7 @@

{{ _("System")}}:

{% if not notification.is_read %} -

{{ notification.message|safe }} {{ notification.created|timesince }}

+

{{ notification.message }} {{ notification.created|timesince }}

{% else %}

{{ notification.message|safe }} {{ notification.created|timesince }}

{% endif %} diff --git a/templates/header.html b/templates/header.html index cfdfd189..f97c839e 100644 --- a/templates/header.html +++ b/templates/header.html @@ -344,7 +344,7 @@
- {% if user.dealer.logo %} +{% if user.dealer.logo %} {% elif user.staff.dealer.logo %} diff --git a/templates/inventory/car_detail.html b/templates/inventory/car_detail.html index 7f511f7b..d6157359 100644 --- a/templates/inventory/car_detail.html +++ b/templates/inventory/car_detail.html @@ -291,7 +291,7 @@ - Transfer + Transfer {% if car.get_transfer.status == "draft" %} @@ -301,12 +301,12 @@ waiting for dealer acceptance - {% endif %} + {% endif %} {{ car.get_transfer.from_dealer|title }} {{ car.get_transfer.to_dealer|title }} {{ car.get_transfer.transfer_date|date:"Y-m-d" }} - + {% if car.get_transfer.status == "draft" %} Approve From 44c1f208015f517f9dd03692efad5a735f517007 Mon Sep 17 00:00:00 2001 From: gitea Date: Wed, 22 Jan 2025 12:30:39 +0000 Subject: [PATCH 2/3] update repo --- inventory/models.py | 731 +++++++++++++++++++++++++++++++------------- 1 file changed, 512 insertions(+), 219 deletions(-) diff --git a/inventory/models.py b/inventory/models.py index eb908558..3b708eed 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -17,7 +17,6 @@ from django_ledger.models import ( UnitOfMeasureModel, CustomerModel, ItemModelQuerySet, - ) from django.db.models import Sum from decimal import Decimal, InvalidOperation @@ -29,66 +28,105 @@ from sqlalchemy.orm.base import object_state 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,ItemModel +from django_ledger.models import EntityModel, ItemModel from django_countries.fields import CountryField from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType class DealerUserManager(UserManager): - def create_user_with_dealer(self, email, password, dealer_name, arabic_name, crn, vrn, address, **extra_fields): - user = self.create_user(username=email, email=email, password=password, **extra_fields) - Dealer.objects.create(user=user, name=dealer_name,arabic_name=arabic_name, crn=crn, vrn=vrn, address=address, **extra_fields) + def create_user_with_dealer( + self, + email, + password, + dealer_name, + arabic_name, + crn, + vrn, + address, + **extra_fields, + ): + user = self.create_user( + username=email, email=email, password=password, **extra_fields + ) + Dealer.objects.create( + user=user, + name=dealer_name, + arabic_name=arabic_name, + crn=crn, + vrn=vrn, + address=address, + **extra_fields, + ) return user class StaffUserManager(UserManager): - def create_user_with_staff(self, email, password, name, arabic_name, phone_number, staff_type, **extra_fields): - user = self.create_user(username=email, email=email, password=password, **extra_fields) - Staff.objects.create(user=user, name=name, arabic_name=arabic_name, phone_number=phone_number, staff_type=staff_type, **extra_fields) + def create_user_with_staff( + self, + email, + password, + name, + arabic_name, + phone_number, + staff_type, + **extra_fields, + ): + user = self.create_user( + username=email, email=email, password=password, **extra_fields + ) + Staff.objects.create( + user=user, + name=name, + arabic_name=arabic_name, + phone_number=phone_number, + staff_type=staff_type, + **extra_fields, + ) return user - + + class UnitOfMeasure(models.TextChoices): - EACH = 'EA', 'Each' - PAIR = 'PR', 'Pair' - SET = 'SET', 'Set' - GALLON = 'GAL', 'Gallon' - LITER = 'L', 'Liter' - METER = 'M', 'Meter' - KILOGRAM = 'KG', 'Kilogram' - HOUR = 'HR', 'Hour' - BOX = 'BX', 'Box' - ROLL = 'RL', 'Roll' - PACKAGE = 'PKG', 'Package' - DOZEN = 'DZ', 'Dozen' - SQUARE_METER = 'SQ_M', 'Square Meter' - PIECE = 'PC', 'Piece' - BUNDLE = 'BDL', 'Bundle' + EACH = "EA", "Each" + PAIR = "PR", "Pair" + SET = "SET", "Set" + GALLON = "GAL", "Gallon" + LITER = "L", "Liter" + METER = "M", "Meter" + KILOGRAM = "KG", "Kilogram" + HOUR = "HR", "Hour" + BOX = "BX", "Box" + ROLL = "RL", "Roll" + PACKAGE = "PKG", "Package" + DOZEN = "DZ", "Dozen" + SQUARE_METER = "SQ_M", "Square Meter" + PIECE = "PC", "Piece" + BUNDLE = "BDL", "Bundle" class VatRate(models.Model): - rate = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal('0.15')) + rate = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.15")) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Rate: {self.rate}%" -class CarType(models.IntegerChoices): - CAR = 1, _('Car') - LIGHT_COMMERCIAL = 2, _('Light Commercial') - HEAVY_DUTY_TRACTORS = 3, _('Heavy-Duty Tractors') - TRAILERS = 4, _('Trailers') - MEDIUM_TRUCKS = 5, _('Medium Trucks') - BUSES = 6, _('Buses') - MOTORCYCLES = 20, _('Motorcycles') - BUGGY = 21, _('Buggy') - MOTO_ATV = 22, _('Moto ATV') - SCOOTERS = 23, _('Scooters') - KARTING = 24, _('Karting') - ATV = 25, _('ATV') - SNOWMOBILES = 26, _('Snowmobiles') +class CarType(models.IntegerChoices): + CAR = 1, _("Car") + LIGHT_COMMERCIAL = 2, _("Light Commercial") + HEAVY_DUTY_TRACTORS = 3, _("Heavy-Duty Tractors") + TRAILERS = 4, _("Trailers") + MEDIUM_TRUCKS = 5, _("Medium Trucks") + BUSES = 6, _("Buses") + MOTORCYCLES = 20, _("Motorcycles") + BUGGY = 21, _("Buggy") + MOTO_ATV = 22, _("Moto ATV") + SCOOTERS = 23, _("Scooters") + KARTING = 24, _("Karting") + ATV = 25, _("ATV") + SNOWMOBILES = 26, _("Snowmobiles") class CarMake(models.Model, LocalizedNameMixin): @@ -121,7 +159,9 @@ class CarModel(models.Model, LocalizedNameMixin): 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") + id_car_model = models.ForeignKey( + CarModel, models.DO_NOTHING, db_column="id_car_model" + ) name = models.CharField(max_length=255, blank=True, null=True) arabic_name = models.CharField(max_length=255, blank=True, null=True) year_begin = models.IntegerField(blank=True, null=True) @@ -137,7 +177,9 @@ class CarSerie(models.Model, LocalizedNameMixin): 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") + id_car_serie = models.ForeignKey( + CarSerie, models.DO_NOTHING, db_column="id_car_serie" + ) name = models.CharField(max_length=255, blank=True, null=True) arabic_name = models.CharField(max_length=255, blank=True, null=True) start_production_year = models.IntegerField(blank=True, null=True) @@ -159,6 +201,7 @@ class CarEquipment(models.Model, LocalizedNameMixin): def __str__(self): return self.name + class Meta: verbose_name = "Equipment" @@ -167,7 +210,9 @@ 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) + id_parent = models.ForeignKey( + "self", models.DO_NOTHING, db_column="id_parent", blank=True, null=True + ) def __str__(self): return self.name @@ -179,7 +224,9 @@ class CarSpecification(models.Model, LocalizedNameMixin): 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") + 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) @@ -194,7 +241,9 @@ class CarOption(models.Model, LocalizedNameMixin): id_car_option = models.AutoField(primary_key=True) name = models.CharField(max_length=255, blank=True, null=True) arabic_name = models.CharField(max_length=255, blank=True, null=True) - id_parent = models.ForeignKey("self", models.DO_NOTHING, db_column="id_parent", blank=True, null=True) + id_parent = models.ForeignKey( + "self", models.DO_NOTHING, db_column="id_parent", blank=True, null=True + ) def __str__(self): return self.name @@ -205,8 +254,12 @@ class CarOption(models.Model, LocalizedNameMixin): class CarOptionValue(models.Model): id_car_option_value = models.AutoField(primary_key=True) - id_car_option = models.ForeignKey(CarOption, models.DO_NOTHING, db_column="id_car_option") - id_car_equipment = models.ForeignKey(CarEquipment, models.DO_NOTHING, db_column="id_car_equipment") + id_car_option = models.ForeignKey( + CarOption, models.DO_NOTHING, db_column="id_car_option" + ) + id_car_equipment = models.ForeignKey( + CarEquipment, models.DO_NOTHING, db_column="id_car_equipment" + ) value = models.CharField(max_length=500) unit = models.CharField(max_length=255, blank=True, null=True) is_base = models.IntegerField() @@ -218,27 +271,51 @@ class CarOptionValue(models.Model): verbose_name = "Option Value" +class CarTransferStatusChoices(models.TextChoices): + draft = "draft", _("Draft") + approved = "approved", _("Approved") + pending = "pending", _("Pending") + accepted = "accepted", _("Accepted") + success = "success", _("Success") + reject = "reject", _("Reject") + + class CarStatusChoices(models.TextChoices): AVAILABLE = "available", _("Available") SOLD = "sold", _("Sold") HOLD = "hold", _("Hold") DAMAGED = "damaged", _("Damaged") - RESERVED = "reserved", _("Reserved") - + RESERVED = "reserved", _("Reserved") + TRANSFER = "transfer", _("Transfer") class CarStockTypeChoices(models.TextChoices): NEW = "new", _("New") USED = "used", _("Used") + class AdditionalServices(models.Model, LocalizedNameMixin): name = models.CharField(max_length=255, verbose_name=_("Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) description = models.TextField(verbose_name=_("Description")) - price = models.DecimalField(max_digits=14, decimal_places=2, verbose_name=_("Price")) + price = models.DecimalField( + max_digits=14, decimal_places=2, verbose_name=_("Price") + ) taxable = models.BooleanField(default=False, verbose_name=_("taxable")) - uom = models.CharField(max_length=10, choices=UnitOfMeasure.choices, verbose_name=_("Unit of Measurement")) - dealer = models.ForeignKey("Dealer", on_delete=models.CASCADE, verbose_name=_("Dealer")) - item = models.OneToOneField(ItemModel, on_delete=models.CASCADE, verbose_name=_("Item"),null=True, blank=True) + uom = models.CharField( + max_length=10, + choices=UnitOfMeasure.choices, + verbose_name=_("Unit of Measurement"), + ) + dealer = models.ForeignKey( + "Dealer", on_delete=models.CASCADE, verbose_name=_("Dealer") + ) + item = models.OneToOneField( + ItemModel, + on_delete=models.CASCADE, + verbose_name=_("Item"), + null=True, + blank=True, + ) class Meta: verbose_name = _("Additional Services") @@ -325,13 +402,71 @@ class Car(models.Model): active_reservations = self.reservations.filter(reserved_until__gt=now()) return active_reservations.exists() + def get_transfer(self): + return self.transfer_logs.filter(active=True).first() @property def get_car_group(self): return f"{self.id_car_make.get_local_name} {self.id_car_model.get_local_name}" + +class CarTransfer(models.Model): + car = models.ForeignKey( + "Car", + on_delete=models.CASCADE, + related_name="transfer_logs", + verbose_name=_("Car"), + ) + from_dealer = models.ForeignKey( + "Dealer", + on_delete=models.CASCADE, + related_name="transfers_out", + verbose_name=_("From Dealer"), + ) + to_dealer = models.ForeignKey( + "Dealer", + on_delete=models.CASCADE, + related_name="transfers_in", + verbose_name=_("To Dealer"), + ) + transfer_date = models.DateTimeField( + auto_now_add=True, verbose_name=_("Transfer Date") + ) + quantity = models.IntegerField(verbose_name=_("Quantity"),default=1) + remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks")) + status = models.CharField( + CarTransferStatusChoices.choices, + max_length=10, + default=CarTransferStatusChoices.draft, + ) + is_approved = models.BooleanField(default=False) + active = models.BooleanField(default=True) + 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_price(self): + return self.quantity * self.car.finances.cost_price + class Meta: + verbose_name = _("Car Transfer Log") + verbose_name_plural = _("Car Transfer Logs") + + def __str__(self): + return f"{self.car} Transfer car from {self.from_dealer} to {self.to_dealer}" + + 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")) + 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")) @@ -340,53 +475,64 @@ class CarReservation(models.Model): return self.reserved_until > now() class Meta: - unique_together = ('car', 'reserved_until') - ordering = ['-reserved_at'] + 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')) - + 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"), + ) + @property def total(self): if self.additional_services.count() > 0: - return self.selling_price + sum(x.price for x in self.additional_services.all()) + return self.selling_price + sum( + x.price for x in self.additional_services.all() + ) return self.selling_price + @property def total_discount(self): if self.discount_amount > 0: return self.total - self.discount_amount return self.total - + @property def total_vat(self): return self.total_discount + self.vat_amount - - + @property def vat_amount(self): vat = VatRate.objects.filter(is_active=True).first() if vat: - return (self.total_discount * Decimal(vat.rate)).quantize(Decimal('0.01')) - return Decimal('0.00') + return (self.total_discount * Decimal(vat.rate)).quantize(Decimal("0.01")) + return Decimal("0.00") @property def revenue(self): - return self.selling_price-self.cost_price - - + return self.selling_price - self.cost_price + def __str__(self): return f"Car: {self.car}, Selling Price: {self.selling_price}" - # def save(self, *args, **kwargs): + # def save(self, *args, **kwargs): # self.full_clean() # try: # price_after_discount = self.selling_price - self.discount_amount @@ -446,7 +592,12 @@ class CarColors(models.Model): class CustomCard(models.Model): - car = models.OneToOneField(Car, on_delete=models.CASCADE, related_name='custom_cards', verbose_name=_("Car")) + 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")) @@ -460,39 +611,30 @@ class CustomCard(models.Model): class CarLocation(models.Model): car = models.OneToOneField( - Car, - on_delete=models.CASCADE, - related_name='location', - verbose_name=_("Car") + Car, on_delete=models.CASCADE, related_name="location", verbose_name=_("Car") ) owner = models.ForeignKey( - 'Dealer', + "Dealer", on_delete=models.CASCADE, - related_name='owned_cars', + related_name="owned_cars", verbose_name=_("Owner"), - help_text=_("Dealer who owns the car.") + help_text=_("Dealer who owns the car."), ) showroom = models.ForeignKey( - 'Dealer', + "Dealer", on_delete=models.CASCADE, - related_name='showroom_cars', + related_name="showroom_cars", verbose_name=_("Showroom"), - help_text=_("Dealer where the car is displayed (can be the owner).") + 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") + 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") @@ -519,16 +661,14 @@ class CarRegistration(models.Model): 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_type = registration_date = models.DateTimeField(verbose_name=_("Registration Date")) - registration_expiry_date = models.DateTimeField(verbose_name=_("Registration Expiry Date")) class Meta: verbose_name = _("Registration") verbose_name_plural = _("Registrations") def __str__(self): - return f"{self.text1} {self.text2} {self.text3} - {self.plate_number}" + return f"{self.plate_number} - {self.text1} {self.text2} {self.text3}" # TimestampedModel Abstract Class @@ -541,19 +681,28 @@ class TimestampedModel(models.Model): class Subscription(models.Model): - plan = models.ForeignKey("SubscriptionPlan", on_delete=models.CASCADE, related_name="subscriptions") + plan = models.ForeignKey( + "SubscriptionPlan", on_delete=models.CASCADE, related_name="subscriptions" + ) start_date = models.DateField(help_text="Date when the subscription starts") end_date = models.DateField(help_text="Date when the subscription ends") - users = models.ManyToManyField(User, through='SubscriptionUser') # many-to-many relationship with User model + users = models.ManyToManyField( + User, through="SubscriptionUser" + ) # many-to-many relationship with User model is_active = models.BooleanField(default=True) billing_cycle = models.CharField( max_length=10, - choices=[('monthly', 'Monthly'), ('annual', 'Annual')], - default='monthly', - help_text="Billing cycle for the subscription" + choices=[("monthly", "Monthly"), ("annual", "Annual")], + default="monthly", + help_text="Billing cycle for the subscription", ) - last_payment_date = models.DateField(null=True, blank=True, help_text="Date of the last payment made") - next_payment_date = models.DateField(null=True, blank=True, help_text="Date of the next payment due") + last_payment_date = models.DateField( + null=True, blank=True, help_text="Date of the last payment made" + ) + next_payment_date = models.DateField( + null=True, blank=True, help_text="Date of the next payment due" + ) + class Meta: verbose_name = _("Subscription") verbose_name_plural = _("Subscriptions") @@ -579,18 +728,30 @@ class SubscriptionUser(models.Model): class SubscriptionPlan(models.Model): - name = models.CharField(max_length=100, unique=True, help_text=_("Name of the subscription plan")) + name = models.CharField( + max_length=100, unique=True, help_text=_("Name of the subscription plan") + ) description = models.TextField() price = models.DecimalField(max_digits=10, decimal_places=2) - max_users = models.PositiveIntegerField(help_text=_("Maximum number of users allowed"), default=1) - max_inventory_size = models.PositiveIntegerField(help_text=_("Maximum number of cars in inventory"), default=50) + max_users = models.PositiveIntegerField( + help_text=_("Maximum number of users allowed"), default=1 + ) + max_inventory_size = models.PositiveIntegerField( + help_text=_("Maximum number of cars in inventory"), default=50 + ) support_level = models.CharField( max_length=50, - choices=[('basic', 'Basic Support'), ('priority', 'Priority Support'), ('dedicated', 'Dedicated Support')], - default='basic', - help_text="Level of support provided" + choices=[ + ("basic", "Basic Support"), + ("priority", "Priority Support"), + ("dedicated", "Dedicated Support"), + ], + default="basic", + help_text="Level of support provided", + ) + custom_features = models.JSONField( + blank=True, null=True, help_text=_("Additional features specific to this plan") ) - custom_features = models.JSONField(blank=True, null=True, help_text=_("Additional features specific to this plan")) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -604,26 +765,27 @@ class SubscriptionPlan(models.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) + 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")) - entity = models.ForeignKey(EntityModel, on_delete=models.SET_NULL, null=True, blank=True) + 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") + ) + entity = models.ForeignKey( + EntityModel, on_delete=models.SET_NULL, null=True, blank=True + ) joined_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Joined At")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) @@ -649,14 +811,14 @@ class Dealer(models.Model, LocalizedNameMixin): class Meta: verbose_name = _("Dealer") - verbose_name_plural = _("Dealers") + 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": @@ -670,18 +832,18 @@ class Dealer(models.Model, LocalizedNameMixin): # def get_root_dealer(self): # return self.parent_dealer if self.parent_dealer else self + ############################## # Additional staff types for later - # COORDINATOR = "coordinator", _("Coordinator") - # RECEPTIONIST = "receptionist", _("Receptionist") - # AGENT = "agent", _("Agent") - # TECHNICIAN = "technician", _("Technician") - # DRIVER = "driver", _("Driver") +# COORDINATOR = "coordinator", _("Coordinator") +# RECEPTIONIST = "receptionist", _("Receptionist") +# AGENT = "agent", _("Agent") +# TECHNICIAN = "technician", _("Technician") +# DRIVER = "driver", _("Driver") ############################## - class StaffTypes(models.TextChoices): MANAGER = "manager", _("Manager") INVENTORY = "inventory", _("Inventory") @@ -698,7 +860,9 @@ class Staff(models.Model, LocalizedNameMixin): name = models.CharField(max_length=255, verbose_name=_("Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) - staff_type = models.CharField(choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type")) + staff_type = models.CharField( + choices=StaffTypes.choices, max_length=255, verbose_name=_("Staff Type") + ) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) @@ -726,6 +890,7 @@ class Sources(models.TextChoices): YOUTUBE = "youtube", _("Youtube") CAMPAIGN = "campaign", _("Campaign") + class Channel(models.TextChoices): WALK_IN = "walk_in", _("Walk In") TOLL_FREE = "toll_free", _("Toll Free") @@ -741,6 +906,7 @@ class Status(models.TextChoices): QUALIFIED = "qualified", _("Qualified") CANCELED = "canceled", _("Canceled") + class Title(models.TextChoices): MR = "mr", _("Mr") MRS = "mrs", _("Mrs") @@ -753,6 +919,7 @@ class Title(models.TextChoices): COMPANY = "company", _("Company") NA = "na", _("N/A") + class ActionChoices(models.TextChoices): CALL = "call", _("Call") SMS = "sms", _("SMS") @@ -769,6 +936,7 @@ class ActionChoices(models.TextChoices): CREATE_INVOICE = "create_invoice", _("Create Invoice") CANCEL_INVOICE = "cancel_invoice", _("Cancel Invoice") + class Stage(models.TextChoices): PROSPECT = "prospect", _("Prospect") PROPOSAL = "proposal", _("Proposal") @@ -776,6 +944,7 @@ class Stage(models.TextChoices): CLOSED_WON = "closed_won", _("Closed Won") CLOSED_LOST = "closed_lost", _("Closed Lost") + class Priority(models.TextChoices): LOW = "low", _("Low") MEDIUM = "medium", _("Medium") @@ -783,17 +952,33 @@ class Priority(models.TextChoices): class Customer(models.Model): - dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE,related_name="customers") - title = models.CharField(choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title")) + dealer = models.ForeignKey( + Dealer, on_delete=models.CASCADE, related_name="customers" + ) + title = models.CharField( + choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title") + ) 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")) + 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")) - gender = models.CharField(choices=[('m', _('Male')), ('f', _('Female'))], max_length=1, verbose_name=_("Gender")) + gender = models.CharField( + choices=[("m", _("Male")), ("f", _("Female"))], + max_length=1, + verbose_name=_("Gender"), + ) dob = models.DateField(verbose_name=_("Date of Birth")) 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")) + 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")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) @@ -811,33 +996,48 @@ class Customer(models.Model): class Organization(models.Model, LocalizedNameMixin): - dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='organizations') + 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")) + 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")) + 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") + ) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) 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') + 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, unique=True, verbose_name=_("ID Number")) - phone_number = PhoneNumberField(region='SA', verbose_name=_("Phone Number")) + id_number = models.CharField( + max_length=10, unique=True, verbose_name=_("ID Number") + ) + phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) email = models.EmailField(max_length=255, verbose_name=_("Email Address")) - address = models.CharField(max_length=200, blank=True, null=True, verbose_name=_("Address")) - organization = models.ManyToManyField(Organization, related_name='representatives') + 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") @@ -849,18 +1049,57 @@ class Representative(models.Model, LocalizedNameMixin): class Lead(models.Model): dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="leads") - customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="leads") - id_car_make = models.ForeignKey(CarMake, on_delete=models.DO_NOTHING, blank=True, null=True, verbose_name=_("Make")) - id_car_model = models.ForeignKey(CarModel, on_delete=models.DO_NOTHING, blank=True, null=True, verbose_name=_("Model")) - year = models.PositiveSmallIntegerField(verbose_name=_("Year"), blank=True, null=True) - source = models.CharField(max_length=50, choices=Sources.choices, verbose_name=_("Source")) - channel = models.CharField(max_length=50, choices=Channel.choices, verbose_name=_("Channel")) + customer = models.ForeignKey( + Customer, on_delete=models.CASCADE, related_name="leads" + ) + id_car_make = models.ForeignKey( + CarMake, + on_delete=models.DO_NOTHING, + blank=True, + null=True, + verbose_name=_("Make"), + ) + id_car_model = models.ForeignKey( + CarModel, + on_delete=models.DO_NOTHING, + blank=True, + null=True, + verbose_name=_("Model"), + ) + year = models.PositiveSmallIntegerField( + verbose_name=_("Year"), blank=True, null=True + ) + source = models.CharField( + max_length=50, choices=Sources.choices, verbose_name=_("Source") + ) + channel = models.CharField( + max_length=50, choices=Channel.choices, verbose_name=_("Channel") + ) city = models.CharField(max_length=50, verbose_name=_("City")) - staff = models.ForeignKey(Staff, on_delete=models.SET_NULL, blank=True, null=True, related_name="assigned", verbose_name=_("Assigned")) - priority = models.CharField(max_length=10, choices=Priority.choices, default=Priority.MEDIUM, - verbose_name=_("Priority")) - status = models.CharField(max_length=50, choices=Status.choices, verbose_name=_("Status"), db_index=True, default=Status.NEW) - created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"), db_index=True) + staff = models.ForeignKey( + Staff, + on_delete=models.SET_NULL, + blank=True, + null=True, + related_name="assigned", + verbose_name=_("Assigned"), + ) + priority = models.CharField( + max_length=10, + choices=Priority.choices, + default=Priority.MEDIUM, + verbose_name=_("Priority"), + ) + status = models.CharField( + max_length=50, + choices=Status.choices, + verbose_name=_("Status"), + db_index=True, + default=Status.NEW, + ) + created = models.DateTimeField( + auto_now_add=True, verbose_name=_("Created"), db_index=True + ) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) class Meta: @@ -868,14 +1107,22 @@ class Lead(models.Model): verbose_name_plural = _("Leads") def __str__(self): - return self.customer.get_full_name + return f"{self.first_name} {self.last_name}" class LeadStatusHistory(models.Model): - lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name="status_history") - old_status = models.CharField(max_length=50, choices=Status.choices, verbose_name=_("Old Status")) - new_status = models.CharField(max_length=50, choices=Status.choices, verbose_name=_("New Status")) - changed_by = models.ForeignKey(Staff, on_delete=models.DO_NOTHING, related_name="status_changes") + lead = models.ForeignKey( + Lead, on_delete=models.CASCADE, related_name="status_history" + ) + old_status = models.CharField( + max_length=50, choices=Status.choices, verbose_name=_("Old Status") + ) + new_status = models.CharField( + max_length=50, choices=Status.choices, verbose_name=_("New Status") + ) + changed_by = models.ForeignKey( + Staff, on_delete=models.DO_NOTHING, related_name="status_changes" + ) changed_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Changed At")) class Meta: @@ -892,12 +1139,31 @@ def validate_probability(value): class Opportunity(models.Model): - dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="opportunities") - customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="opportunities") - car = models.ForeignKey(Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car")) - stage = models.CharField(max_length=20, choices=Stage.choices, verbose_name=_("Stage")) - status = models.CharField(max_length=20, choices=Status.choices, verbose_name=_("Status"), default=Status.NEW) - staff = models.ForeignKey(Staff, on_delete=models.SET_NULL, null=True, related_name="owner", verbose_name=_("Owner")) + dealer = models.ForeignKey( + Dealer, on_delete=models.CASCADE, related_name="opportunities" + ) + customer = models.ForeignKey( + Customer, on_delete=models.CASCADE, related_name="opportunities" + ) + car = models.ForeignKey( + Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car") + ) + stage = models.CharField( + max_length=20, choices=Stage.choices, verbose_name=_("Stage") + ) + status = models.CharField( + max_length=20, + choices=Status.choices, + verbose_name=_("Status"), + default=Status.NEW, + ) + staff = models.ForeignKey( + Staff, + on_delete=models.SET_NULL, + null=True, + related_name="owner", + verbose_name=_("Owner"), + ) probability = models.PositiveIntegerField(validators=[validate_probability]) closing_date = models.DateField(verbose_name=_("Closing Date")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) @@ -915,9 +1181,11 @@ class Opportunity(models.Model): class Notes(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") note = models.TextField(verbose_name=_("Note")) - created_by = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name="notes_created") + created_by = models.ForeignKey( + User, on_delete=models.DO_NOTHING, related_name="notes_created" + ) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) @@ -932,10 +1200,14 @@ class Notes(models.Model): class Activity(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() - content_object = GenericForeignKey('content_type', 'object_id') - activity_type = models.CharField(max_length=50, choices=ActionChoices.choices, verbose_name=_("Activity Type")) + content_object = GenericForeignKey("content_type", "object_id") + activity_type = models.CharField( + max_length=50, choices=ActionChoices.choices, verbose_name=_("Activity Type") + ) notes = models.TextField(blank=True, null=True, verbose_name=_("Notes")) - created_by = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name="activities_created") + created_by = models.ForeignKey( + User, on_delete=models.DO_NOTHING, related_name="activities_created" + ) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) @@ -948,7 +1220,9 @@ class Activity(models.Model): class Notification(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notifications") + user = models.ForeignKey( + User, on_delete=models.CASCADE, related_name="notifications" + ) message = models.CharField(max_length=255, verbose_name=_("Message")) is_read = models.BooleanField(default=False, verbose_name=_("Is Read")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) @@ -956,7 +1230,7 @@ class Notification(models.Model): class Meta: verbose_name = _("Notification") verbose_name_plural = _("Notifications") - ordering = ['-created'] + ordering = ["-created"] def __str__(self): return self.message @@ -991,7 +1265,6 @@ class Vendor(models.Model, LocalizedNameMixin): return self.name - class SaleQuotation(models.Model): quotation_number = models.CharField(max_length=10, unique=True) @@ -1003,7 +1276,7 @@ class SaleQuotation(models.Model): ] dealer = models.ForeignKey( Dealer, on_delete=models.CASCADE, related_name="sales", null=True - ) + ) customer = models.ForeignKey( Customer, on_delete=models.CASCADE, @@ -1023,35 +1296,47 @@ class SaleQuotation(models.Model): ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) - + posted = models.BooleanField(default=False) - payment_id = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Payment ID")) + payment_id = models.CharField( + max_length=255, null=True, blank=True, verbose_name=_("Payment ID") + ) is_paid = models.BooleanField(default=False) - date_draft = models.DateTimeField(null=True, blank=True, verbose_name=_('Draft Date')) - date_in_review = models.DateTimeField(null=True, blank=True, verbose_name=_('In Review Date')) - date_approved = models.DateTimeField(null=True, blank=True, verbose_name=_('Approved Date')) - date_paid = models.DateTimeField(null=True, blank=True, verbose_name=_('Paid Date')) - date_void = models.DateTimeField(null=True, blank=True, verbose_name=_('Void Date')) - date_canceled = models.DateTimeField(null=True, blank=True, verbose_name=_('Canceled Date')) + date_draft = models.DateTimeField( + null=True, blank=True, verbose_name=_("Draft Date") + ) + date_in_review = models.DateTimeField( + null=True, blank=True, verbose_name=_("In Review Date") + ) + date_approved = models.DateTimeField( + null=True, blank=True, verbose_name=_("Approved Date") + ) + date_paid = models.DateTimeField(null=True, blank=True, verbose_name=_("Paid Date")) + date_void = models.DateTimeField(null=True, blank=True, verbose_name=_("Void Date")) + date_canceled = models.DateTimeField( + null=True, blank=True, verbose_name=_("Canceled Date") + ) @property def total_quantity(self): - total_quantity = self.quotation_cars.aggregate(total=Sum('quantity'))['total'] + 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'))) + 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": @@ -1068,11 +1353,11 @@ class SaleQuotation(models.Model): 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) @@ -1080,7 +1365,7 @@ class SaleQuotation(models.Model): @classmethod def _get_quotation_number(cls): - last_quotation = cls.objects.all().order_by('id').last() + last_quotation = cls.objects.all().order_by("id").last() if last_quotation: last_quotation_number = int(last_quotation.quotation_number) else: @@ -1095,16 +1380,13 @@ class SaleQuotationCar(models.Model): related_name="quotation_cars", verbose_name=_("Quotation"), ) - car = models.ForeignKey( - Car, - on_delete=models.CASCADE, - verbose_name=_("Car") - ) + 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): """ @@ -1133,6 +1415,7 @@ class SaleQuotationCar(models.Model): if not self.car.finances: return Decimal("0.00") return self.car.finances.selling_price * self.quantity + @property def total_vat(self): """ @@ -1165,16 +1448,24 @@ class SalesOrder(models.Model): class Payment(models.Model): METHOD_CHOICES = [ - ('cash', _('cash')), - ('credit', _('credit')), - ('transfer', _('transfer')), - ('debit', _('debit')), - ('SADAD', _('SADAD')), + ("cash", _("cash")), + ("credit", _("credit")), + ("transfer", _("transfer")), + ("debit", _("debit")), + ("SADAD", _("SADAD")), ] - quotation = models.ForeignKey(SaleQuotation, on_delete=models.CASCADE, related_name="payments") - amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("amount")) - payment_method = models.CharField(choices=METHOD_CHOICES, max_length=50, verbose_name=_("method")) - reference_number = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("reference number")) + quotation = models.ForeignKey( + SaleQuotation, on_delete=models.CASCADE, related_name="payments" + ) + amount = models.DecimalField( + max_digits=10, decimal_places=2, verbose_name=_("amount") + ) + payment_method = models.CharField( + choices=METHOD_CHOICES, max_length=50, verbose_name=_("method") + ) + reference_number = models.CharField( + max_length=100, null=True, blank=True, verbose_name=_("reference number") + ) payment_date = models.DateField(auto_now_add=True, verbose_name=_("date")) # def save(self, *args, **kwargs): @@ -1193,8 +1484,12 @@ class Payment(models.Model): class Refund(models.Model): - payment = models.OneToOneField(Payment, on_delete=models.CASCADE, related_name="refund") - amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name=_("amount")) + payment = models.OneToOneField( + Payment, on_delete=models.CASCADE, related_name="refund" + ) + amount = models.DecimalField( + max_digits=10, decimal_places=2, verbose_name=_("amount") + ) reason = models.TextField(blank=True, verbose_name=_("reason")) refund_date = models.DateField(auto_now_add=True, verbose_name=_("refund date")) @@ -1214,9 +1509,7 @@ class UserActivityLog(models.Model): class Meta: verbose_name = "User Activity Log" verbose_name_plural = "User Activity Logs" - ordering = ['-timestamp'] + ordering = ["-timestamp"] def __str__(self): return f"{self.user.email} - {self.action} - {self.timestamp}" - - From 45a937a0b3b6c33c81496ed591a2b85ade84c443 Mon Sep 17 00:00:00 2001 From: gitea Date: Wed, 22 Jan 2025 12:34:08 +0000 Subject: [PATCH 3/3] rebased --- inventory/forms.py | 9 +++++++++ .../migrations/0007_delete_cartransferlog.py | 16 ---------------- templates/header.html | 5 ++--- templates/ledger/bills/bill_list.html | 9 ++++++++- 4 files changed, 19 insertions(+), 20 deletions(-) delete mode 100644 inventory/migrations/0007_delete_cartransferlog.py diff --git a/inventory/forms.py b/inventory/forms.py index f5b397b6..8dcd403e 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -16,6 +16,7 @@ from .models import ( Vendor, Customer, Car, + CarTransfer, CarFinance, CustomCard, CarRegistration, @@ -241,6 +242,14 @@ class CarLocationForm(forms.ModelForm): "description": forms.Textarea(attrs={"rows": 2, "class": "form-control"}), } +class CarTransferForm(forms.ModelForm): + class Meta: + model = CarTransfer + fields = ["car", "to_dealer", "remarks"] + widgets = { + "remarks": forms.Textarea(attrs={"rows": 2, "class": "form-control"}), + } + # Custom Card Form class CustomCardForm(forms.ModelForm): diff --git a/inventory/migrations/0007_delete_cartransferlog.py b/inventory/migrations/0007_delete_cartransferlog.py deleted file mode 100644 index c926ee10..00000000 --- a/inventory/migrations/0007_delete_cartransferlog.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 5.1.5 on 2025-01-21 12:57 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0006_cartransferlog_delete_invoicemodelbase'), - ] - - operations = [ - migrations.DeleteModel( - name='CarTransferLog', - ), - ] diff --git a/templates/header.html b/templates/header.html index f97c839e..32018185 100644 --- a/templates/header.html +++ b/templates/header.html @@ -300,8 +300,7 @@
- {% include "notifications.html" %} - + {% include "notifications.html" %}