diff --git a/db.sqlite3-shm b/db.sqlite3-shm deleted file mode 100644 index 87689361..00000000 Binary files a/db.sqlite3-shm and /dev/null differ diff --git a/db.sqlite3-wal b/db.sqlite3-wal deleted file mode 100644 index e69de29b..00000000 diff --git a/db.sqlite3.backup b/db.sqlite3.backup new file mode 100644 index 00000000..3c7db0c6 Binary files /dev/null and b/db.sqlite3.backup differ diff --git a/haikalbot/migrations/0001_initial.py b/haikalbot/migrations/0001_initial.py index b85002cb..9359116f 100644 --- a/haikalbot/migrations/0001_initial.py +++ b/haikalbot/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.4 on 2025-01-12 17:20 +# Generated by Django 4.2.17 on 2025-01-21 13:59 from django.db import migrations, models diff --git a/haikalbot/migrations/0002_initial.py b/haikalbot/migrations/0002_initial.py index 46ca49a3..42e4199a 100644 --- a/haikalbot/migrations/0002_initial.py +++ b/haikalbot/migrations/0002_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.4 on 2025-01-12 17:20 +# Generated by Django 4.2.17 on 2025-01-21 13:59 from django.db import migrations, models import django.db.models.deletion @@ -9,8 +9,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('inventory', '0001_initial'), ('haikalbot', '0001_initial'), + ('inventory', '0001_initial'), ] operations = [ diff --git a/inventory/forms.py b/inventory/forms.py index f2160e43..f5b397b6 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -9,6 +9,7 @@ from phonenumber_field.phonenumber import PhoneNumber from .mixins import AddClassMixin from django.forms.models import inlineformset_factory from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase +from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase from .models import ( Dealer, # Branch, @@ -31,7 +32,7 @@ from .models import ( Staff, Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel ) -from django_ledger.models import ItemModel, InvoiceModel +from django_ledger.models import ItemModel, InvoiceModel,BillModel from django.forms import ModelMultipleChoiceField, ValidationError, DateInput from django.utils.translation import gettext_lazy as _ import django_tables2 as tables @@ -543,7 +544,10 @@ class ItemForm(forms.Form): class PaymentForm(forms.Form): invoice = forms.ModelChoiceField( - queryset=InvoiceModel.objects.all(), label="Invoice", required=True + queryset=InvoiceModel.objects.all(), label="Invoice", required=False + ) + bill = forms.ModelChoiceField( + queryset=BillModel.objects.all(), label="Bill", required=False ) amount = forms.DecimalField(label="Amount", required=True) payment_method = forms.ChoiceField( @@ -561,17 +565,19 @@ class PaymentForm(forms.Form): def clean_amount(self): invoice = self.cleaned_data['invoice'] + bill = self.cleaned_data['bill'] + model = invoice if invoice else bill amount = self.cleaned_data['amount'] - if amount < invoice.amount_due: - raise forms.ValidationError("Payment amount is greater than invoice amount due") + if amount + model.amount_paid > model.amount_due: + raise forms.ValidationError("Payment amount is greater than amount due") if amount <= 0: raise forms.ValidationError("Payment amount must be greater than 0") - if invoice.amount_due == invoice.amount_paid or invoice.invoice_status == "paid": + if model.is_paid(): raise forms.ValidationError("Invoice is already paid") - if amount > invoice.amount_due: - raise forms.ValidationError("Payment amount is greater than invoice amount due") - return amount - + if amount > model.amount_due: + raise forms.ValidationError("Payment amount is greater than amount due") + return amount + class EmailForm(forms.Form): @@ -628,4 +634,13 @@ class InvoiceModelCreateForm(InvoiceModelCreateFormBase): self.fields['cash_account'].widget = forms.HiddenInput() self.fields['prepaid_account'].widget = forms.HiddenInput() self.fields['unearned_account'].widget = forms.HiddenInput() - self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'})) \ No newline at end of file + self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'})) + +class BillModelCreateForm(BillModelCreateFormBase): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields['cash_account'].widget = forms.HiddenInput() + self.fields['prepaid_account'].widget = forms.HiddenInput() + self.fields['unearned_account'].widget = forms.HiddenInput() + self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'})) diff --git a/inventory/migrations/0001_initial.py b/inventory/migrations/0001_initial.py index 94b54d6e..db2f426b 100644 --- a/inventory/migrations/0001_initial.py +++ b/inventory/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.4 on 2025-01-12 17:20 +# Generated by Django 4.2.17 on 2025-01-21 13:59 from decimal import Decimal from django.conf import settings @@ -15,6 +15,7 @@ class Migration(migrations.Migration): dependencies = [ ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), ] @@ -42,7 +43,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('vin', models.CharField(max_length=17, unique=True, verbose_name='VIN')), ('year', models.IntegerField(verbose_name='Year')), - ('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved')], default='available', max_length=10, verbose_name='Status')), + ('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved'), ('transfer', 'Transfer')], default='available', max_length=10, verbose_name='Status')), ('stock_type', models.CharField(choices=[('new', 'New'), ('used', 'Used')], default='new', max_length=10, verbose_name='Stock Type')), ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), ('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')), @@ -74,138 +75,13 @@ class Migration(migrations.Migration): ('arabic_name', models.CharField(blank=True, max_length=255, null=True)), ('logo', models.ImageField(blank=True, null=True, upload_to='car_make', verbose_name='logo')), ('is_sa_import', models.BooleanField(default=False)), - ('car_type', models.SmallIntegerField(choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')])), + ('car_type', models.SmallIntegerField(blank=True, choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')], null=True)), ], options={ 'verbose_name': 'Make', }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), - migrations.CreateModel( - name='ExteriorColors', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='Name')), - ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), - ('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')), - ], - options={ - 'verbose_name': 'Exterior Colors', - 'verbose_name_plural': 'Exterior Colors', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='InteriorColors', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='Name')), - ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), - ('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')), - ], - options={ - 'verbose_name': 'Interior Colors', - 'verbose_name_plural': 'Interior Colors', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='Payment', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')), - ('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('SADAD', 'SADAD')], max_length=50, verbose_name='method')), - ('reference_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='reference number')), - ('payment_date', models.DateField(auto_now_add=True, verbose_name='date')), - ], - options={ - 'verbose_name': 'payment', - 'verbose_name_plural': 'payments', - }, - ), - migrations.CreateModel( - name='SubscriptionPlan', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='Name of the subscription plan', max_length=100, unique=True)), - ('description', models.TextField()), - ('price', models.DecimalField(decimal_places=2, max_digits=10)), - ('max_users', models.PositiveIntegerField(default=1, help_text='Maximum number of users allowed')), - ('max_inventory_size', models.PositiveIntegerField(default=50, help_text='Maximum number of cars in inventory')), - ('support_level', models.CharField(choices=[('basic', 'Basic Support'), ('priority', 'Priority Support'), ('dedicated', 'Dedicated Support')], default='basic', help_text='Level of support provided', max_length=50)), - ('custom_features', models.JSONField(blank=True, help_text='Additional features specific to this plan', null=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ], - options={ - 'verbose_name': 'Subscription Plan', - 'verbose_name_plural': 'Subscription Plans', - }, - ), - migrations.CreateModel( - name='VatRate', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('rate', models.DecimalField(decimal_places=2, default=Decimal('0.15'), max_digits=5)), - ('is_active', models.BooleanField(default=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ], - ), - migrations.CreateModel( - name='Activity', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('object_id', models.PositiveIntegerField()), - ('activity_type', models.CharField(choices=[('call', 'Call'), ('sms', 'SMS'), ('email', 'Email'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('add_car', 'Add Car'), ('reserve_car', 'Reserve Car'), ('remove_car', 'Remove Car'), ('create_quotation', 'Create Quotation'), ('cancel_quotation', 'Cancel Quotation'), ('create_order', 'Create Order'), ('cancel_order', 'Cancel Order'), ('create_invoice', 'Create Invoice'), ('cancel_invoice', 'Cancel Invoice')], max_length=50, verbose_name='Activity Type')), - ('notes', models.TextField(blank=True, null=True, verbose_name='Notes')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), - ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Activity', - 'verbose_name_plural': 'Activities', - }, - ), - migrations.CreateModel( - name='AdditionalServices', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('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(decimal_places=2, max_digits=14, verbose_name='Price')), - ('taxable', models.BooleanField(default=False, verbose_name='taxable')), - ('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')), - ('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item')), - ], - options={ - 'verbose_name': 'Additional Services', - 'verbose_name_plural': 'Additional Services', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='CarFinance', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('cost_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Cost Price')), - ('selling_price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Selling Price')), - ('discount_amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=14, verbose_name='Discount Amount')), - ('additional_services', models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices')), - ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')), - ], - options={ - 'verbose_name': 'Car Financial Details', - 'verbose_name_plural': 'Car Financial Details', - }, - ), - migrations.AddField( - model_name='car', - name='id_car_make', - field=models.ForeignKey(blank=True, db_column='id_car_make', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make'), - ), migrations.CreateModel( name='CarModel', fields=[ @@ -274,7 +150,6 @@ class Migration(migrations.Migration): ('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')), ('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')), ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')), - ('city', models.CharField(blank=True, max_length=255, verbose_name='City')), ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), @@ -310,51 +185,46 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='CarLocation', + name='ExteriorColors', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('description', models.TextField(blank=True, help_text='Optional description about the showroom placement.', null=True, verbose_name='Description')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Updated')), - ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='location', to='inventory.car', verbose_name='Car')), - ('owner', models.ForeignKey(help_text='Dealer who owns the car.', on_delete=django.db.models.deletion.CASCADE, related_name='owned_cars', to='inventory.dealer', verbose_name='Owner')), - ('showroom', models.ForeignKey(help_text='Dealer where the car is displayed (can be the owner).', on_delete=django.db.models.deletion.CASCADE, related_name='showroom_cars', to='inventory.dealer', verbose_name='Showroom')), + ('name', models.CharField(max_length=255, verbose_name='Name')), + ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), + ('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')), ], options={ - 'verbose_name': 'Car Location', - 'verbose_name_plural': 'Car Locations', + 'verbose_name': 'Exterior Colors', + 'verbose_name_plural': 'Exterior Colors', }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), - migrations.AddField( - model_name='car', - name='dealer', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'), - ), - migrations.AddField( - model_name='additionalservices', - name='dealer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'), + migrations.CreateModel( + name='InteriorColors', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='Name')), + ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), + ('rgb', models.CharField(blank=True, max_length=24, null=True, verbose_name='RGB')), + ], + options={ + 'verbose_name': 'Interior Colors', + 'verbose_name_plural': 'Interior Colors', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), migrations.CreateModel( name='Lead', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], max_length=20, verbose_name='Title')), - ('first_name', models.CharField(max_length=50, verbose_name='First Name')), - ('last_name', models.CharField(max_length=50, verbose_name='Last Name')), - ('email', models.EmailField(db_index=True, max_length=254, unique=True, verbose_name='Email')), - ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), - ('salary', models.PositiveIntegerField(blank=True, null=True, verbose_name='Salary')), - ('obligations', models.PositiveIntegerField(blank=True, null=True, verbose_name='Obligations')), ('year', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Year')), ('source', models.CharField(choices=[('referrals', 'Referrals'), ('whatsapp', 'WhatsApp'), ('showroom', 'Showroom'), ('tiktok', 'TikTok'), ('instagram', 'Instagram'), ('x', 'X'), ('facebook', 'Facebook'), ('motory', 'Motory'), ('influencers', 'Influencers'), ('youtube', 'Youtube'), ('campaign', 'Campaign')], max_length=50, verbose_name='Source')), ('channel', models.CharField(choices=[('walk_in', 'Walk In'), ('toll_free', 'Toll Free'), ('website', 'Website'), ('email', 'Email'), ('form', 'Form')], max_length=50, verbose_name='Channel')), - ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), ('city', models.CharField(max_length=50, verbose_name='City')), ('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=10, verbose_name='Priority')), ('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], db_index=True, default='new', max_length=50, verbose_name='Status')), ('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.customer')), ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.dealer')), ('id_car_make', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make')), ('id_car_model', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model')), @@ -364,62 +234,6 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Leads', }, ), - migrations.CreateModel( - name='Customer', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], default='na', max_length=10, verbose_name='Title')), - ('first_name', models.CharField(max_length=50, verbose_name='First Name')), - ('middle_name', models.CharField(blank=True, max_length=50, 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')), - ('dob', models.DateField(verbose_name='Date of Birth')), - ('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')), - ('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')), - ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')), - ('city', models.CharField(blank=True, max_length=255, verbose_name='City')), - ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')), - ('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='converted', to='inventory.lead', verbose_name='Lead')), - ], - options={ - 'verbose_name': 'Customer', - 'verbose_name_plural': 'Customers', - }, - ), - migrations.CreateModel( - name='Notes', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('object_id', models.PositiveIntegerField()), - ('note', models.TextField(verbose_name='Note')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), - ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), - ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Note', - 'verbose_name_plural': 'Notes', - }, - ), - migrations.CreateModel( - name='Notification', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('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')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Notification', - 'verbose_name_plural': 'Notifications', - 'ordering': ['-created'], - }, - ), migrations.CreateModel( name='Organization', fields=[ @@ -431,7 +245,8 @@ class Migration(migrations.Migration): ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), ('logo', models.ImageField(blank=True, null=True, upload_to='logos', verbose_name='Logo')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to='inventory.dealer')), ], options={ @@ -478,98 +293,6 @@ class Migration(migrations.Migration): ('dealer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer')), ], ), - migrations.AddField( - model_name='payment', - name='quotation', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='inventory.salequotation'), - ), - migrations.CreateModel( - name='SaleQuotationCar', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quantity', models.PositiveIntegerField(default=1, verbose_name='Quantity')), - ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.car', verbose_name='Car')), - ('quotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotation_cars', to='inventory.salequotation', verbose_name='Quotation')), - ], - ), - migrations.CreateModel( - name='SalesOrder', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('total_amount', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Total Amount')), - ('quotation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='sales_order', to='inventory.salequotation', verbose_name='Quotation')), - ], - ), - migrations.CreateModel( - name='Staff', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='Name')), - ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), - ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), - ('staff_type', models.CharField(choices=[('manager', 'Manager'), ('inventory', 'Inventory'), ('accountant', 'Accountant'), ('sales', 'Sales'), ('coordinator', 'Coordinator'), ('receptionist', 'Receptionist'), ('agent', 'Agent')], 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')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='inventory.dealer')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Staff', - 'verbose_name_plural': 'Staff', - 'permissions': [], - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - managers=[ - ('objects', inventory.models.StaffUserManager()), - ], - ), - migrations.CreateModel( - name='Opportunity', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('stage', models.CharField(choices=[('prospect', 'Prospect'), ('proposal', 'Proposal'), ('negotiation', 'Negotiation'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost')], max_length=20, verbose_name='Stage')), - ('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], default='new', max_length=20, verbose_name='Status')), - ('probability', models.PositiveIntegerField(validators=[inventory.models.validate_probability])), - ('closing_date', models.DateField(verbose_name='Closing Date')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), - ('closed', models.BooleanField(default=False, verbose_name='Closed')), - ('car', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.car', verbose_name='Car')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.dealer')), - ('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner')), - ], - options={ - 'verbose_name': 'Opportunity', - 'verbose_name_plural': 'Opportunities', - }, - ), - migrations.CreateModel( - name='LeadStatusHistory', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('old_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status')), - ('new_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status')), - ('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='Changed At')), - ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')), - ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')), - ], - options={ - 'verbose_name': 'Lead Status History', - 'verbose_name_plural': 'Lead Status Histories', - }, - ), - migrations.AddField( - model_name='lead', - name='assigned', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'), - ), - migrations.AddField( - model_name='customer', - name='staff', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_staff', to='inventory.staff', verbose_name='Staff'), - ), migrations.CreateModel( name='Subscription', fields=[ @@ -719,7 +442,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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')), + ('id_number', models.CharField(max_length=10, unique=True, verbose_name='ID Number')), ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), ('email', models.EmailField(max_length=255, verbose_name='Email Address')), ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), @@ -820,39 +543,14 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='lead', - name='assigned', + name='staff', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'), ), - migrations.AddField( - model_name='lead', - name='dealer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.dealer'), - ), - migrations.AddField( - model_name='lead', - name='id_car_make', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake', verbose_name='Make'), - ), - migrations.AddField( - model_name='lead', - name='id_car_model', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model'), - ), migrations.AddField( model_name='customer', name='dealer', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer'), ), - migrations.AddField( - model_name='customer', - name='lead', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='converted', to='inventory.lead', verbose_name='Lead'), - ), - migrations.AddField( - model_name='customer', - name='staff', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_staff', to='inventory.staff', verbose_name='Staff'), - ), migrations.CreateModel( name='CustomCard', fields=[ @@ -881,6 +579,27 @@ class Migration(migrations.Migration): }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), + migrations.CreateModel( + name='CarTransfer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('transfer_date', models.DateTimeField(auto_now_add=True, verbose_name='Transfer Date')), + ('quantity', models.IntegerField(default=1, verbose_name='Quantity')), + ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), + ('status', models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accepted', 'Accepted'), ('success', 'Success'), ('reject', 'Reject')])), + ('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')), + ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_logs', to='inventory.car', verbose_name='Car')), + ('from_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_out', to='inventory.dealer', verbose_name='From Dealer')), + ('to_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_in', to='inventory.dealer', verbose_name='To Dealer')), + ], + options={ + 'verbose_name': 'Car Transfer Log', + 'verbose_name_plural': 'Car Transfer Logs', + }, + ), migrations.CreateModel( name='CarSpecificationValue', fields=[ diff --git a/inventory/migrations/0002_alter_carmake_car_type.py b/inventory/migrations/0002_alter_carmake_car_type.py deleted file mode 100644 index 06e7827a..00000000 --- a/inventory/migrations/0002_alter_carmake_car_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-01-13 10:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='carmake', - name='car_type', - field=models.SmallIntegerField(blank=True, choices=[], null=True), - ), - ] diff --git a/inventory/migrations/0003_alter_carmake_car_type.py b/inventory/migrations/0003_alter_carmake_car_type.py deleted file mode 100644 index c701974c..00000000 --- a/inventory/migrations/0003_alter_carmake_car_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.17 on 2025-01-14 12:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0002_alter_carmake_car_type'), - ] - - operations = [ - migrations.AlterField( - model_name='carmake', - name='car_type', - field=models.SmallIntegerField(blank=True, choices=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')], null=True), - ), - ] diff --git a/inventory/migrations/0004_rename_assigned_lead_staff_remove_customer_city_and_more.py b/inventory/migrations/0004_rename_assigned_lead_staff_remove_customer_city_and_more.py deleted file mode 100644 index 74725067..00000000 --- a/inventory/migrations/0004_rename_assigned_lead_staff_remove_customer_city_and_more.py +++ /dev/null @@ -1,90 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-17 00:20 - -import django.db.models.deletion -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0003_alter_carmake_car_type'), - ] - - operations = [ - migrations.RenameField( - model_name='lead', - old_name='assigned', - new_name='staff', - ), - migrations.RemoveField( - model_name='customer', - name='city', - ), - migrations.RemoveField( - model_name='customer', - name='lead', - ), - migrations.RemoveField( - model_name='customer', - name='staff', - ), - migrations.RemoveField( - model_name='lead', - name='address', - ), - migrations.RemoveField( - model_name='lead', - name='email', - ), - migrations.RemoveField( - model_name='lead', - name='first_name', - ), - migrations.RemoveField( - model_name='lead', - name='last_name', - ), - migrations.RemoveField( - model_name='lead', - name='obligations', - ), - migrations.RemoveField( - model_name='lead', - name='phone_number', - ), - migrations.RemoveField( - model_name='lead', - name='salary', - ), - migrations.RemoveField( - model_name='lead', - name='title', - ), - migrations.RemoveField( - model_name='organization', - name='created_at', - ), - migrations.AddField( - model_name='lead', - name='customer', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='inventory.customer'), - preserve_default=False, - ), - migrations.AddField( - model_name='organization', - name='created', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created'), - preserve_default=False, - ), - migrations.AddField( - model_name='organization', - name='updated', - field=models.DateTimeField(auto_now=True, verbose_name='Updated'), - ), - migrations.AlterField( - model_name='representative', - name='id_number', - field=models.CharField(max_length=10, unique=True, verbose_name='ID Number'), - ), - ] diff --git a/inventory/migrations/0005_merge_20250119_1555.py b/inventory/migrations/0005_merge_20250119_1555.py deleted file mode 100644 index 35d879d8..00000000 --- a/inventory/migrations/0005_merge_20250119_1555.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 4.2.17 on 2025-01-19 12:55 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0004_rename_assigned_lead_staff_remove_customer_city_and_more'), - ] - - operations = [ - ] diff --git a/inventory/migrations/0006_cartransferlog_delete_invoicemodelbase.py b/inventory/migrations/0006_cartransferlog_delete_invoicemodelbase.py deleted file mode 100644 index 20e29d55..00000000 --- a/inventory/migrations/0006_cartransferlog_delete_invoicemodelbase.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 4.2.17 on 2025-01-19 14:01 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0005_merge_20250119_1555'), - ] - - operations = [ - migrations.CreateModel( - name='CarTransferLog', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('transfer_date', models.DateTimeField(auto_now_add=True, verbose_name='Transfer Date')), - ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), - ('cars', models.ManyToManyField(related_name='transfer_logs', to='inventory.car', verbose_name='Cars')), - ('from_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_out', to='inventory.dealer', verbose_name='From Dealer')), - ('to_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_in', to='inventory.dealer', verbose_name='To Dealer')), - ], - options={ - 'verbose_name': 'Car Transfer Log', - 'verbose_name_plural': 'Car Transfer Logs', - }, - ), - # migrations.DeleteModel( - # name='InvoiceModelBase', - # ), - ] diff --git a/inventory/urls.py b/inventory/urls.py index 08b832fe..0f35f4d7 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -1,196 +1,531 @@ from django.urls import path from . import views from allauth.account import views as allauth_views -from django.conf.urls import ( -handler400, handler403, handler404, handler500 -) +from django.conf.urls import handler400, handler403, handler404, handler500 urlpatterns = [ # main URLs - path('', views.HomeView.as_view(), name='landing_page'), - path('welcome/', views.WelcomeView.as_view(), name='welcome'), - + path("", views.HomeView.as_view(), name="landing_page"), + path("welcome/", views.WelcomeView.as_view(), name="welcome"), # Accounts URLs - path('login/', views.Login.as_view(), name='account_login'), - path('logout/', allauth_views.LogoutView.as_view(template_name='account/logout.html'), name='account_logout'), -# path('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'), - path('signup/', views.dealer_signup, name='account_signup'), - path('password/change/', - allauth_views.PasswordChangeView.as_view(template_name='account/password_change.html'), - name='account_change_password'), - path('password/reset/', - allauth_views.PasswordResetView.as_view(template_name='account/password_reset.html'), - name='account_reset_password'), - path('password/reset/done/', - allauth_views.PasswordResetDoneView.as_view(template_name='account/password_reset_done.html'), - name='account_password_reset_done'), - path('login/code/', allauth_views.RequestLoginCodeView.as_view(template_name='account/request_login_code.html')), - #Dashboards - path('dashboards/accounting/', views.AccountingDashboard.as_view(), name='accounting'), - path('test/', views.TestView.as_view(), name='test'), + path("login/", views.Login.as_view(), name="account_login"), + path( + "logout/", + allauth_views.LogoutView.as_view(template_name="account/logout.html"), + name="account_logout", + ), + # path('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'), + path("signup/", views.dealer_signup, name="account_signup"), + path( + "password/change/", + allauth_views.PasswordChangeView.as_view( + template_name="account/password_change.html" + ), + name="account_change_password", + ), + path( + "password/reset/", + allauth_views.PasswordResetView.as_view( + template_name="account/password_reset.html" + ), + name="account_reset_password", + ), + path( + "password/reset/done/", + allauth_views.PasswordResetDoneView.as_view( + template_name="account/password_reset_done.html" + ), + name="account_password_reset_done", + ), + path( + "login/code/", + allauth_views.RequestLoginCodeView.as_view( + template_name="account/request_login_code.html" + ), + ), + # Dashboards + path( + "dashboards/accounting/", views.AccountingDashboard.as_view(), name="accounting" + ), + path("test/", views.TestView.as_view(), name="test"), # Dealer URLs - path('dealers//', views.DealerDetailView.as_view(), name='dealer_detail'), - path('dealers//update/', views.DealerUpdateView.as_view(), name='dealer_update'), - path('dealers/activity/', views.UserActivityLogListView.as_view(), name='dealer_activity'), + path("dealers//", views.DealerDetailView.as_view(), name="dealer_detail"), + path( + "dealers//update/", + views.DealerUpdateView.as_view(), + name="dealer_update", + ), + path( + "dealers/activity/", + views.UserActivityLogListView.as_view(), + name="dealer_activity", + ), # path('dealers//delete/', views.DealerDeleteView.as_view(), name='dealer_delete'), - # CRM URLs - path('customers/', views.CustomerListView.as_view(), name='customer_list'), - path('customers//', views.CustomerDetailView.as_view(), name='customer_detail'), - path('customers/create/', views.CustomerCreateView.as_view(), name='customer_create'), - path('customers//update/', views.CustomerUpdateView.as_view(), name='customer_update'), - path('customers//delete/', views.delete_customer, name='customer_delete'), - path('customers//opportunities/create/', views.OpportunityCreateView.as_view(), name='create_opportunity'), - path('customers//add-note/', views.add_note_to_customer, name='add_note_to_customer'), - - path('crm/leads/', views.LeadListView.as_view(), name='lead_list'), - path('crm/leads//view/', views.LeadDetailView.as_view(), name='lead_detail'), - path('crm/leads/create/', views.LeadCreateView.as_view(), name='lead_create'), - path('crm/leads//update/', views.LeadUpdateView.as_view(), name='lead_update'), - path('crm/leads//delete/', views.LeadDeleteView.as_view(), name='lead_delete'), - path('crm/leads//add-note/', views.add_note_to_lead, name='add_note'), - path('crm/leads//add-activity/', views.add_activity_to_lead, name='add_activity'), - path('crm/opportunities/create/', views.OpportunityCreateView.as_view(), name='opportunity_create'), - path('crm/opportunities//', views.OpportunityDetailView.as_view(), name='opportunity_detail'), - path('crm/opportunities//edit/', views.OpportunityUpdateView.as_view(), name='update_opportunity'), - path('crm/opportunities/', views.OpportunityListView.as_view(), name='opportunity_list'), - path('crm/opportunities//delete/', views.delete_opportunity, name='delete_opportunity'), + path("customers/", views.CustomerListView.as_view(), name="customer_list"), + path( + "customers//", + views.CustomerDetailView.as_view(), + name="customer_detail", + ), + path( + "customers/create/", views.CustomerCreateView.as_view(), name="customer_create" + ), + path( + "customers//update/", + views.CustomerUpdateView.as_view(), + name="customer_update", + ), + path("customers//delete/", views.delete_customer, name="customer_delete"), + path( + "customers//opportunities/create/", + views.OpportunityCreateView.as_view(), + name="create_opportunity", + ), + path( + "customers//add-note/", + views.add_note_to_customer, + name="add_note_to_customer", + ), + path("crm/leads/", views.LeadListView.as_view(), name="lead_list"), + path( + "crm/leads//view/", views.LeadDetailView.as_view(), name="lead_detail" + ), + path("crm/leads/create/", views.LeadCreateView.as_view(), name="lead_create"), + path( + "crm/leads//update/", views.LeadUpdateView.as_view(), name="lead_update" + ), + path( + "crm/leads//delete/", views.LeadDeleteView.as_view(), name="lead_delete" + ), + path("crm/leads//add-note/", views.add_note_to_lead, name="add_note"), + path( + "crm/leads//add-activity/", + views.add_activity_to_lead, + name="add_activity", + ), + path( + "crm/opportunities/create/", + views.OpportunityCreateView.as_view(), + name="opportunity_create", + ), + path( + "crm/opportunities//", + views.OpportunityDetailView.as_view(), + name="opportunity_detail", + ), + path( + "crm/opportunities//edit/", + views.OpportunityUpdateView.as_view(), + name="update_opportunity", + ), + path( + "crm/opportunities/", + views.OpportunityListView.as_view(), + name="opportunity_list", + ), + path( + "crm/opportunities//delete/", + views.delete_opportunity, + name="delete_opportunity", + ), # path('crm/opportunities//logs/', views.OpportunityLogsView.as_view(), name='opportunity_logs'), - path('crm/notifications/', views.NotificationListView.as_view(), name='notifications_history'), - path('crm/fetch_notifications/', views.fetch_notifications, name='fetch_notifications'), - path('crm/notifications//mark_as_read/', views.mark_notification_as_read, name='mark_notification_as_read'), - - #Vendor URLs - path('vendors', views.VendorListView.as_view(), name='vendor_list'), - path('vendors//', views.VendorDetailView.as_view(), name='vendor_detail'), - path('vendors/create/', views.VendorCreateView.as_view(), name='vendor_create'), - path('vendors//update/', views.VendorUpdateView.as_view(), name='vendor_update'), - path('vendors//delete/', views.VendorDetailView.as_view(), name='vendor_delete'), - + path( + "crm/notifications/", + views.NotificationListView.as_view(), + name="notifications_history", + ), + path( + "crm/fetch_notifications/", + views.fetch_notifications, + name="fetch_notifications", + ), + path( + "crm/notifications//mark_as_read/", + views.mark_notification_as_read, + name="mark_notification_as_read", + ), + # Vendor URLs + path("vendors", views.VendorListView.as_view(), name="vendor_list"), + path("vendors//", views.VendorDetailView.as_view(), name="vendor_detail"), + path("vendors/create/", views.VendorCreateView.as_view(), name="vendor_create"), + path( + "vendors//update/", + views.VendorUpdateView.as_view(), + name="vendor_update", + ), + path( + "vendors//delete/", + views.VendorDetailView.as_view(), + name="vendor_delete", + ), # Car URLs - path('cars/inventory/', views.CarInventory.as_view(), name='car_inventory_all'), - path('cars/inventory////', views.CarInventory.as_view(), name='car_inventory'), - path('cars/inventory/stats', views.inventory_stats_view, name='inventory_stats'), - path('cars//', views.CarDetailView.as_view(), name='car_detail'), - path('cars//update/', views.CarUpdateView.as_view(), name='car_update'), - path('cars//delete/', views.CarDeleteView.as_view(), name='car_delete'), - path('cars//finance/create/', views.CarFinanceCreateView.as_view(), name='car_finance_create'), - path('cars/finance//update/', views.CarFinanceUpdateView.as_view(), name='car_finance_update'), - path('cars/add/', views.CarCreateView.as_view(), name='car_add'), - path('ajax/', views.AjaxHandlerView.as_view(), name='ajax_handler'), - path('cars/get-car-models/', views.get_car_models, name='get_car_models'), - path('cars//add-color/', views.CarColorCreate.as_view(), name='add_color'), - path('cars//location/add/', views.CarLocationCreateView.as_view(), name='add_car_location'), - path('cars//location/update/', views.CarLocationUpdateView.as_view(), name='transfer'), - path('cars/inventory/search/', views.SearchCodeView.as_view(), name='car_search'), + path("cars/inventory/", views.CarInventory.as_view(), name="car_inventory_all"), + path( + "cars/inventory////", + views.CarInventory.as_view(), + name="car_inventory", + ), + path("cars/inventory/stats", views.inventory_stats_view, name="inventory_stats"), + path("cars//", views.CarDetailView.as_view(), name="car_detail"), + path("cars//update/", views.CarUpdateView.as_view(), name="car_update"), + path("cars//delete/", views.CarDeleteView.as_view(), name="car_delete"), + path( + "cars//finance/create/", + views.CarFinanceCreateView.as_view(), + name="car_finance_create", + ), + path( + "cars/finance//update/", + views.CarFinanceUpdateView.as_view(), + name="car_finance_update", + ), + path("cars/add/", views.CarCreateView.as_view(), name="car_add"), + path("ajax/", views.AjaxHandlerView.as_view(), name="ajax_handler"), + path("cars/get-car-models/", views.get_car_models, name="get_car_models"), + path( + "cars//add-color/", views.CarColorCreate.as_view(), name="add_color" + ), + path( + "cars//location/add/", + views.CarLocationCreateView.as_view(), + name="add_car_location", + ), + path( + "cars//location/update/", + views.CarTransferCreateView.as_view(), + name="transfer", + ), + path( + "cars//location/detail/", + views.CarTransferDetailView, + name="transfer_detail", + ), + path( + "cars//location//transfer_approve/", + views.car_transfer_approve, + name="transfer_confirm", + ), + path( + "cars//location//transfer_accept_reject/", + views.car_transfer_accept_reject, + name="transfer_accept_reject", + ), + path( + "cars//location//preview/", + views.CarTransferPreviewView, + name="transfer_preview", + ), + path("cars/inventory/search/", views.SearchCodeView.as_view(), name="car_search"), # path('cars//colors//update/',views.CarColorUpdateView.as_view(),name='color_update'), - - path('cars/reserve//', views.reserve_car_view, name='reserve_car'), - path('reservations//', views.manage_reservation, name='reservations'), - path('cars//add-custom-card/', views.CustomCardCreateView.as_view(), name='add_custom_card'), - + path("cars/reserve//", views.reserve_car_view, name="reserve_car"), + path( + "reservations//", + views.manage_reservation, + name="reservations", + ), + path( + "cars//add-custom-card/", + views.CustomCardCreateView.as_view(), + name="add_custom_card", + ), # Sales URLs quotation_create - path('sales/quotations/create/', views.QuotationCreateView.as_view(), name='quotation_create'), - path('sales/quotations//', views.QuotationDetailView.as_view(), name='quotation_detail'), - path('sales/quotations/', views.QuotationListView.as_view(), name='quotation_list'), - path('sales/quotations//confirm/', views.confirm_quotation, name='confirm_quotation'), - path('sales/orders/detail//', views.SalesOrderDetailView.as_view(), name='order_detail'), - path('quotation//pdf/', views.download_quotation_pdf, name='quotation_pdf'), - path('generate_invoice//', views.generate_invoice, name='generate_invoice'), - path('sales/quotations//mark_quotation/', views.mark_quotation, name='mark_quotation'), - path('sales/quotations//post_quotation/', views.post_quotation, name='post_quotation'), - path('sales/quotations//invoice_detail/', views.invoice_detail, name='invoice_detail'), - path('subscriptions', views.SubscriptionPlans.as_view(), name='subscriptions'), - #Payment URLs -# path('sales/quotations//payment/', views.PaymentCreateView.as_view(), name='payment_create'), - path('sales/quotations//payment/', views.payment_create, name='payment_create'), - + path( + "sales/quotations/create/", + views.QuotationCreateView.as_view(), + name="quotation_create", + ), + path( + "sales/quotations//", + views.QuotationDetailView.as_view(), + name="quotation_detail", + ), + path("sales/quotations/", views.QuotationListView.as_view(), name="quotation_list"), + path( + "sales/quotations//confirm/", + views.confirm_quotation, + name="confirm_quotation", + ), + path( + "sales/orders/detail//", + views.SalesOrderDetailView.as_view(), + name="order_detail", + ), + path( + "quotation//pdf/", + views.download_quotation_pdf, + name="quotation_pdf", + ), + path("generate_invoice//", views.generate_invoice, name="generate_invoice"), + path( + "sales/quotations//mark_quotation/", + views.mark_quotation, + name="mark_quotation", + ), + path( + "sales/quotations//post_quotation/", + views.post_quotation, + name="post_quotation", + ), + path( + "sales/quotations//invoice_detail/", + views.invoice_detail, + name="invoice_detail", + ), + path("subscriptions", views.SubscriptionPlans.as_view(), name="subscriptions"), + # Payment URLs + # path('sales/quotations//payment/', views.PaymentCreateView.as_view(), name='payment_create'), + path( + "sales/quotations//payment/", + views.payment_create, + name="payment_create", + ), # Users URLs - path('user/create/', views.UserCreateView.as_view(), name='user_create'), - path('user//update/', views.UserUpdateView.as_view(), name='user_update'), - path('user//', views.UserDetailView.as_view(), name='user_detail'), - path('user/', views.UserListView.as_view(), name='user_list'), - path('user//confirm/', views.UserDeleteview, name='user_delete'), + path("user/create/", views.UserCreateView.as_view(), name="user_create"), + path("user//update/", views.UserUpdateView.as_view(), name="user_update"), + path("user//", views.UserDetailView.as_view(), name="user_detail"), + path("user/", views.UserListView.as_view(), name="user_list"), + path("user//confirm/", views.UserDeleteview, name="user_delete"), # Organization URLs - path('organizations/', views.OrganizationListView.as_view(), name='organization_list'), - path('organizations//', views.OrganizationDetailView.as_view(), name='organization_detail'), - path('organizations/create/', views.OrganizationCreateView.as_view(), name='organization_create'), - path('organizations//update/', views.OrganizationUpdateView.as_view(), name='organization_update'), - path('organizations//delete/', views.OrganizationDeleteView.as_view(), name='organization_delete'), - + path( + "organizations/", views.OrganizationListView.as_view(), name="organization_list" + ), + path( + "organizations//", + views.OrganizationDetailView.as_view(), + name="organization_detail", + ), + path( + "organizations/create/", + views.OrganizationCreateView.as_view(), + name="organization_create", + ), + path( + "organizations//update/", + views.OrganizationUpdateView.as_view(), + name="organization_update", + ), + path( + "organizations//delete/", + views.OrganizationDeleteView.as_view(), + name="organization_delete", + ), # Representative URLs - path('representatives/', views.RepresentativeListView.as_view(), name='representative_list'), - path('representatives//', views.RepresentativeDetailView.as_view(), name='representative_detail'), - path('representatives/create/', views.RepresentativeCreateView.as_view(), name='representative_create'), - path('representatives//update/', views.RepresentativeUpdateView.as_view(), name='representative_update'), - path('representatives//delete/', views.RepresentativeDeleteView.as_view(), name='representative_delete'), - - #Ledger URLS - #Bank Account - path('bank_accounts/', views.BankAccountListView.as_view(), name='bank_account_list'), - path('bank_accounts//', views.BankAccountDetailView.as_view(), name='bank_account_detail'), - path('bank_accounts/create/', views.BankAccountCreateView.as_view(), name='bank_account_create'), - path('bank_accounts//update/', views.BankAccountUpdateView.as_view(), name='bank_account_update'), - path('bank_accounts//delete/', views.bank_account_delete, name='bank_account_delete'), + path( + "representatives/", + views.RepresentativeListView.as_view(), + name="representative_list", + ), + path( + "representatives//", + views.RepresentativeDetailView.as_view(), + name="representative_detail", + ), + path( + "representatives/create/", + views.RepresentativeCreateView.as_view(), + name="representative_create", + ), + path( + "representatives//update/", + views.RepresentativeUpdateView.as_view(), + name="representative_update", + ), + path( + "representatives//delete/", + views.RepresentativeDeleteView.as_view(), + name="representative_delete", + ), + # Ledger URLS + # Bank Account + path( + "bank_accounts/", views.BankAccountListView.as_view(), name="bank_account_list" + ), + path( + "bank_accounts//", + views.BankAccountDetailView.as_view(), + name="bank_account_detail", + ), + path( + "bank_accounts/create/", + views.BankAccountCreateView.as_view(), + name="bank_account_create", + ), + path( + "bank_accounts//update/", + views.BankAccountUpdateView.as_view(), + name="bank_account_update", + ), + path( + "bank_accounts//delete/", + views.bank_account_delete, + name="bank_account_delete", + ), # Account - path('coa_accounts/', views.AccountListView.as_view(), name='account_list'), - path('coa_accounts//', views.AccountDetailView.as_view(), name='account_detail'), - path('coa_accounts/create/', views.AccountCreateView.as_view(), name='account_create'), - path('coa_accounts//update/', views.AccountUpdateView.as_view(), name='account_update'), - path('coa_accounts//delete/', views.account_delete, name='account_delete'), + path("coa_accounts/", views.AccountListView.as_view(), name="account_list"), + path( + "coa_accounts//", + views.AccountDetailView.as_view(), + name="account_detail", + ), + path( + "coa_accounts/create/", views.AccountCreateView.as_view(), name="account_create" + ), + path( + "coa_accounts//update/", + views.AccountUpdateView.as_view(), + name="account_update", + ), + path("coa_accounts//delete/", views.account_delete, name="account_delete"), # Estimate - path('sales/estimates/', views.EstimateListView.as_view(), name='estimate_list'), - path('sales/estimates//', views.EstimateDetailView.as_view(), name='estimate_detail'), - path('sales/estimates/create/', views.create_estimate, name='estimate_create'), - path('sales/estimates//estimate_mark_as/', views.estimate_mark_as, name='estimate_mark_as'), - path('sales/estimates//preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), - path('sales/estimates//payment_request/', views.PaymentRequest.as_view(), name='payment_request'), - path('sales/estimates//send_email', views.send_email_view, name='send_email'), + path("sales/estimates/", views.EstimateListView.as_view(), name="estimate_list"), + path( + "sales/estimates//", + views.EstimateDetailView.as_view(), + name="estimate_detail", + ), + path("sales/estimates/create/", views.create_estimate, name="estimate_create"), + path( + "sales/estimates//estimate_mark_as/", + views.estimate_mark_as, + name="estimate_mark_as", + ), + path( + "sales/estimates//preview/", + views.EstimatePreviewView.as_view(), + name="estimate_preview", + ), + path( + "sales/estimates//payment_request/", + views.PaymentRequest.as_view(), + name="payment_request", + ), + path( + "sales/estimates//send_email", views.send_email_view, name="send_email" + ), # Invoice - path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'), - path('sales/invoices//create/', views.invoice_create, name='invoice_create'), - path('sales/invoices//', views.InvoiceDetailView.as_view(), name='invoice_detail'), - path('sales/invoices//preview/', views.InvoicePreviewView.as_view(), name='invoice_preview'), - path('sales/invoices//invoice_mark_as/', views.invoice_mark_as, name='invoice_mark_as'), - path('sales/invoices//draft_invoice_update/', views.DraftInvoiceModelUpdateFormView.as_view(), name='draft_invoice_update'), - path('sales/invoices//approved_invoice_update/', views.ApprovedInvoiceModelUpdateFormView.as_view(), name='approved_invoice_update'), - path('sales/invoices//paid_invoice_update/', views.PaidInvoiceModelUpdateFormView.as_view(), name='paid_invoice_update'), - - # path('sales/estimates//preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), - # path('send_email/', views.send_email, name='send_email'), - - #Payment - path('sales/payments/', views.PaymentListView, name='payment_list'), - path('sales/payments//create/', views.PaymentCreateView, name='payment_create'), - path('sales/payments/create/', views.PaymentCreateView, name='payment_create'), - path('sales/payments//payment_details/', views.PaymentDetailView, name='payment_details'), - path('sales/payments//payment_mark_as_paid/', views.payment_mark_as_paid, name='payment_mark_as_paid'), - # path('sales/payments//update/', views.JournalEntryUpdateView.as_view(), name='payment_update'), - # path('sales/payments//delete/', views.JournalEntryDeleteView.as_view(), name='payment_delete'), - # path('sales/payments//preview/', views.JournalEntryPreviewView.as_view(), name='payment_preview'), - # # Journal - # path('sales/journal//create/', views.JournalEntryCreateView.as_view(), name='journal_create'), - - # Items - path('items/services/', views.ItemServiceListView.as_view(), name='item_service_list'), - path('items/services/create/', views.ItemServiceCreateView.as_view(), name='item_service_create'), - path('items/services//update/', views.ItemServiceUpdateView.as_view(), name='item_service_update'), - # Expanese - path('items/expeneses/', views.ItemExpenseListView.as_view(), name='item_expense_list'), - path('items/expeneses/create/', views.ItemExpenseCreateView.as_view(), name='item_expense_create'), - path('items/expeneses//update/', views.ItemExpenseUpdateView.as_view(), name='item_expense_update'), - # Bills - path('items/bills/', views.BillListView.as_view(), name='bill_list'), - path('items/bills/create/', views.BillCreateView.as_view(), name='bill_create'), - # path('items/bills//update/', views.ItemExpenseUpdateView.as_view(), name='item_expense_update'), + path("sales/invoices/", views.InvoiceListView.as_view(), name="invoice_list"), + path( + "sales/invoices//create/", views.invoice_create, name="invoice_create" + ), + path( + "sales/invoices//", + views.InvoiceDetailView.as_view(), + name="invoice_detail", + ), + path( + "sales/invoices//preview/", + views.InvoicePreviewView.as_view(), + name="invoice_preview", + ), + path( + "sales/invoices//invoice_mark_as/", + views.invoice_mark_as, + name="invoice_mark_as", + ), + path( + "sales/invoices//draft_invoice_update/", + views.DraftInvoiceModelUpdateFormView.as_view(), + name="draft_invoice_update", + ), + path( + "sales/invoices//approved_invoice_update/", + views.ApprovedInvoiceModelUpdateFormView.as_view(), + name="approved_invoice_update", + ), + path( + "sales/invoices//paid_invoice_update/", + views.PaidInvoiceModelUpdateFormView.as_view(), + name="paid_invoice_update", + ), + # path('sales/estimates//preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), + # path('send_email/', views.send_email, name='send_email'), + # Payment + path("sales/payments/", views.PaymentListView, name="payment_list"), + path( + "sales/payments//create/", + views.PaymentCreateView, + name="payment_create", + ), + # path("sales/payments/create/", views.PaymentCreateView, name="payment_create"), + path( + "sales/payments//payment_details/", + views.PaymentDetailView, + name="payment_details", + ), + path( + "sales/payments//payment_mark_as_paid/", + views.payment_mark_as_paid, + name="payment_mark_as_paid", + ), + # path('sales/payments//update/', views.JournalEntryUpdateView.as_view(), name='payment_update'), + # path('sales/payments//delete/', views.JournalEntryDeleteView.as_view(), name='payment_delete'), + # path('sales/payments//preview/', views.JournalEntryPreviewView.as_view(), name='payment_preview'), + # # Journal + # path('sales/journal//create/', views.JournalEntryCreateView.as_view(), name='journal_create'), + # Items + path( + "items/services/", views.ItemServiceListView.as_view(), name="item_service_list" + ), + path( + "items/services/create/", + views.ItemServiceCreateView.as_view(), + name="item_service_create", + ), + path( + "items/services//update/", + views.ItemServiceUpdateView.as_view(), + name="item_service_update", + ), + # Expanese + path( + "items/expeneses/", + views.ItemExpenseListView.as_view(), + name="item_expense_list", + ), + path( + "items/expeneses/create/", + views.ItemExpenseCreateView.as_view(), + name="item_expense_create", + ), + path( + "items/expeneses//update/", + views.ItemExpenseUpdateView.as_view(), + name="item_expense_update", + ), + # Bills + path("items/bills/", views.BillListView.as_view(), name="bill_list"), + path("items/bills/create/", views.bill_create, name="bill_create"), + path( + "items/bills//bill_detail/", + views.BillDetailView.as_view(), + name="bill_detail", + ), + path("items/bills//delete/", views.BillDeleteView, name="bill_delete"), + path( + "items/bills//in_review/", + views.InReviewBillView.as_view(), + name="in_review_bill", + ), + path( + "items/bills//in_approve/", + views.ApprovedBillModelView.as_view(), + name="in_approve_bill", + ), + path( + "items/bills//mark_as_approved/", + views.bill_mark_as_approved, + name="bill_mark_as_approved", + ), + path( + "items/bills//mark_as_paid/", + views.bill_mark_as_paid, + name="bill_mark_as_paid", + ), ] -handler404 = 'inventory.views.custom_page_not_found_view' -handler500 = 'inventory.views.custom_error_view' -handler403 = 'inventory.views.custom_permission_denied_view' -handler400 = 'inventory.views.custom_bad_request_view' - - - - +handler404 = "inventory.views.custom_page_not_found_view" +handler500 = "inventory.views.custom_error_view" +handler403 = "inventory.views.custom_permission_denied_view" +handler400 = "inventory.views.custom_bad_request_view" diff --git a/inventory/utils.py b/inventory/utils.py index 135450fb..a95a19ac 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -1,6 +1,13 @@ +import json +import random +import datetime from django.shortcuts import redirect from django.contrib import messages from django.utils import timezone +from django_ledger.models.entity import UnitOfMeasureModel +from django_ledger.models.journal_entry import JournalEntryModel +from django_ledger.models.ledger import LedgerModel +from django_ledger.models.transactions import TransactionModel import requests from inventory import models from django.conf import settings @@ -8,7 +15,7 @@ from django.core.mail import send_mail from django.utils.translation import gettext_lazy as _ from inventory.utilities.financials import get_financial_value from django_ledger.models.items import ItemModel -from django_ledger.models import InvoiceModel, EstimateModel +from django_ledger.models import InvoiceModel, EstimateModel,BillModel from decimal import Decimal @@ -179,3 +186,287 @@ def get_financial_values(model): "vat_amount": vat_amount, "vat": vat.rate, } + + +def set_invoice_payment(dealer, entity, invoice, amount, payment_method): + vat_amount = 0 + total_amount = 0 + + if invoice.terms == "on_receipt": + for x in invoice.get_itemtxs_data()[0].all(): + vat_amount += models.Car.objects.get( + vin=x.item_model.name + ).finances.vat_amount * Decimal(x.quantity) + total_amount += Decimal(x.unit_cost) * Decimal(x.quantity) + + grand_total = total_amount - Decimal(vat_amount) + + ledger = LedgerModel.objects.filter( + name__icontains=str(invoice.pk), entity=entity + ).first() + journal = JournalEntryModel.objects.create( + posted=False, + description=f"Payment for Invoice {invoice.invoice_number}", + ledger=ledger, + locked=False, + origin="Payment", + ) + credit_account = entity.get_default_coa_accounts().get(name="Sales Revenue") + debit_account = None + if payment_method == "cash": + debit_account = entity.get_default_coa_accounts().get(name="Cash", active=True) + elif payment_method == "credit": + debit_account = entity.get_default_coa_accounts().get( + name="Accounts Receivable", active=True + ) + else: + debit_account = entity.get_default_coa_accounts().get( + name="Cash in Bank", active=True + ) + + vat_payable_account = entity.get_default_coa_accounts().get( + name="VAT Payable", active=True + ) + TransactionModel.objects.create( + journal_entry=journal, + account=debit_account, # Debit Cash + amount=amount, # Payment amount + tx_type="debit", + description="Payment Received", + ) + + TransactionModel.objects.create( + journal_entry=journal, + account=credit_account, # Credit Accounts Receivable + amount=grand_total, # Payment amount + tx_type="credit", + description="Payment Received", + ) + + if vat_amount > 0: + TransactionModel.objects.create( + journal_entry=journal, + account=vat_payable_account, # Credit VAT Payable + amount=vat_amount, + tx_type="credit", + description="VAT Payable on Invoice", + ) + + invoice.make_payment(amount) + invoice.save() + + +def set_bill_payment(dealer, entity, bill, amount, payment_method): + total_amount = 0 + for x in bill.get_itemtxs_data()[0].all(): + total_amount += Decimal(x.unit_cost) * Decimal(x.quantity) + + journal = JournalEntryModel.objects.create( + posted=False, + description=f"Payment for bill {bill.bill_number}", + ledger=bill.ledger, + locked=False, + origin="Payment", + ) + + cash_account = entity.get_default_coa_accounts().get(name="Cash", active=True) + + account_payable = entity.get_default_coa_accounts().get( + name="Accounts Payable", active=True + ) + + + TransactionModel.objects.create( + journal_entry=journal, + account=cash_account, # Debit Cash + amount=amount, # Payment amount + tx_type="debit", + description="Payment Received", + ) + + TransactionModel.objects.create( + journal_entry=journal, + account=account_payable, # Credit Accounts Receivable + amount=amount, # Payment amount + tx_type="credit", + description="Payment Received", + ) + + bill.make_payment(amount) + bill.save() + + +def transfer_to_dealer(request,cars, to_dealer, remarks=None): + dealer = get_user_type(request) + + if not cars: + raise ValueError("No cars selected for transfer.") + + from_dealer = cars[0].dealer # Assume all cars are from the same dealer + + # Validate that all cars are from the same dealer + for car in cars: + if car.dealer != from_dealer: + raise ValueError("All cars must be from the same dealer.") + + if from_dealer == to_dealer: + raise ValueError("Cannot transfer cars to the same dealer.") + + # Log the transfer + transfer_log = models.CarTransferLog.objects.create( + from_dealer=from_dealer, + to_dealer=to_dealer, + remarks=remarks, + ) + transfer_log.cars.set(cars) # Associate the cars with the transfer log + + # Update the dealer for all cars + for car in cars: + car.dealer = to_dealer + car.save() + +def transfer_car(car,transfer): + from_dealer = transfer.from_dealer + to_dealer = transfer.to_dealer + # add transfer.to_dealer as customer in transfer.from_dealer entity + instance = models.Customer.objects.filter( + dealer=from_dealer, + email=to_dealer.user.email, + ).first() + if not instance: + instance = models.Customer.objects.create( + dealer=from_dealer, + title=models.Title.MR, + email=to_dealer.user.email, + first_name=to_dealer.user.first_name, + last_name=to_dealer.user.last_name, + phone_number=f"05685412{random.randint(10, 99)}", + address=to_dealer.address, + national_id=f"{random.randint(100, 9999)}", + dob="1990-01-01", + ) + + # create invoice from transfer.from_dealer to transfer.to_dealer + name = f"{instance.first_name} {instance.middle_name} {instance.last_name}" + customer = from_dealer.entity.get_customers().filter(customer_name=name).first() + + invoice = from_dealer.entity.create_invoice( + customer_model=customer, + terms=InvoiceModel.TERMS_NET_30, + cash_account=from_dealer.entity.get_default_coa_accounts().get(name="Cash", active=True), + prepaid_account=from_dealer.entity.get_default_coa_accounts().get(name="Accounts Receivable", active=True), + coa_model=from_dealer.entity.get_default_coa(), + ) + + ledger = from_dealer.entity.create_ledger(name=str(invoice.pk)) + invoice.ledgar = ledger + ledger.invoicemodel = invoice + ledger.save() + invoice.save() + item = from_dealer.entity.get_items_products().filter(name=car.vin).first() + if not item: + return + + invoice_itemtxs = { + item.item_number: { + "unit_cost": car.finances.cost_price, + "quantity": transfer.quantity, + "total_amount": transfer.total_price, + } + } + + invoice_itemtxs = invoice.migrate_itemtxs( + itemtxs=invoice_itemtxs, + commit=True, + operation=InvoiceModel.ITEMIZE_APPEND, + ) + + invoice.mark_as_review() + invoice.mark_as_approved(from_dealer.entity.slug, from_dealer.entity.admin) + invoice.mark_as_paid(from_dealer.entity.slug, from_dealer.entity.admin) + invoice.save() + + #create car item product in to_dealer entity + uom = to_dealer.entity.get_uom_all().filter(name=item.uom.name).first() + + product = to_dealer.entity.create_item_product( + name=item.name, + uom_model=uom, + item_type=item.item_type, + coa_model=to_dealer.entity.get_default_coa(), + ) + + car_dict = vars(car).copy() + del car_dict["_state"] + for key, value in car_dict.items(): + if isinstance(value, datetime.datetime): + car_dict[key] = value.strftime('%Y-%m-%d %H:%M:%S') + product.additional_info = json.dumps({"car_info": car_dict}) + product.save() + + #add the sender as vendor and create a bill for it + vendor_instance, created = models.Vendor.objects.get_or_create( + dealer=to_dealer, + crn=from_dealer.crn, + vrn=from_dealer.vrn, + name=from_dealer.name, + email=from_dealer.user.email, + arabic_name=from_dealer.arabic_name, + address=from_dealer.address, + phone_number=from_dealer.phone_number, + contact_person='', + ) + + #transfer the car to to_dealer and create items record + + vendor = to_dealer.entity.get_vendors().filter(vendor_name=vendor_instance.name).first() + + bill = to_dealer.entity.create_bill( + vendor_model=vendor, + terms=BillModel.TERMS_NET_30, + cash_account=to_dealer.entity.get_default_coa_accounts().get(name="Cash", active=True), + prepaid_account=to_dealer.entity.get_default_coa_accounts().get(name="Prepaid Expenses", active=True), + coa_model=to_dealer.entity.get_default_coa(), + ) + + bill_itemtxs = { + item.item_number: { + "unit_cost": car.finances.cost_price, + "quantity": transfer.quantity, + "total_amount": transfer.total_price, + } + } + + bill_itemtxs = bill.migrate_itemtxs(itemtxs=bill_itemtxs, + commit=True, + operation=BillModel.ITEMIZE_REPLACE) + + + car.dealer = to_dealer + car.vendor = vendor_instance + car.receiving_date = datetime.datetime.now() + car.finances.additional_services.clear() + if hasattr(car, "custom_cards"): + car.custom_cards.delete() + # car.finances.cost_price = 0 + car.finances.selling_price = 0 + car.finances.discount_amount = 0 + car.finances.save() + car.location.owner = to_dealer + car.location.showroom = to_dealer + car.location.description = "" + car.location.save() + + # car.reservations.all().delete() + car.status = models.CarStatusChoices.AVAILABLE + transfer.status = models.CarTransferStatusChoices.success + transfer.active = False + transfer.save() + car.save() + + return True + #pay the pill + # set_bill_payment(to_dealer,to_dealer.entity,bill,transfer.total_price,"credit") + + + \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index c1d08243..aa6c2e9d 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1,6 +1,7 @@ from decimal import Decimal from django.core.paginator import Paginator from django.forms import DateField, DateInput, HiddenInput, TextInput +from django_ledger.forms.bill import ApprovedBillModelUpdateForm, InReviewBillModelUpdateForm from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django_ledger.models import ( @@ -81,6 +82,9 @@ from .utils import ( reserve_car, send_email, get_user_type, + set_bill_payment, + set_invoice_payment, + transfer_car, ) from django.contrib.auth.models import User from allauth.account import views @@ -775,6 +779,120 @@ class CarLocationUpdateView(UpdateView): def get_success_url(self): return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) +class CarTransferCreateView(CreateView): + model = models.CarTransfer + form_class = forms.CarTransferForm + template_name = "inventory/car_location_form.html" + + def get_form(self, form_class = None): + form = super().get_form(form_class) + form.fields['to_dealer'].queryset = models.Dealer.objects.exclude(pk=get_user_type(self.request).pk).all() + form.fields['car'].queryset = models.Car.objects.filter(pk=self.kwargs["pk"]) + return form + def get_initial(self): + initial = super().get_initial() + initial["car"] = get_object_or_404(models.Car, pk=self.kwargs["pk"]) + return initial + + def form_valid(self, form): + form.instance.from_dealer = get_user_type(self.request) + form.instance.car.status = "transfer" + form.instance.car.save() + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) + +def CarTransferDetailView(request, pk): + transfer = get_object_or_404(models.CarTransfer, pk=pk) + context = {"transfer":transfer} + return render(request,'inventory/transfer_details.html',context) + +def car_transfer_approve(request, car_pk,transfer_pk): + car = get_object_or_404(models.Car, pk=car_pk) + transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) + transfer.status = "approved" + transfer.save() + url = request.build_absolute_uri(reverse('transfer_preview',kwargs={'car_pk':car.pk,"transfer_pk":transfer.pk})) + models.Notification.objects.create( + user=transfer.to_dealer.user, + message=f"Car transfer request from {transfer.from_dealer} is waiting for your acceptance. Accept", + ) + messages.success(request, _("Car transfer approved successfully.")) + return redirect("car_detail", pk=car.pk) + +def car_transfer_accept_reject(request, car_pk,transfer_pk): + car = get_object_or_404(models.Car, pk=car_pk) + transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) + status = request.GET.get("status") + if status == "rejected": + transfer.status = "reject" + transfer.active = False + messages.success(request, _("Car transfer rejected successfully.")) + models.Notification.objects.create( + user=transfer.from_dealer.user, + message=f"Car transfer request from {transfer.to_dealer} is rejected.", + ) + transfer.save() + elif status == "accepted": + transfer.status = "accept" + transfer.save() + success = transfer_car(car,transfer) + if success: + messages.success(request, _("Car Transfer Completed successfully.")) + models.Notification.objects.create( + user=transfer.from_dealer.user, + message=f"Car transfer request from {transfer.to_dealer} is completed.", + ) + return redirect("inventory_stats") + +def CarTransferPreviewView(request, car_pk,transfer_pk): + transfer = get_object_or_404(models.CarTransfer, pk=transfer_pk) + if transfer.to_dealer != get_user_type(request): + return redirect("car_detail", pk=car_pk) + return render(request,'inventory/transfer_preview.html',{"transfer":transfer}) + # def get_context_data(self, **kwargs): + # estimate = kwargs.get("object") + # if estimate.get_itemtxs_data(): + # data = get_financial_values(estimate) + + # kwargs["vat_amount"] = data["vat_amount"] + # kwargs["total"] = data["grand_total"] + # kwargs["discount_amount"] = data["discount_amount"] + # kwargs["vat"] = data["vat"] + # kwargs["car_and_item_info"] = data["car_and_item_info"] + # kwargs["additional_services"] = data["additional_services"] + # return super().get_context_data(**kwargs) + + + + +# class CarTransferView(View): +# template_name = "inventory/car_location_form.html" + +# def get(self, request, *args, **kwargs): +# form = forms.CarTransferForm() +# car = models.Car.objects.filter(pk=self.kwargs["pk"]) +# form.fields['to_dealer'].queryset = form.fields['to_dealer'].queryset.exclude(pk=get_user_type(request).pk) +# form.fields['car'].queryset = car +# form.initial['car'] = car.first() +# context = {"form": form} +# return render(request, self.template_name,context) + +# def post(self, request, *args, **kwargs): +# form = forms.CarTransferForm(request.POST) +# if form.is_valid(): +# from_dealer = get_user_type(request) +# car = form.cleaned_data['car'] +# to_dealer = form.cleaned_data['to_dealer'] +# remarks = form.cleaned_data['remarks'] +# models.CarTransferLog.objects.create(car=car, from_dealer=from_dealer, to_dealer=to_dealer, remarks=remarks) +# # car = models.Car.objects.filter(pk=self.kwargs["pk"]) +# # form.instance.car = car.first() +# # form.instance.to_dealer = get_user_type(request) +# # form.save() +# # messages.success(request, "Car transfered successfully.") +# return redirect("car_detail", pk=self.kwargs["pk"]) class CustomCardCreateView(LoginRequiredMixin, CreateView): model = models.CustomCard @@ -2439,8 +2557,11 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView): # payments -def PaymentCreateView(request, pk=None): +def PaymentCreateView(request, pk): + print(pk) invoice = InvoiceModel.objects.filter(pk=pk).first() + bill = BillModel.objects.filter(pk=pk).first() + model = invoice if invoice else bill dealer = get_user_type(request) entity = dealer.entity form = forms.PaymentForm() @@ -2449,92 +2570,38 @@ def PaymentCreateView(request, pk=None): if form.is_valid(): amount = form.cleaned_data.get("amount") invoice = form.cleaned_data.get("invoice") + bill = form.cleaned_data.get("bill") payment_method = form.cleaned_data.get("payment_method") + redirect_url = 'invoice_detail' if invoice else 'bill_detail' + model = invoice if invoice else bill + + if not model.is_approved(): + model.mark_as_approved(user_model=entity.admin) try: - vat_amount = 0 - total_amount = 0 - - if invoice.terms == "on_receipt": - for x in invoice.get_itemtxs_data()[0].all(): - vat_amount += models.Car.objects.get( - vin=x.item_model.name - ).finances.vat_amount * Decimal(x.quantity) - total_amount += Decimal(x.unit_cost) * Decimal(x.quantity) - - grand_total = total_amount - Decimal(vat_amount) - - ledger = LedgerModel.objects.filter( - name=str(invoice.pk), entity=entity - ).first() - journal = JournalEntryModel.objects.create( - posted=False, - description=f"Payment for Invoice {invoice.invoice_number}", - ledger=ledger, - locked=False, - origin="Payment", - ) - credit_account = entity.get_default_coa_accounts().get( - name="Sales Revenue" - ) - debit_account = None - if payment_method == "cash": - debit_account = entity.get_default_coa_accounts().get( - name="Cash", active=True - ) - elif payment_method == "credit": - debit_account = entity.get_default_coa_accounts().get( - name="Accounts Receivable", active=True - ) - else: - debit_account = entity.get_default_coa_accounts().get( - name="Cash in Bank", active=True - ) - - vat_payable_account = entity.get_default_coa_accounts().get( - name="VAT Payable", active=True - ) - TransactionModel.objects.create( - journal_entry=journal, - account=debit_account, # Debit Cash - amount=amount, # Payment amount - tx_type="debit", - description="Payment Received", - ) - - TransactionModel.objects.create( - journal_entry=journal, - account=credit_account, # Credit Accounts Receivable - amount=grand_total, # Payment amount - tx_type="credit", - description="Payment Received", - ) - - if vat_amount > 0: - TransactionModel.objects.create( - journal_entry=journal, - account=vat_payable_account, # Credit VAT Payable - amount=vat_amount, - tx_type="credit", - description="VAT Payable on Invoice", - ) - - invoice.make_payment(amount) - invoice.save() + if invoice: + set_invoice_payment(dealer,entity,invoice,amount,payment_method) + elif bill: + set_bill_payment(dealer,entity,bill,amount,payment_method) + messages.success(request, "Payment created successfully!") + return redirect(redirect_url, pk=model.pk) except Exception as e: messages.error(request, f"Error creating payment: {str(e)}") else: - messages.error(request, f"Invalid form data: {str(form.errors)}") - return redirect("invoice_detail", pk=invoice.pk) + messages.error(request, f"Invalid form data: {str(form.errors)}") + # return redirect(redirect_url, pk=model.pk) form = forms.PaymentForm() - form.initial["amount"] = invoice.amount_due - - if invoice: - form.initial["invoice"] = invoice + if model: + form.initial["amount"] = model.amount_due - model.amount_paid + if isinstance(model, InvoiceModel): + form.initial["invoice"] = model + form.fields['bill'].widget = HiddenInput() + elif isinstance(model, BillModel): + form.initial["bill"] = model + form.fields['invoice'].widget = HiddenInput() return render( - request, "sales/payments/payment_form.html", {"invoice": invoice, "form": form} + request, "sales/payments/payment_form.html", {"model": model, "form": form} ) - def PaymentListView(request): dealer = get_user_type(request) @@ -2574,7 +2641,12 @@ def payment_mark_as_paid(request, pk): invoice.ledger.post() invoice.ledger.save() - messages.success(request, "Payment created successfully!") + messages.success(request, "Payment created successfully!") + else: + messages.error( + request, + "Invoice is not fully paid. Payment cannot be marked as paid.", + ) except Exception as e: messages.error(request, f"Error: {str(e)}") return redirect("invoice_detail", pk=invoice.pk) @@ -2793,15 +2865,16 @@ def fetch_notifications(request): notifications = models.Notification.objects.filter( user=request.user, is_read=False ).order_by("-created") - notifications_data = [ - { - "id": notification.id, - "message": notification.message, - "created": notification.created.strftime("%Y-%m-%d %H:%M:%S"), - } - for notification in notifications - ] - return JsonResponse({"notifications": notifications_data}) + # notifications_data = [ + # { + # "id": notification.id, + # "message": notification.message, + # "created": notification.created.strftime("%Y-%m-%d %H:%M:%S"), + # } + # for notification in notifications + # ] + # return JsonResponse({"notifications": notifications_data}) + return render(request,'notifications.html',{'notifications_':notifications}) class ItemServiceCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): @@ -2897,33 +2970,273 @@ class ItemExpenseListView(ListView): class BillListView(ListView): - model = ItemModel + model = BillModel template_name = "ledger/bills/bill_list.html" context_object_name = "bills" - paginate_by = 20 - def get_queryset(self): dealer = get_user_type(self.request) - return dealer.entity.get_bills() + qs = dealer.entity.get_bills() + return qs - -class BillCreateView(LoginRequiredMixin,SuccessMessageMixin,CreateView): +class BillDetailView(LoginRequiredMixin, DetailView): model = BillModel - form_class = BillModelCreateForm - template_name = "ledger/bills/bill_form.html" + template_name = "ledger/bills/bill_detail.html" + context_object_name = "bill" + def get_context_data(self, **kwargs): + bill = kwargs.get("object") + if bill.get_itemtxs_data(): + txs = bill.get_itemtxs_data()[0] + car_and_item_info = [ + { + "car": models.Car.objects.get(vin=x.item_model.name), + "total": models.Car.objects.get( + vin=x.item_model.name + ).finances.cost_price + * Decimal(x.quantity), + "itemmodel": x, + } + for x in txs + ] + grand_total = sum( + Decimal( + models.Car.objects.get(vin=x.item_model.name).finances.cost_price + ) + * Decimal(x.quantity) + for x in txs + ) + kwargs["car_and_item_info"] = car_and_item_info + kwargs["grand_total"] = grand_total + return super().get_context_data(**kwargs) + +class InReviewBillView(LoginRequiredMixin, UpdateView): + model = BillModel + form_class = InReviewBillModelUpdateForm + template_name = "ledger/bills/bill_update_form.html" success_url = reverse_lazy("bill_list") - success_message = _("Bill created successfully.") - + success_message = _("Bill updated successfully.") + context_object_name = "bill" + def get_form_kwargs(self): - dealer = get_user_type(self.request) kwargs = super().get_form_kwargs() + dealer = get_user_type(self.request) kwargs["entity_model"] = dealer.entity + kwargs["user_model"] = dealer.entity.admin return kwargs + + def get_success_url(self): + return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]}) + def form_valid(self, form): + dealer = get_user_type(self.request) + form.instance.entity = dealer.entity + self.object.mark_as_review() + return super().form_valid(form) +class ApprovedBillModelView(LoginRequiredMixin, UpdateView): + model = BillModel + form_class = ApprovedBillModelUpdateForm + template_name = "ledger/bills/bill_update_form.html" + success_url = reverse_lazy("bill_list") + success_message = _("Bill updated successfully.") + context_object_name = "bill" + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + dealer = get_user_type(self.request) + kwargs["entity_model"] = dealer.entity + kwargs["user_model"] = dealer.entity.admin + return kwargs + + def get_success_url(self): + return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]}) + def form_valid(self, form): + dealer = get_user_type(self.request) + form.instance.entity = dealer.entity + if not self.object.is_approved(): + self.object.mark_as_approved(user_model=dealer.entity.admin) + return super().form_valid(form) - # def form_valid(self, form): +def bill_mark_as_approved(request,pk): + bill = get_object_or_404(BillModel,pk=pk) + if request.method == "POST": + dealer = get_user_type(request) + if bill.is_approved(): + messages.error(request, _("Bill is already approved.")) + return redirect("bill_detail",pk=bill.pk) + bill.mark_as_approved(user_model=dealer.entity.admin) + bill.save() + messages.success(request, _("Bill marked as approved successfully.")) + return redirect("bill_detail",pk=bill.pk) + +def bill_mark_as_paid(request,pk): + bill = get_object_or_404(BillModel,pk=pk) + if request.method == "POST": + dealer = get_user_type(request) + if bill.is_paid(): + messages.error(request, _("Bill is already paid.")) + return redirect("bill_detail",pk=bill.pk) + if bill.amount_due == bill.amount_paid: + bill.mark_as_paid(user_model=dealer.entity.admin) + bill.save() + bill.ledger.lock_journal_entries() + bill.ledger.post_journal_entries() + bill.ledger.post() + bill.ledger.save() + messages.success(request, _("Bill marked as paid successfully.")) + else: + messages.error(request, _("Amount paid is not equal to amount due.")) + return redirect("bill_detail",pk=bill.pk) + + # def get_context_data(self, **kwargs): # dealer = get_user_type(self.request) - # form.instance.entity = dealer.entity - # return super().form_valid(form) + # context = super().get_context_data(**kwargs) + # context['entity_model'] = dealer.entity + # context['user_model'] = dealer.entity.admin + + # return context +# class BillCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): +# model = BillModel +# form_class = BillModelCreateForm +# template_name = "ledger/bills/bill_form.html" +# success_url = reverse_lazy("bill_list") +# success_message = _("Bill created successfully.") +# def get_form_kwargs(self): +# dealer = get_user_type(self.request) +# kwargs = super().get_form_kwargs() +# kwargs["entity_model"] = dealer.entity +# return kwargs + +# def form_valid(self, form): +# dealer = get_user_type(self.request) +# form.instance.entity = dealer.entity +# ledger = dealer.entity.create_ledger( +# name=f"Bill for Vendor {form.instance.vendor.vendor_name}", posted=True +# ) +# form.instance.ledger = ledger +# return super().form_valid(form) +@login_required +def bill_create(request): + dealer = get_user_type(request) + entity = dealer.entity + + if request.method == "POST": + data = json.loads(request.body) + vendor_id = data.get("vendor") + terms = data.get("terms") + vendor = entity.get_vendors().filter(pk=vendor_id).first() + + items = data.get("item", []) + quantities = data.get("quantity", []) + + if not all([items, quantities]): + return JsonResponse( + {"status": "error", "message": "Items and Quantities are required"}, + status=400, + ) + if isinstance(quantities, list): + if "0" in quantities: + return JsonResponse( + {"status": "error", "message": "Quantity must be greater than zero"} + ) + else: + if int(quantities) <= 0: + return JsonResponse( + {"status": "error", "message": "Quantity must be greater than zero"} + ) + + bill = entity.create_bill(vendor_model=vendor, terms=terms) + if isinstance(items, list): + item_quantity_map = {} + for item, quantity in zip(items, quantities): + if item in item_quantity_map: + item_quantity_map[item] += int(quantity) + else: + item_quantity_map[item] = int(quantity) + item_list = list(item_quantity_map.keys()) + quantity_list = list(item_quantity_map.values()) + + items_list = [ + {"item_id": item_list[i], "quantity": quantity_list[i]} + for i in range(len(item_list)) + ] + items_txs = [] + for item in items_list: + item_instance = ItemModel.objects.get(pk=item.get("item_id")) + car = models.Car.objects.get(vin=item_instance.name) + quantity = Decimal(item.get("quantity")) + items_txs.append( + { + "item_number": item_instance.item_number, + "quantity": quantity, + "unit_cost": car.finances.cost_price, + "total_amount": car.finances.cost_price * quantity, + } + ) + + bill_itemtxs = { + item.get("item_number"): { + "unit_cost": item.get("unit_cost"), + "quantity": item.get("quantity"), + "total_amount": item.get("total_amount"), + } + for item in items_txs + } + else: + item = entity.get_items_all().filter(pk=items).first() + instance = models.Car.objects.get(vin=item.name) + bill_itemtxs = { + item.item_number: { + "unit_cost": instance.finances.cost_price, + "quantity": Decimal(quantities), + "total_amount": instance.finances.cost_price * Decimal(quantities), + } + } + + bill_itemtxs = bill.migrate_itemtxs( + itemtxs=bill_itemtxs, + commit=True, + operation=BillModel.ITEMIZE_APPEND, + ) + + url = reverse("bill_detail", kwargs={"pk": bill.pk}) + return JsonResponse( + { + "status": "success", + "message": "Bill created successfully!", + "url": f"{url}", + } + ) + + form = forms.BillModelCreateForm(entity_model=entity) + form.initial.update( + { + "cash_account": entity.get_default_coa_accounts().get(name="Cash"), + "prepaid_account": entity.get_default_coa_accounts().get( + name="Prepaid Expenses" + ), + "unearned_account": entity.get_default_coa_accounts().get( + name="Accounts Payable" + ), + } + ) + car_list = models.Car.objects.filter(dealer=dealer) + context = { + "form": form, + "items": [ + { + "car": x, + "product": entity.get_items_all() + .filter(item_role=ItemModel.ITEM_ROLE_PRODUCT, name=x.vin) + .first(), + } + for x in car_list + ], + } + + return render(request, "ledger/bills/bill_form.html", context) + +def BillDeleteView(request, pk): + bill = get_object_or_404(BillModel, pk=pk) + bill.delete() + return redirect("bill_list") class SubscriptionPlans(ListView): diff --git a/templates/auth_base.html b/templates/auth_base.html index 9c63dab3..1567ab39 100644 --- a/templates/auth_base.html +++ b/templates/auth_base.html @@ -79,7 +79,7 @@ - {% block extra_js %}{% endblock extra_js %} + {% block customJS %}{% endblock customJS %} + + + +{% endblock %} \ No newline at end of file diff --git a/templates/ledger/bills/bill_form.html b/templates/ledger/bills/bill_form.html index 1e9759c7..f4c1038a 100644 --- a/templates/ledger/bills/bill_form.html +++ b/templates/ledger/bills/bill_form.html @@ -1,38 +1,158 @@ {% extends "base.html" %} -{% load i18n %} {% load crispy_forms_filters %} -{% block title %}{% trans "account" %}{% endblock title %} +{% load i18n static %} + +{% block title %}{{ _("Create Bill") }}{% endblock title %} {% block content %} -
- -
-
-

- {% if account.created %} - - {{ _("Edit Account") }} - {% else %} - - {{ _("Add Account") }} - {% endif %} -

-
-
-
- {% csrf_token %} - {{ form|crispy }} - {% for error in form.errors %} -
{{ error }}
- {% endfor %} -
- - {% trans "Cancel"|capfirst %} +
+

{% trans "Create Bill" %}

+ + {% csrf_token %} +
+ {{ form|crispy }} +
+
+

Unit Items

+
+
+ +
+
+ +
+
+ +
+
- +
+ +
+
-
+ + +
+ + {% trans "Cancel" %} +
+
-{% endblock %} \ No newline at end of file +{% endblock content %} + +{% block customJS %} + +{% endblock customJS %} \ No newline at end of file diff --git a/templates/ledger/bills/bill_list.html b/templates/ledger/bills/bill_list.html index a7830700..20486004 100644 --- a/templates/ledger/bills/bill_list.html +++ b/templates/ledger/bills/bill_list.html @@ -30,30 +30,25 @@
- - - {% if page_obj.object_list %} +
+ {% trans 'Bill Status' %} + - - + {% for bill in bills %} - - + + -
- {% trans 'Account Name' %} + {% trans 'Bill Number' %} - {% trans 'Code' %} - - {% trans 'Balance Type' %} - - {% trans 'Active' %} + {% trans 'Vendor' %}
{{ bill.name }}{{ bill.code }}{{ bill.bill_number }}{{ bill.bill_status }} - {% if bill.balance_type == 'debit' %} -
- {{ _('Debit') }} -
- {% else %} -
- {{ _('Credit') }} -
- {% endif %} -
- {% if bill.active %} - - {% else %} - - {% endif %} + {{bill.vendor.vendor_name}}
@@ -157,8 +137,7 @@ - - {% endif %} + diff --git a/templates/ledger/bills/bill_update_form.html b/templates/ledger/bills/bill_update_form.html new file mode 100644 index 00000000..e43c596c --- /dev/null +++ b/templates/ledger/bills/bill_update_form.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load crispy_forms_filters %} +{% block title %}{% trans "Bill" %}{% endblock title %} +{% block content %} +
+

{% trans "Bill" %}

+
+ {% csrf_token %} + {{ form|crispy }} + + {% trans "Cancel" %} +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/notifications.html b/templates/notifications.html new file mode 100644 index 00000000..d3ee5cbf --- /dev/null +++ b/templates/notifications.html @@ -0,0 +1,50 @@ + + \ No newline at end of file diff --git a/templates/sales/estimates/estimate_detail.html b/templates/sales/estimates/estimate_detail.html index 58d2f8f7..77c55607 100644 --- a/templates/sales/estimates/estimate_detail.html +++ b/templates/sales/estimates/estimate_detail.html @@ -174,7 +174,7 @@ {% endblock %} -{% block extra_js %} +{% block customJS %} -{% endblock extra_js %} \ No newline at end of file +{% endblock customJS %} \ No newline at end of file diff --git a/templates/sales/invoices/invoice_detail.html b/templates/sales/invoices/invoice_detail.html index 3f1f6ac7..7bb2b3e3 100644 --- a/templates/sales/invoices/invoice_detail.html +++ b/templates/sales/invoices/invoice_detail.html @@ -262,7 +262,7 @@ {% endblock %} -{% block extra_js %} +{% block customJS %}