From b48d40465528b2af9dba194eb769bd77e0f850a1 Mon Sep 17 00:00:00 2001 From: gitea Date: Tue, 14 Jan 2025 11:51:26 +0000 Subject: [PATCH] update --- db.sqlite3-shm | Bin 0 -> 32768 bytes db.sqlite3-wal | 0 haikalbot/migrations/0001_initial.py | 2 +- haikalbot/migrations/0002_initial.py | 6 +- inventory/forms.py | 29 +- inventory/migrations/0001_initial.py | 950 ++++++++++-------- .../migrations/0002_alter_carmake_car_type.py | 18 + .../0004_additionalservices_item_and_more.py | 26 - .../0005_alter_additionalservices_item.py | 20 - ...d_customer_dob_customer_gender_and_more.py | 59 -- ...r_nationality_customer_country_and_more.py | 43 - ...ons_alter_notification_options_and_more.py | 224 ----- .../migrations/0009_alter_staff_managers.py | 20 - inventory/migrations/0010_customer_staff.py | 19 - ...1_remove_customer_country_customer_city.py | 22 - .../0012_opportunity_probability.py | 20 - .../migrations/0013_lead_phone_number.py | 20 - ...ivity_created_by_alter_notes_created_by.py | 26 - inventory/migrations/0015_lead_city.py | 19 - inventory/migrations/0016_lead_address.py | 18 - .../migrations/0017_alter_lead_assigned.py | 19 - .../migrations/0018_alter_lead_priority.py | 18 - ...losed_opportunity_closing_date_and_more.py | 45 - inventory/models.py | 4 +- inventory/signals.py | 186 ++-- inventory/urls.py | 1 + inventory/utils.py | 1 + inventory/views.py | 269 +++-- templates/header.html | 54 +- templates/sales/estimates/estimate_form.html | 2 +- templates/sales/invoices/invoice_detail.html | 85 +- templates/sales/payments/payment_details.html | 12 +- templates/sales/payments/payment_form.html | 1 - 33 files changed, 1006 insertions(+), 1232 deletions(-) create mode 100644 db.sqlite3-shm create mode 100644 db.sqlite3-wal create mode 100644 inventory/migrations/0002_alter_carmake_car_type.py delete mode 100644 inventory/migrations/0004_additionalservices_item_and_more.py delete mode 100644 inventory/migrations/0005_alter_additionalservices_item.py delete mode 100644 inventory/migrations/0006_remove_customer_is_lead_customer_dob_customer_gender_and_more.py delete mode 100644 inventory/migrations/0007_remove_customer_nationality_customer_country_and_more.py delete mode 100644 inventory/migrations/0008_alter_notes_options_alter_notification_options_and_more.py delete mode 100644 inventory/migrations/0009_alter_staff_managers.py delete mode 100644 inventory/migrations/0010_customer_staff.py delete mode 100644 inventory/migrations/0011_remove_customer_country_customer_city.py delete mode 100644 inventory/migrations/0012_opportunity_probability.py delete mode 100644 inventory/migrations/0013_lead_phone_number.py delete mode 100644 inventory/migrations/0014_alter_activity_created_by_alter_notes_created_by.py delete mode 100644 inventory/migrations/0015_lead_city.py delete mode 100644 inventory/migrations/0016_lead_address.py delete mode 100644 inventory/migrations/0017_alter_lead_assigned.py delete mode 100644 inventory/migrations/0018_alter_lead_priority.py delete mode 100644 inventory/migrations/0019_opportunity_closed_opportunity_closing_date_and_more.py diff --git a/db.sqlite3-shm b/db.sqlite3-shm new file mode 100644 index 0000000000000000000000000000000000000000..87689361e1c86a4161dc5c5c2f0c3683ac861438 GIT binary patch literal 32768 zcmeI5b+lAf8^)hA_Yww3sDObYA|Mz@s$igCAShrXVgQODg5qa^fr>$QcXR3P?n`%f zhk^>IpnT7nxifQO_CD`?-@p5;^>f~M_WPXk&b_SVA9p72fxBQ|If(`22!(*t#WSJ) z!A5m+Wm{0OWWCm(k>z9sSxM5#DzciaA#2GxvYu=p8_6cJnQS3j$u_c` z>>xYIF0z~KA$!R_vY#9v2gxCFm>eNT$uV-AoFFI3DRP?pLVhJ@h`o;|`JW}#;y;H? z`Loz5e=1Nm^2}#5boOqBO$9!!6T<8Uq43YFt+n4v?mn47w7$E zn*mHZ2DCVD;nLuUZvL8x*8aV-?|t++_x;*_eg4{<+l2W8JDMQMVa^!yU0C|v< zCl$y;KKZ>LHYBAJ^OOm-o4DE_t53Kwcy-k(bFUab zMcyWLNj*}ZyhGk44aj?>A!$V3CymJmqzP$CJ|rKJW@K_2aH32It&T_DGrJaj&#E)9 z`)g}N`X%}A--IbC=$1DX=_TU7NA^A2=RPL?A)k;>$!Fwq@&##5T97ZvSLAE*4QWZf zCEtvW;vfJIGG5 zi|i(Q$X>FK>?a4vL2`&3CP&Coa*P}&C&)>1ikv3qG8-@(FdHx%FdHx%FdHx%FdHx% zFdHx%FdHx%FdHx%FdHx%FdHx%FdHx%FdHx%FdHx%FdHx%FdHx%FdHx%FdHx%FdHx% zFdHx%FdHx%FdHx%FdHx%FdHx%FdHx%FdHx%FdHx%FdHx%&~4yj`_D8**Y9E_@VRKB zEh-Q&wNz}%dUEAKOc5*9 zGyI)d{*t2dR-pga8H#;@%DBag40pv-O>|Myi?*H)I}b?0KYB9wwNtPpLvg>@Ki#S< z*4{4mU++;~fk*HT#$pF@Se2|64#(Dx*mKB}@*;64 zHK>5fsEfu(!(?p5L1eRTuqs;5S@o<>t+{rT~I%4?LX(@yOFmskmW?efqig~d~6h?PJVHco+(xWx#PzIqWJ;+c>5iJ|HEO;VSzF_w^lR=eict0 zDpm%Y#ueGo2;Gs-dNy$SLxGCu)OAATqdZVFdMNPqWS}A{=pgh5E(R#eMo041{;XWt zRa86mkYmU!t~IVPd7m&e!VUtEdV!#s2F> z$}7W0cdG1s^;5ozs?bs7Z=b#-Rs!Fu?0oe*#ZzaCmB4jAA5~$Ys^P0Q5c?k$D}mZR zA5|eFo;qBtRCy(;_e@kwSbJXVzd@`7iY4?)R6L!in6TDK?EjBYX@7?HcL}``75PEO z!fTFL30#jSg;N&^b-;UK|6^h$@VeMPMLcyxLgm#KKScZgxO$1tz99D5NaY0K+=uX# zQ2C2c2Xyw`>4SJ2Z{a;o;3ukMI3{My`q!san|!Zsc{;_#Q|F46Kpu~aI$f-8c{&Xh z{#RModpgHGxo1!;-N&dNn#6K_kSd%Cj1(*Nj_z)6cdE%=6#H)yD}kpqj_%g4?o^X? z(fE(RB*)3i8l^)P4qJH(%A8+5A-v{^l|WwMOn%dYSlLMIe?qJTnu`51iKmVdE5+s% z_7RI;68mo!D}gFP|Es|-gAiU_<^CbzNXqPQhml=(YyH1rGp61btEhssurdd(P2xVHv&KgQ-)moAMOAoN?7Ky*1fEgZ`RZqV z6;+{|*xyRxr~gNlov(h6*nfdo3EbfGQ5BY{8oqjCvHz(+<@>SeAH|}eAWPuX=!DT? zW%Pvoy2qI$i2Eg|B`&=p_TTDJ-ko@0xLQ@ybGLqTr<$z0$6qbAzr9R){UjCQUa|i| zu@bmZ?7v*B{6Oq~TBE$u(`zR7&+0j;J~&3KRL}5t`uj_Y%By1kZDJ)*jcz&d1U|qN z9L9}Sb?XPk>E9LioLep~%!6LB|xXFD~ldaJ7+^r^J z|6hd4p)~l|ZKBDpu(De>S&vyY0;i7+RA_9JtpUo8w^7UbQFsU2J-U0mqa69OjyMu- z!G1hyRrSmO{_rVPg`U2%DrGYD6kLVYq67Dd{TGXsz|A6WTeMQFY%2EuRjlOSSBU*@ z7Ec|gRPGeJ`Go`hB}L^mrN8&U4zUt=&U;vypHp_cjb2IkN6(gs2Flkq%8s{DQsCb* ztXU#ZMyKbG27#~XI2Ndh2tE}1pAjp8PlWD3e(}`tVx`!e!a-tjZL$AOu@a~*_CN1a zj`UR`h29DLQ&ApoV;BygT7s@(kgMF*mFbdF2^Nr5#NVZpT*|uf-2PBDwrCXF;T44 zd3t-(y#-}nN9li^r?cCWD@U?XQ`dC${&Z!!q%ZwHv%X2 invoice.amount_due: + raise forms.ValidationError("Payment amount is greater than invoice amount due") + return amount - from django import forms class EmailForm(forms.Form): subject = forms.CharField(max_length=255) @@ -640,4 +653,14 @@ class ActivityForm(forms.ModelForm): class OpportunityForm(forms.ModelForm): class Meta: model = Opportunity - fields = ['customer', 'car', 'stage', 'probability', 'closing_date'] \ No newline at end of file + fields = ['customer', 'car', 'stage', 'probability', 'closing_date'] + + +class InvoiceModelCreateForm(InvoiceModelCreateFormBase): + 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'})) \ No newline at end of file diff --git a/inventory/migrations/0001_initial.py b/inventory/migrations/0001_initial.py index 9be8205c..acb0f9ac 100644 --- a/inventory/migrations/0001_initial.py +++ b/inventory/migrations/0001_initial.py @@ -1,12 +1,12 @@ -# Generated by Django 5.1.4 on 2025-01-07 22:27 +# Generated by Django 4.2.17 on 2025-01-13 08:40 +from decimal import Decimal +from django.conf import settings +from django.db import migrations, models import django.db.models.deletion import inventory.mixins import inventory.models import phonenumber_field.modelfields -from decimal import Decimal -from django.conf import settings -from django.db import migrations, models class Migration(migrations.Migration): @@ -14,11 +14,29 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0002_remove_content_type_name'), + ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), ] operations = [ + 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')), + ], + options={ + 'verbose_name': 'Additional Services', + 'verbose_name_plural': 'Additional Services', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), migrations.CreateModel( name='Car', fields=[ @@ -57,13 +75,116 @@ 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, 'truck trailer'), (4, 'trailer'), (5, 'truck'), (6, 'bus')])), + ('car_type', models.SmallIntegerField(choices=[])), ], options={ 'verbose_name': 'Make', }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), + migrations.CreateModel( + name='CarModel', + fields=[ + ('id_car_model', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(blank=True, max_length=255, null=True)), + ('arabic_name', models.CharField(blank=True, max_length=255, null=True)), + ('id_car_make', models.ForeignKey(db_column='id_car_make', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake')), + ], + options={ + 'verbose_name': 'Model', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='CarOption', + fields=[ + ('id_car_option', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(blank=True, max_length=255, null=True)), + ('arabic_name', models.CharField(blank=True, max_length=255, null=True)), + ('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.caroption')), + ], + options={ + 'verbose_name': 'Option', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='CarSerie', + fields=[ + ('id_car_serie', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(blank=True, max_length=255, null=True)), + ('arabic_name', models.CharField(blank=True, max_length=255, null=True)), + ('year_begin', models.IntegerField(blank=True, null=True)), + ('year_end', models.IntegerField(blank=True, null=True)), + ('generation_name', models.CharField(blank=True, max_length=255, null=True)), + ('id_car_model', models.ForeignKey(db_column='id_car_model', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel')), + ], + options={ + 'verbose_name': 'Series', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='CarSpecification', + fields=[ + ('id_car_specification', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('arabic_name', models.CharField(max_length=255)), + ('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')), + ], + options={ + 'verbose_name': 'Specification', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + 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')), + ], + options={ + 'verbose_name': 'Customer', + 'verbose_name_plural': 'Customers', + }, + ), + migrations.CreateModel( + name='Dealer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('crn', models.CharField(blank=True, max_length=10, null=True, verbose_name='Commercial Registration Number')), + ('vrn', models.CharField(blank=True, max_length=15, null=True, verbose_name='VAT Registration Number')), + ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), + ('name', models.CharField(max_length=255, verbose_name='English Name')), + ('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/users', verbose_name='Logo')), + ('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('entity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Dealer', + 'verbose_name_plural': 'Dealers', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + managers=[ + ('objects', inventory.models.DealerUserManager()), + ], + ), migrations.CreateModel( name='ExteriorColors', fields=[ @@ -92,6 +213,52 @@ class Migration(migrations.Migration): }, 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')), + ], + options={ + 'verbose_name': 'Lead', + 'verbose_name_plural': 'Leads', + }, + ), + migrations.CreateModel( + name='Organization', + 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')), + ('crn', models.CharField(max_length=15, verbose_name='Commercial Registration Number')), + ('vrn', models.CharField(max_length=15, verbose_name='VAT Registration Number')), + ('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')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to='inventory.dealer')), + ], + options={ + 'verbose_name': 'Organization', + 'verbose_name_plural': 'Organizations', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), migrations.CreateModel( name='Payment', fields=[ @@ -106,6 +273,46 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'payments', }, ), + migrations.CreateModel( + name='SaleQuotation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quotation_number', models.CharField(max_length=10, unique=True)), + ('amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='Amount')), + ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), + ('is_approved', models.BooleanField(default=False)), + ('status', models.CharField(choices=[('Draft', 'Draft'), ('Approved', 'Approved'), ('In Review', 'In Review'), ('Paid', 'Paid')], default='Draft', max_length=10, verbose_name='Status')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('posted', models.BooleanField(default=False)), + ('payment_id', models.CharField(blank=True, max_length=255, null=True, verbose_name='Payment ID')), + ('is_paid', models.BooleanField(default=False)), + ('date_draft', models.DateTimeField(blank=True, null=True, verbose_name='Draft Date')), + ('date_in_review', models.DateTimeField(blank=True, null=True, verbose_name='In Review Date')), + ('date_approved', models.DateTimeField(blank=True, null=True, verbose_name='Approved Date')), + ('date_paid', models.DateTimeField(blank=True, null=True, verbose_name='Paid Date')), + ('date_void', models.DateTimeField(blank=True, null=True, verbose_name='Void Date')), + ('date_canceled', models.DateTimeField(blank=True, null=True, verbose_name='Canceled Date')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotations', to='inventory.customer', verbose_name='Customer')), + ('dealer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer')), + ], + ), + migrations.CreateModel( + name='Subscription', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start_date', models.DateField(help_text='Date when the subscription starts')), + ('end_date', models.DateField(help_text='Date when the subscription ends')), + ('is_active', models.BooleanField(default=True)), + ('billing_cycle', models.CharField(choices=[('monthly', 'Monthly'), ('annual', 'Annual')], default='monthly', help_text='Billing cycle for the subscription', max_length=10)), + ('last_payment_date', models.DateField(blank=True, help_text='Date of the last payment made', null=True)), + ('next_payment_date', models.DateField(blank=True, help_text='Date of the next payment due', null=True)), + ], + options={ + 'verbose_name': 'Subscription', + 'verbose_name_plural': 'Subscriptions', + }, + ), migrations.CreateModel( name='SubscriptionPlan', fields=[ @@ -135,120 +342,257 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='CarFinance', + name='Vendor', 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='django_ledger.itemmodel')), - ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='finances', to='inventory.car')), + ('crn', models.CharField(max_length=10, unique=True, verbose_name='Commercial Registration Number')), + ('vrn', models.CharField(max_length=15, unique=True, verbose_name='VAT Registration Number')), + ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), + ('name', models.CharField(max_length=255, verbose_name='English Name')), + ('contact_person', models.CharField(max_length=100, verbose_name='Contact Person')), + ('phone_number', 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')), + ('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')), ], options={ - 'verbose_name': 'Car Financial Details', - 'verbose_name_plural': 'Car Financial Details', + 'verbose_name': 'Vendor', + 'verbose_name_plural': 'Vendors', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='UserActivityLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('action', models.TextField()), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'User Activity Log', + 'verbose_name_plural': 'User Activity Logs', + 'ordering': ['-timestamp'], + }, + ), + migrations.CreateModel( + name='SubscriptionUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('subscription', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.subscription')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Subscription User', + 'verbose_name_plural': 'Subscription Users', }, ), migrations.AddField( - model_name='car', + model_name='subscription', + name='plan', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subscriptions', to='inventory.subscriptionplan'), + ), + migrations.AddField( + model_name='subscription', + name='users', + field=models.ManyToManyField(through='inventory.SubscriptionUser', to=settings.AUTH_USER_MODEL), + ), + 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='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='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='Representative', + 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')), + ('id_number', models.CharField(max_length=10, 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')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='representatives', to='inventory.dealer')), + ('organization', models.ManyToManyField(related_name='representatives', to='inventory.organization')), + ], + options={ + 'verbose_name': 'Representative', + 'verbose_name_plural': 'Representatives', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='Refund', + 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')), + ('reason', models.TextField(blank=True, verbose_name='reason')), + ('refund_date', models.DateField(auto_now_add=True, verbose_name='refund date')), + ('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='refund', to='inventory.payment')), + ], + options={ + 'verbose_name': 'refund', + 'verbose_name_plural': 'refunds', + }, + ), + 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='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='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='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='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')), + ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')), + ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')), + ], + 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='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, 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=[ - ('id_car_model', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(blank=True, max_length=255, null=True)), - ('arabic_name', models.CharField(blank=True, max_length=255, null=True)), - ('id_car_make', models.ForeignKey(db_column='id_car_make', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmake')), - ], - options={ - 'verbose_name': 'Model', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), + 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='car', + model_name='lead', name='id_car_model', - field=models.ForeignKey(blank=True, db_column='id_car_model', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='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='CarOption', - fields=[ - ('id_car_option', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(blank=True, max_length=255, null=True)), - ('arabic_name', models.CharField(blank=True, max_length=255, null=True)), - ('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.caroption')), - ], - options={ - 'verbose_name': 'Option', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='CarOptionValue', - fields=[ - ('id_car_option_value', models.AutoField(primary_key=True, serialize=False)), - ('value', models.CharField(max_length=500)), - ('unit', models.CharField(blank=True, max_length=255, null=True)), - ('is_base', models.IntegerField()), - ('id_car_equipment', models.ForeignKey(db_column='id_car_equipment', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carequipment')), - ('id_car_option', models.ForeignKey(db_column='id_car_option', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.caroption')), - ], - options={ - 'verbose_name': 'Option Value', - }, - ), - migrations.CreateModel( - name='CarRegistration', + name='CustomCard', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('plate_number', models.IntegerField(verbose_name='Plate Number')), - ('text1', models.CharField(max_length=1, verbose_name='Text 1')), - ('text2', models.CharField(max_length=1, verbose_name='Text 2')), - ('text3', models.CharField(max_length=1, verbose_name='Text 3')), - ('registration_date', models.DateTimeField(verbose_name='Registration Date')), - ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')), + ('custom_number', models.CharField(max_length=255, verbose_name='Custom Number')), + ('custom_date', models.DateField(verbose_name='Custom Date')), + ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='custom_cards', to='inventory.car', verbose_name='Car')), ], options={ - 'verbose_name': 'Registration', - 'verbose_name_plural': 'Registrations', + 'verbose_name': 'Custom Card', + 'verbose_name_plural': 'Custom Cards', }, ), - migrations.CreateModel( - name='CarSerie', - fields=[ - ('id_car_serie', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(blank=True, max_length=255, null=True)), - ('arabic_name', models.CharField(blank=True, max_length=255, null=True)), - ('year_begin', models.IntegerField(blank=True, null=True)), - ('year_end', models.IntegerField(blank=True, null=True)), - ('generation_name', models.CharField(blank=True, max_length=255, null=True)), - ('id_car_model', models.ForeignKey(db_column='id_car_model', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel')), - ], - options={ - 'verbose_name': 'Series', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.AddField( - model_name='car', - name='id_car_serie', - field=models.ForeignKey(blank=True, db_column='id_car_serie', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie', verbose_name='Series'), - ), - migrations.CreateModel( - name='CarSpecification', - fields=[ - ('id_car_specification', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=255)), - ('arabic_name', models.CharField(max_length=255)), - ('id_parent', models.ForeignKey(blank=True, db_column='id_parent', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')), - ], - options={ - 'verbose_name': 'Specification', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), migrations.CreateModel( name='CarTrim', fields=[ @@ -277,72 +621,34 @@ class Migration(migrations.Migration): 'verbose_name': 'Specification Value', }, ), - migrations.AddField( - model_name='carequipment', - name='id_car_trim', - field=models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim'), - ), - migrations.AddField( - model_name='car', - name='id_car_trim', - field=models.ForeignKey(blank=True, db_column='id_car_trim', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim', verbose_name='Trim'), - ), migrations.CreateModel( - name='CustomCard', + name='CarRegistration', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('custom_number', models.CharField(max_length=255, verbose_name='Custom Number')), - ('custom_date', models.DateField(verbose_name='Custom Date')), - ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='custom_cards', to='inventory.car', verbose_name='Car')), + ('plate_number', models.IntegerField(verbose_name='Plate Number')), + ('text1', models.CharField(max_length=1, verbose_name='Text 1')), + ('text2', models.CharField(max_length=1, verbose_name='Text 2')), + ('text3', models.CharField(max_length=1, verbose_name='Text 3')), + ('registration_date', models.DateTimeField(verbose_name='Registration Date')), + ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')), ], options={ - 'verbose_name': 'Custom Card', - 'verbose_name_plural': 'Custom Cards', + 'verbose_name': 'Registration', + 'verbose_name_plural': 'Registrations', }, ), migrations.CreateModel( - name='Dealer', + name='CarOptionValue', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('crn', models.CharField(blank=True, max_length=10, null=True, verbose_name='Commercial Registration Number')), - ('vrn', models.CharField(blank=True, max_length=15, null=True, verbose_name='VAT Registration Number')), - ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), - ('name', models.CharField(max_length=255, verbose_name='English Name')), - ('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/users', verbose_name='Logo')), - ('joined_at', models.DateTimeField(auto_now_add=True, verbose_name='Joined At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), - ('entity', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_ledger.entitymodel')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dealer', to=settings.AUTH_USER_MODEL)), + ('id_car_option_value', models.AutoField(primary_key=True, serialize=False)), + ('value', models.CharField(max_length=500)), + ('unit', models.CharField(blank=True, max_length=255, null=True)), + ('is_base', models.IntegerField()), + ('id_car_equipment', models.ForeignKey(db_column='id_car_equipment', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carequipment')), + ('id_car_option', models.ForeignKey(db_column='id_car_option', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.caroption')), ], options={ - 'verbose_name': 'Dealer', - 'verbose_name_plural': 'Dealers', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - managers=[ - ('objects', inventory.models.DealerUserManager()), - ], - ), - migrations.CreateModel( - name='Customer', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('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')), - ('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')), - ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), - ('is_lead', models.BooleanField(default=False, verbose_name='Is Lead')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')), - ], - options={ - 'verbose_name': 'Customer', - 'verbose_name_plural': 'Customers', + 'verbose_name': 'Option Value', }, ), migrations.CreateModel( @@ -361,295 +667,83 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Car Locations', }, ), + 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='carequipment', + name='id_car_trim', + field=models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim'), + ), 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.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')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer')), - ], - options={ - 'verbose_name': 'Additional Services', - 'verbose_name_plural': 'Additional Services', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - 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_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('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_at'], - }, - ), - migrations.CreateModel( - name='Opportunity', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('deal_name', models.CharField(max_length=255, verbose_name='Deal Name')), - ('deal_value', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Deal Value')), - ('deal_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], default='new', max_length=20, verbose_name='Deal Status')), - ('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='low', max_length=10, verbose_name='Priority')), - ('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(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')), - ], - options={ - 'verbose_name': 'Opportunity', - 'verbose_name_plural': 'Opportunities', - }, - ), - migrations.CreateModel( - name='Notes', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('note', models.TextField(verbose_name='Note')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), - ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL)), - ('opportunity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='inventory.opportunity')), - ], - options={ - 'verbose_name': 'Notes', - 'verbose_name_plural': 'Notes', - }, - ), - migrations.CreateModel( - name='Organization', - 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')), - ('crn', models.CharField(max_length=15, verbose_name='Commercial Registration Number')), - ('vrn', models.CharField(max_length=15, verbose_name='VAT Registration Number')), - ('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')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organizations', to='inventory.dealer')), - ], - options={ - 'verbose_name': 'Organization', - 'verbose_name_plural': 'Organizations', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='Refund', - 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')), - ('reason', models.TextField(blank=True, verbose_name='reason')), - ('refund_date', models.DateField(auto_now_add=True, verbose_name='refund date')), - ('payment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='refund', to='inventory.payment')), - ], - options={ - 'verbose_name': 'refund', - 'verbose_name_plural': 'refunds', - }, - ), - migrations.CreateModel( - name='Representative', - 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')), - ('id_number', models.CharField(max_length=10, 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')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='representatives', to='inventory.dealer')), - ('organization', models.ManyToManyField(related_name='representatives', to='inventory.organization')), - ], - options={ - 'verbose_name': 'Representative', - 'verbose_name_plural': 'Representatives', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), - ), - migrations.CreateModel( - name='SaleQuotation', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quotation_number', models.CharField(max_length=10, unique=True)), - ('amount', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10, verbose_name='Amount')), - ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), - ('is_approved', models.BooleanField(default=False)), - ('status', models.CharField(choices=[('Draft', 'Draft'), ('Approved', 'Approved'), ('In Review', 'In Review'), ('Paid', 'Paid')], default='Draft', max_length=10, verbose_name='Status')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), - ('posted', models.BooleanField(default=False)), - ('payment_id', models.CharField(blank=True, max_length=255, null=True, verbose_name='Payment ID')), - ('is_paid', models.BooleanField(default=False)), - ('date_draft', models.DateTimeField(blank=True, null=True, verbose_name='Draft Date')), - ('date_in_review', models.DateTimeField(blank=True, null=True, verbose_name='In Review Date')), - ('date_approved', models.DateTimeField(blank=True, null=True, verbose_name='Approved Date')), - ('date_paid', models.DateTimeField(blank=True, null=True, verbose_name='Paid Date')), - ('date_void', models.DateTimeField(blank=True, null=True, verbose_name='Void Date')), - ('date_canceled', models.DateTimeField(blank=True, null=True, verbose_name='Canceled Date')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quotations', to='inventory.customer', verbose_name='Customer')), - ('dealer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sales', to='inventory.dealer')), - ], + 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.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_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), - ('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), - ), - migrations.CreateModel( - name='OpportunityLog', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('action', models.CharField(choices=[('create', 'Create'), ('update', 'Update'), ('delete', 'Delete'), ('status_change', 'Status Change')], max_length=50, verbose_name='Action')), - ('old_status', models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], max_length=50, null=True, verbose_name='Old Status')), - ('new_status', models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('canceled', 'Canceled'), ('completed', 'Completed')], max_length=50, null=True, verbose_name='New Status')), - ('details', models.TextField(blank=True, null=True, verbose_name='Details')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('opportunity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='inventory.opportunity')), - ('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='inventory.staff', verbose_name='Staff')), - ], - options={ - 'verbose_name': 'Log', - 'verbose_name_plural': 'Logs', - 'ordering': ['-created_at'], - }, + model_name='car', + name='id_car_model', + field=models.ForeignKey(blank=True, db_column='id_car_model', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carmodel', verbose_name='Model'), ), migrations.AddField( - model_name='opportunity', - name='created_by', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deals_created', to='inventory.staff'), - ), - migrations.CreateModel( - name='Subscription', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('start_date', models.DateField(help_text='Date when the subscription starts')), - ('end_date', models.DateField(help_text='Date when the subscription ends')), - ('is_active', models.BooleanField(default=True)), - ('billing_cycle', models.CharField(choices=[('monthly', 'Monthly'), ('annual', 'Annual')], default='monthly', help_text='Billing cycle for the subscription', max_length=10)), - ('last_payment_date', models.DateField(blank=True, help_text='Date of the last payment made', null=True)), - ('next_payment_date', models.DateField(blank=True, help_text='Date of the next payment due', null=True)), - ('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subscriptions', to='inventory.subscriptionplan')), - ], - options={ - 'verbose_name': 'Subscription', - 'verbose_name_plural': 'Subscriptions', - }, - ), - migrations.CreateModel( - name='SubscriptionUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('subscription', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.subscription')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Subscription User', - 'verbose_name_plural': 'Subscription Users', - }, + model_name='car', + name='id_car_serie', + field=models.ForeignKey(blank=True, db_column='id_car_serie', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie', verbose_name='Series'), ), migrations.AddField( - model_name='subscription', - name='users', - field=models.ManyToManyField(through='inventory.SubscriptionUser', to=settings.AUTH_USER_MODEL), - ), - migrations.CreateModel( - name='UserActivityLog', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('action', models.TextField()), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'User Activity Log', - 'verbose_name_plural': 'User Activity Logs', - 'ordering': ['-timestamp'], - }, - ), - migrations.CreateModel( - name='Vendor', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('crn', models.CharField(max_length=10, unique=True, verbose_name='Commercial Registration Number')), - ('vrn', models.CharField(max_length=15, unique=True, verbose_name='VAT Registration Number')), - ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), - ('name', models.CharField(max_length=255, verbose_name='English Name')), - ('contact_person', models.CharField(max_length=100, verbose_name='Contact Person')), - ('phone_number', 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')), - ('logo', models.ImageField(blank=True, null=True, upload_to='logos/vendors', verbose_name='Logo')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vendors', to='inventory.dealer')), - ], - options={ - 'verbose_name': 'Vendor', - 'verbose_name_plural': 'Vendors', - }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), + model_name='car', + name='id_car_trim', + field=models.ForeignKey(blank=True, db_column='id_car_trim', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim', verbose_name='Trim'), ), migrations.AddField( model_name='car', name='vendor', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.vendor', verbose_name='Vendor'), ), + migrations.AddField( + model_name='additionalservices', + name='dealer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'), + ), + migrations.AddField( + model_name='additionalservices', + name='item', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item'), + ), + 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='CarReservation', fields=[ diff --git a/inventory/migrations/0002_alter_carmake_car_type.py b/inventory/migrations/0002_alter_carmake_car_type.py new file mode 100644 index 00000000..06e7827a --- /dev/null +++ b/inventory/migrations/0002_alter_carmake_car_type.py @@ -0,0 +1,18 @@ +# 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/0004_additionalservices_item_and_more.py b/inventory/migrations/0004_additionalservices_item_and_more.py deleted file mode 100644 index 2241b959..00000000 --- a/inventory/migrations/0004_additionalservices_item_and_more.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 4.2.17 on 2025-01-08 08:42 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), - ('inventory', '0003_alter_caroptionvalue_is_base'), - ] - - operations = [ - migrations.AddField( - model_name='additionalservices', - name='item', - field=models.OneToOneField(default='', on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item'), - preserve_default=False, - ), - migrations.AlterField( - model_name='carfinance', - name='additional_services', - field=models.ManyToManyField(blank=True, related_name='additional_finances', to='inventory.additionalservices'), - ), - ] diff --git a/inventory/migrations/0005_alter_additionalservices_item.py b/inventory/migrations/0005_alter_additionalservices_item.py deleted file mode 100644 index bf542c58..00000000 --- a/inventory/migrations/0005_alter_additionalservices_item.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.2.17 on 2025-01-08 08:43 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), - ('inventory', '0004_additionalservices_item_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='additionalservices', - name='item', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item'), - ), - ] diff --git a/inventory/migrations/0006_remove_customer_is_lead_customer_dob_customer_gender_and_more.py b/inventory/migrations/0006_remove_customer_is_lead_customer_dob_customer_gender_and_more.py deleted file mode 100644 index 3e6592a1..00000000 --- a/inventory/migrations/0006_remove_customer_is_lead_customer_dob_customer_gender_and_more.py +++ /dev/null @@ -1,59 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-08 19:03 - -import django.utils.timezone -import django_countries.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0005_alter_additionalservices_item'), - ] - - operations = [ - migrations.RemoveField( - model_name='customer', - name='is_lead', - ), - migrations.AddField( - model_name='customer', - name='dob', - field=models.DateField(default=django.utils.timezone.now, verbose_name='Date of Birth'), - preserve_default=False, - ), - migrations.AddField( - model_name='customer', - name='gender', - field=models.CharField(choices=[('m', 'Male'), ('f', 'Female')], default='m', max_length=1, verbose_name='Gender'), - preserve_default=False, - ), - migrations.AddField( - model_name='customer', - name='nationality', - field=django_countries.fields.CountryField(blank=True, max_length=2, verbose_name='Nationality'), - ), - migrations.AddField( - model_name='customer', - name='obligations', - field=models.PositiveIntegerField(default=1000, verbose_name='Obligations'), - preserve_default=False, - ), - migrations.AddField( - model_name='customer', - name='salary', - field=models.PositiveIntegerField(default=10000, verbose_name='Salary'), - preserve_default=False, - ), - migrations.AddField( - model_name='customer', - name='title', - field=models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company')], default='mr', max_length=10, verbose_name='Title'), - preserve_default=False, - ), - migrations.AddField( - model_name='customer', - name='updated', - field=models.DateTimeField(auto_now=True, verbose_name='Updated'), - ), - ] diff --git a/inventory/migrations/0007_remove_customer_nationality_customer_country_and_more.py b/inventory/migrations/0007_remove_customer_nationality_customer_country_and_more.py deleted file mode 100644 index f9e6d93f..00000000 --- a/inventory/migrations/0007_remove_customer_nationality_customer_country_and_more.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-09 05:46 - -import django_countries.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0006_remove_customer_is_lead_customer_dob_customer_gender_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='customer', - name='nationality', - ), - migrations.AddField( - model_name='customer', - name='country', - field=django_countries.fields.CountryField(blank=True, max_length=2, verbose_name='Country'), - ), - migrations.AlterField( - model_name='customer', - name='title', - field=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'), - ), - migrations.AlterField( - model_name='opportunity', - name='deal_status', - field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('canceled', 'Canceled'), ('lost', 'Lost'), ('won', 'Won')], default='new', max_length=20, verbose_name='Deal Status'), - ), - migrations.AlterField( - model_name='opportunitylog', - name='new_status', - field=models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('canceled', 'Canceled'), ('lost', 'Lost'), ('won', 'Won')], max_length=50, null=True, verbose_name='New Status'), - ), - migrations.AlterField( - model_name='opportunitylog', - name='old_status', - field=models.CharField(blank=True, choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('canceled', 'Canceled'), ('lost', 'Lost'), ('won', 'Won')], max_length=50, null=True, verbose_name='Old Status'), - ), - ] diff --git a/inventory/migrations/0008_alter_notes_options_alter_notification_options_and_more.py b/inventory/migrations/0008_alter_notes_options_alter_notification_options_and_more.py deleted file mode 100644 index 47e0ef38..00000000 --- a/inventory/migrations/0008_alter_notes_options_alter_notification_options_and_more.py +++ /dev/null @@ -1,224 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-09 09:19 - -import django.db.models.deletion -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('inventory', '0007_remove_customer_nationality_customer_country_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='notes', - options={'verbose_name': 'Note', 'verbose_name_plural': 'Notes'}, - ), - migrations.AlterModelOptions( - name='notification', - options={'ordering': ['-created'], 'verbose_name': 'Notification', 'verbose_name_plural': 'Notifications'}, - ), - migrations.RemoveField( - model_name='customer', - name='obligations', - ), - migrations.RemoveField( - model_name='customer', - name='salary', - ), - migrations.RemoveField( - model_name='notes', - name='created_at', - ), - migrations.RemoveField( - model_name='notes', - name='opportunity', - ), - migrations.RemoveField( - model_name='notes', - name='updated_at', - ), - migrations.RemoveField( - model_name='notification', - name='created_at', - ), - migrations.RemoveField( - model_name='opportunity', - name='created_at', - ), - migrations.RemoveField( - model_name='opportunity', - name='created_by', - ), - migrations.RemoveField( - model_name='opportunity', - name='deal_name', - ), - migrations.RemoveField( - model_name='opportunity', - name='deal_status', - ), - migrations.RemoveField( - model_name='opportunity', - name='deal_value', - ), - migrations.RemoveField( - model_name='opportunity', - name='priority', - ), - migrations.RemoveField( - model_name='opportunity', - name='updated_at', - ), - migrations.RemoveField( - model_name='staff', - name='created_at', - ), - migrations.RemoveField( - model_name='staff', - name='updated_at', - ), - migrations.AddField( - model_name='notes', - name='content_type', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype'), - preserve_default=False, - ), - migrations.AddField( - model_name='notes', - name='created', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created'), - preserve_default=False, - ), - migrations.AddField( - model_name='notes', - name='object_id', - field=models.PositiveIntegerField(default=1), - preserve_default=False, - ), - migrations.AddField( - model_name='notes', - name='updated', - field=models.DateTimeField(auto_now=True, verbose_name='Updated'), - ), - migrations.AddField( - model_name='notification', - name='created', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created'), - preserve_default=False, - ), - migrations.AddField( - model_name='opportunity', - name='created', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created'), - preserve_default=False, - ), - migrations.AddField( - model_name='opportunity', - name='dealer', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.dealer'), - preserve_default=False, - ), - migrations.AddField( - model_name='opportunity', - name='staff', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner'), - ), - migrations.AddField( - model_name='opportunity', - name='stage', - field=models.CharField(choices=[('prospect', 'Prospect'), ('proposal', 'Proposal'), ('negotiation', 'Negotiation'), ('closed_won', 'Closed Won'), ('closed_lost', 'Closed Lost')], default='prospect', max_length=20, verbose_name='Stage'), - preserve_default=False, - ), - migrations.AddField( - model_name='opportunity', - name='updated', - field=models.DateTimeField(auto_now=True, verbose_name='Updated'), - ), - migrations.AddField( - model_name='staff', - name='created', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created'), - preserve_default=False, - ), - migrations.AddField( - model_name='staff', - name='updated', - field=models.DateTimeField(auto_now=True, verbose_name='Updated'), - ), - migrations.AlterField( - model_name='notes', - name='created_by', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to='inventory.staff'), - ), - 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='inventory.staff')), - ], - options={ - 'verbose_name': 'Activity', - 'verbose_name_plural': 'Activities', - }, - ), - 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')), - ('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')), - ('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='low', max_length=10, verbose_name='Priority')), - ('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('assigned', 'Assigned'), ('in_progress', 'In Progress'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], db_index=True, 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')), - ('assigned', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned')), - ('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')), - ], - options={ - 'verbose_name': 'Lead', - 'verbose_name_plural': 'Leads', - }, - ), - 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.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'), ('assigned', 'Assigned'), ('in_progress', 'In Progress'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status')), - ('new_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('assigned', 'Assigned'), ('in_progress', 'In Progress'), ('contacted', 'Contacted'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status')), - ('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='Changed At')), - ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')), - ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')), - ], - options={ - 'verbose_name': 'Lead Status History', - 'verbose_name_plural': 'Lead Status Histories', - }, - ), - migrations.DeleteModel( - name='OpportunityLog', - ), - ] diff --git a/inventory/migrations/0009_alter_staff_managers.py b/inventory/migrations/0009_alter_staff_managers.py deleted file mode 100644 index 1b94fe19..00000000 --- a/inventory/migrations/0009_alter_staff_managers.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-09 09:57 - -import inventory.models -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0008_alter_notes_options_alter_notification_options_and_more'), - ] - - operations = [ - migrations.AlterModelManagers( - name='staff', - managers=[ - ('objects', inventory.models.StaffUserManager()), - ], - ), - ] diff --git a/inventory/migrations/0010_customer_staff.py b/inventory/migrations/0010_customer_staff.py deleted file mode 100644 index ab0bdd30..00000000 --- a/inventory/migrations/0010_customer_staff.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-09 11:36 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0009_alter_staff_managers'), - ] - - operations = [ - 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'), - ), - ] diff --git a/inventory/migrations/0011_remove_customer_country_customer_city.py b/inventory/migrations/0011_remove_customer_country_customer_city.py deleted file mode 100644 index 29473352..00000000 --- a/inventory/migrations/0011_remove_customer_country_customer_city.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-09 20:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0010_customer_staff'), - ] - - operations = [ - migrations.RemoveField( - model_name='customer', - name='country', - ), - migrations.AddField( - model_name='customer', - name='city', - field=models.CharField(blank=True, max_length=255, verbose_name='City'), - ), - ] diff --git a/inventory/migrations/0012_opportunity_probability.py b/inventory/migrations/0012_opportunity_probability.py deleted file mode 100644 index b30a12ac..00000000 --- a/inventory/migrations/0012_opportunity_probability.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-11 10:32 - -import inventory.models -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0011_remove_customer_country_customer_city'), - ] - - operations = [ - migrations.AddField( - model_name='opportunity', - name='probability', - field=models.PositiveIntegerField(default=70, validators=[inventory.models.validate_probability]), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0013_lead_phone_number.py b/inventory/migrations/0013_lead_phone_number.py deleted file mode 100644 index b9ac98ba..00000000 --- a/inventory/migrations/0013_lead_phone_number.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-11 11:09 - -import phonenumber_field.modelfields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0012_opportunity_probability'), - ] - - operations = [ - migrations.AddField( - model_name='lead', - name='phone_number', - field=phonenumber_field.modelfields.PhoneNumberField(default='0535521547', max_length=128, region='SA', verbose_name='Phone Number'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0014_alter_activity_created_by_alter_notes_created_by.py b/inventory/migrations/0014_alter_activity_created_by_alter_notes_created_by.py deleted file mode 100644 index b25d5a2b..00000000 --- a/inventory/migrations/0014_alter_activity_created_by_alter_notes_created_by.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-11 12:12 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0013_lead_phone_number'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AlterField( - model_name='activity', - name='created_by', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='notes', - name='created_by', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/inventory/migrations/0015_lead_city.py b/inventory/migrations/0015_lead_city.py deleted file mode 100644 index 33f6c4b2..00000000 --- a/inventory/migrations/0015_lead_city.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-11 12:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0014_alter_activity_created_by_alter_notes_created_by'), - ] - - operations = [ - migrations.AddField( - model_name='lead', - name='city', - field=models.CharField(default='Riyadh', max_length=50, verbose_name='City'), - preserve_default=False, - ), - ] diff --git a/inventory/migrations/0016_lead_address.py b/inventory/migrations/0016_lead_address.py deleted file mode 100644 index 62d58179..00000000 --- a/inventory/migrations/0016_lead_address.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-11 12:31 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0015_lead_city'), - ] - - operations = [ - migrations.AddField( - model_name='lead', - name='address', - field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Address'), - ), - ] diff --git a/inventory/migrations/0017_alter_lead_assigned.py b/inventory/migrations/0017_alter_lead_assigned.py deleted file mode 100644 index 1e7aaf8f..00000000 --- a/inventory/migrations/0017_alter_lead_assigned.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-11 19:20 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0016_lead_address'), - ] - - operations = [ - migrations.AlterField( - 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'), - ), - ] diff --git a/inventory/migrations/0018_alter_lead_priority.py b/inventory/migrations/0018_alter_lead_priority.py deleted file mode 100644 index 4ddbaa54..00000000 --- a/inventory/migrations/0018_alter_lead_priority.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-11 23:36 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0017_alter_lead_assigned'), - ] - - operations = [ - migrations.AlterField( - model_name='lead', - name='priority', - field=models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='medium', max_length=10, verbose_name='Priority'), - ), - ] diff --git a/inventory/migrations/0019_opportunity_closed_opportunity_closing_date_and_more.py b/inventory/migrations/0019_opportunity_closed_opportunity_closing_date_and_more.py deleted file mode 100644 index 572ebea5..00000000 --- a/inventory/migrations/0019_opportunity_closed_opportunity_closing_date_and_more.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-12 01:43 - -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('inventory', '0018_alter_lead_priority'), - ] - - operations = [ - migrations.AddField( - model_name='opportunity', - name='closed', - field=models.BooleanField(default=False, verbose_name='Closed'), - ), - migrations.AddField( - model_name='opportunity', - name='closing_date', - field=models.DateField(default=django.utils.timezone.now, verbose_name='Closing Date'), - preserve_default=False, - ), - migrations.AddField( - model_name='opportunity', - name='status', - field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], default='new', max_length=20, verbose_name='Status'), - ), - migrations.AlterField( - model_name='lead', - name='status', - field=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'), - ), - migrations.AlterField( - model_name='leadstatushistory', - name='new_status', - field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status'), - ), - migrations.AlterField( - model_name='leadstatushistory', - name='old_status', - field=models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status'), - ), - ] diff --git a/inventory/models.py b/inventory/models.py index ca265bf9..53401533 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -48,6 +48,8 @@ class StaffUserManager(UserManager): Staff.objects.create(user=user, name=name, arabic_name=arabic_name, phone_number=phone_number, staff_type=staff_type, **extra_fields) return user +class CarType(models.TextChoices): + pass class UnitOfMeasure(models.TextChoices): EACH = 'EA', 'Each' @@ -82,7 +84,7 @@ class CarMake(models.Model, LocalizedNameMixin): arabic_name = models.CharField(max_length=255, blank=True, null=True) logo = models.ImageField(_("logo"), upload_to="car_make", blank=True, null=True) is_sa_import = models.BooleanField(default=False) - car_type = models.SmallIntegerField(choices=CarType.choices) + car_type = models.SmallIntegerField(choices=CarType.choices, blank=True, null=True) def __str__(self): return self.name diff --git a/inventory/signals.py b/inventory/signals.py index fa902ff8..543db67b 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -128,23 +128,11 @@ def create_ledger_entity(sender, instance, created, **kwargs): ) asset_ca_receivables.role_default = True asset_ca_receivables.save() - - # Inventory Account - asset_ca_inventory = entity.create_account( - coa_model=coa, - code="1104", - role=roles.ASSET_CA_INVENTORY, - name=_("Inventory"), - balance_type="debit", - active=True, - ) - asset_ca_inventory.role_default = True - asset_ca_inventory.save() - + # Prepaid Expenses Account asset_ca_prepaid = entity.create_account( coa_model=coa, - code="1105", + code="1104", role=roles.ASSET_CA_PREPAID, name=_("Prepaid Expenses"), balance_type="debit", @@ -152,44 +140,55 @@ def create_ledger_entity(sender, instance, created, **kwargs): ) asset_ca_prepaid.role_default = True asset_ca_prepaid.save() - # Prepaid Expenses Account + + # Employee Expenses Account asset_ca_prepaid_employee = entity.create_account( coa_model=coa, - code="1106", + code="1105", role=roles.ASSET_CA_PREPAID, name=_("Employee Advance"), balance_type="debit", active=True, ) - - # Notes Receivable Account - asset_lti_notes_receivable = entity.create_account( + + # Inventory Account + asset_ca_inventory = entity.create_account( + coa_model=coa, + code="1106", + role=roles.ASSET_CA_INVENTORY, + name=_("Inventory"), + balance_type="debit", + active=True, + ) + asset_ca_inventory.role_default = True + asset_ca_inventory.save() + + # VAT Payable Account + liability_ltl_vat_receivable = entity.create_account( + coa_model=coa, + code="1107", + role=roles.ASSET_CA_RECEIVABLES, + name=_("VAT Receivable"), + balance_type="debit", + active=True, + ) + + # Buildings Accumulated Depreciation Account + asset_ppe_buildings_accum_depreciation = entity.create_account( coa_model=coa, code="1201", - role=roles.ASSET_LTI_NOTES_RECEIVABLE, - name=_("Notes Receivable"), - balance_type="debit", + role=roles.ASSET_PPE_BUILDINGS_ACCUM_DEPRECIATION, + name=_("Buildings - Accum. Depreciation"), + balance_type="credit", active=True, ) - asset_lti_notes_receivable.role_default = True - asset_lti_notes_receivable.save() - - # Land Account - asset_lti_land = entity.create_account( - coa_model=coa, - code="1202", - role=roles.ASSET_LTI_LAND, - name=_("Land"), - balance_type="debit", - active=True, - ) - asset_lti_land.role_default = True - asset_lti_land.save() + asset_ppe_buildings_accum_depreciation.role_default = True + asset_ppe_buildings_accum_depreciation.save() # intangible Account asset_lti_land_intangable = entity.create_account( coa_model=coa, - code="1203", + code="1202", role=roles.ASSET_INTANGIBLE_ASSETS, name=_("Intangible Assets"), balance_type="debit", @@ -198,7 +197,7 @@ def create_ledger_entity(sender, instance, created, **kwargs): asset_lti_land_intangable.role_default = True asset_lti_land_intangable.save() - # investment Account + # investment property Account asset_lti_land_investment = entity.create_account( coa_model=coa, code="1204", @@ -209,7 +208,32 @@ def create_ledger_entity(sender, instance, created, **kwargs): ) asset_lti_land_investment.role_default = True asset_lti_land_investment.save() + + # # Notes Receivable Account + # asset_lti_notes_receivable = entity.create_account( + # coa_model=coa, + # code="1201", + # role=roles.ASSET_LTI_NOTES_RECEIVABLE, + # name=_("Notes Receivable"), + # balance_type="debit", + # active=True, + # ) + # asset_lti_notes_receivable.role_default = True + # asset_lti_notes_receivable.save() + # # Land Account + # asset_lti_land = entity.create_account( + # coa_model=coa, + # code="1202", + # role=roles.ASSET_LTI_LAND, + # name=_("Land"), + # balance_type="debit", + # active=True, + # ) + # asset_lti_land.role_default = True + # asset_lti_land.save() + + # Buildings Account asset_ppe_buildings = entity.create_account( coa_model=coa, @@ -222,17 +246,7 @@ def create_ledger_entity(sender, instance, created, **kwargs): asset_ppe_buildings.role_default = True asset_ppe_buildings.save() - # Buildings Accumulated Depreciation Account - asset_ppe_buildings_accum_depreciation = entity.create_account( - coa_model=coa, - code="1302", - role=roles.ASSET_PPE_BUILDINGS_ACCUM_DEPRECIATION, - name=_("Buildings - Accum. Depreciation"), - balance_type="credit", - active=True, - ) - asset_ppe_buildings_accum_depreciation.role_default = True - asset_ppe_buildings_accum_depreciation.save() + asset_ppe_buildings_accum_depreciation = entity.create_account( coa_model=coa, code="1303", @@ -308,17 +322,17 @@ def create_ledger_entity(sender, instance, created, **kwargs): liability_ltl_vat_payable = entity.create_account( coa_model=coa, code="2106", - role=roles.LIABILITY_LTL_BONDS_PAYABLE, + role=roles.LIABILITY_CL_OTHER, name=_("VAT Payable"), balance_type="credit", active=True, ) - + # taxes Payable Account liability_ltl_taxes_payable = entity.create_account( coa_model=coa, code="2107", - role=roles.LIABILITY_LTL_NOTES_PAYABLE, + role=roles.LIABILITY_CL_OTHER, name=_("Taxes Payable"), balance_type="credit", active=True, @@ -334,6 +348,9 @@ def create_ledger_entity(sender, instance, created, **kwargs): active=True, ) + # End of Service Benefits + entity.create_account(coa_model=coa, code="2202", role=roles.LIABILITY_LTL_NOTES_PAYABLE, name=_("End of Service Benefits"), balance_type="credit", active=True) + # Mortgage Payable Account liability_ltl_mortgage_payable = entity.create_account( coa_model=coa, @@ -346,29 +363,43 @@ def create_ledger_entity(sender, instance, created, **kwargs): liability_ltl_mortgage_payable.role_default = True liability_ltl_mortgage_payable.save() - # Common Stock Account - equity_common_stock = entity.create_account( - coa_model=coa, - code="3101", - role=roles.EQUITY_COMMON_STOCK, - name=_("Common Stock"), - balance_type="credit", - active=True, - ) - equity_common_stock.role_default = True - equity_common_stock.save() - + # Capital + equity_capital = entity.create_account(coa_model=coa, code="3101", role=roles.EQUITY_CAPITAL, name=_("Registered Capital"), balance_type="credit", active=True) + equity_capital.role_default = True + equity_capital.save() + entity.create_account(coa_model=coa, code="3102", role=roles.EQUITY_CAPITAL, name=_("Additional Paid-In Capital"), balance_type="credit", active=True) + + # Other Equity + other_equity = entity.create_account(coa_model=coa, code="3201", role=roles.EQUITY_COMMON_STOCK, name=_("Opening Balances"), balance_type="credit", active=True) + other_equity.role_default = True + other_equity.save() + + # Reserves + reserve = entity.create_account(coa_model=coa, code="3301", role=roles.EQUITY_ADJUSTMENT, name=_("Statutory Reserve"), balance_type="credit", active=True) + reserve.role_default = True + reserve.save() + entity.create_account(coa_model=coa, code="3302", role=roles.EQUITY_ADJUSTMENT, name=_("Foreign Currency Translation Reserve"), balance_type="credit", active=True) + # Retained Earnings Account equity_retained_earnings = entity.create_account( coa_model=coa, - code="3102", - role=roles.EQUITY_ADJUSTMENT, - name=_("Retained Earnings"), + code="3401", + role=roles.EQUITY_PREFERRED_STOCK, + name=_("Operating Profits and Losses"), balance_type="credit", active=True, ) equity_retained_earnings.role_default = True equity_retained_earnings.save() + + equity_retained_earnings_losses = entity.create_account( + coa_model=coa, + code="3402", + role=roles.EQUITY_PREFERRED_STOCK, + name=_("Retained Earnings (or Losses)"), + balance_type="credit", + active=True, + ) # Sales Revenue Account income_operational = entity.create_account( @@ -404,6 +435,12 @@ def create_ledger_entity(sender, instance, created, **kwargs): active=True, ) + # Operating Revenues + entity.create_account(coa_model=coa, code="4104", role=roles.INCOME_OPERATIONAL, name=_("Sales/Service Revenue"), balance_type="credit", active=True) + + #Non-Operating Revenues + entity.create_account(coa_model=coa, code="4201", role=roles.INCOME_OTHER, name=_("Non-Operating Revenues"), balance_type="credit", active=True) + # Cost of Goods Sold (COGS) Account expense_cogs = entity.create_account( @@ -416,12 +453,7 @@ def create_ledger_entity(sender, instance, created, **kwargs): ) expense_cogs.role_default = True expense_cogs.save() - - # 5.1 Direct Costs - entity.create_account(coa_model=coa, code="5101", role=roles.EXPENSE_OPERATIONAL, name=_("Cost of Goods Sold"), balance_type="debit", active=True) - entity.create_account(coa_model=coa, code="5102", role=roles.EXPENSE_OPERATIONAL, name=_("Salaries and Wages"), balance_type="debit", active=True) - entity.create_account(coa_model=coa, code="5103", role=roles.EXPENSE_OPERATIONAL, name=_("Sales Commissions"), balance_type="debit", active=True) - entity.create_account(coa_model=coa, code="5104", role=roles.EXPENSE_OPERATIONAL, name=_("Shipping and Customs Clearance"), balance_type="debit", active=True) + # accrued Expenses Account expense_cogs = entity.create_account( @@ -595,12 +627,18 @@ def create_ledger_entity(sender, instance, created, **kwargs): active=True, ) + # 5.1 Direct Costs + entity.create_account(coa_model=coa, code="6201", role=roles.EXPENSE_OPERATIONAL, name=_("Cost of Goods Sold"), balance_type="debit", active=True) + entity.create_account(coa_model=coa, code="6202", role=roles.EXPENSE_OPERATIONAL, name=_("Salaries and Wages"), balance_type="debit", active=True) + entity.create_account(coa_model=coa, code="6203", role=roles.EXPENSE_OPERATIONAL, name=_("Sales Commissions"), balance_type="debit", active=True) + entity.create_account(coa_model=coa, code="6204", role=roles.EXPENSE_OPERATIONAL, name=_("Shipping and Customs Clearance"), balance_type="debit", active=True) + # 5.3 Non-Operating Expenses entity.create_account(coa_model=coa, code="6301", role=roles.EXPENSE_OTHER, name=_("Zakat"), balance_type="debit", active=True) entity.create_account(coa_model=coa, code="6302", role=roles.EXPENSE_OTHER, name=_("Taxes"), balance_type="debit", active=True) entity.create_account(coa_model=coa, code="6303", role=roles.EXPENSE_OTHER, name=_("Foreign Currency Translation"), balance_type="debit", active=True) entity.create_account(coa_model=coa, code="6304", role=roles.EXPENSE_OTHER, name=_("Interest Expenses"), balance_type="debit", active=True) - + # Create Vendor diff --git a/inventory/urls.py b/inventory/urls.py index 0ae7a21c..8ab137e7 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -162,6 +162,7 @@ urlpatterns = [ 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'), diff --git a/inventory/utils.py b/inventory/utils.py index 8a2fb532..135450fb 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -9,6 +9,7 @@ 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 decimal import Decimal def get_jwt_token(): diff --git a/inventory/views.py b/inventory/views.py index 85c76335..97ebe964 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1,5 +1,6 @@ from decimal import Decimal from django.core.paginator import Paginator +from django.forms import DateField, DateInput, HiddenInput, TextInput from django_ledger.models import ( EntityModel, InvoiceModel, @@ -334,19 +335,17 @@ class AjaxHandlerView(LoginRequiredMixin, View): # Validate inputs if not model_id or not year: - return JsonResponse({"error": "Missing required parameters: model_id or year"}, status=400) + return JsonResponse( + {"error": "Missing required parameters: model_id or year"}, status=400 + ) try: year = int(year) except ValueError: return JsonResponse({"error": "Invalid year format"}, status=400) series = models.CarSerie.objects.filter( - id_car_model=model_id, - year_begin__lte=year, - year_end__gte=year - ).values( - "id_car_serie", "name", "arabic_name", "generation_name" - ) + id_car_model=model_id, year_begin__lte=year, year_end__gte=year + ).values("id_car_serie", "name", "arabic_name", "generation_name") return JsonResponse(list(series), safe=False) @@ -395,15 +394,19 @@ class AjaxHandlerView(LoginRequiredMixin, View): return JsonResponse(serialized_specs, safe=False) def get_equipments(self, request): - trim_id = request.GET.get('trim_id') - equipments = models.CarEquipment.objects.filter( - id_car_trim=trim_id - ).values('id_car_equipment', 'name').order_by('name') + trim_id = request.GET.get("trim_id") + equipments = ( + models.CarEquipment.objects.filter(id_car_trim=trim_id) + .values("id_car_equipment", "name") + .order_by("name") + ) return JsonResponse(list(equipments), safe=False) def get_options(self, request): - equipment_id = request.GET.get('equipment_id') - car_option_values = models.CarOptionValue.objects.filter(id_car_equipment=equipment_id) + equipment_id = request.GET.get("equipment_id") + car_option_values = models.CarOptionValue.objects.filter( + id_car_equipment=equipment_id + ) options_by_parent = {} for value in car_option_values: @@ -412,16 +415,19 @@ class AjaxHandlerView(LoginRequiredMixin, View): parent_id = parent.id_car_option if parent else 0 parent_name = parent.name if parent else "Root" if parent_id not in options_by_parent: - options_by_parent[parent_id] = {'parent_name': parent_name, 'options': []} + options_by_parent[parent_id] = { + "parent_name": parent_name, + "options": [], + } option_data = { - 'option_id': option.id_car_option, - 'option_name': option.name, - 'is_base': value.is_base, - 'equipment_name': value.id_car_equipment.name + "option_id": option.id_car_option, + "option_name": option.name, + "is_base": value.is_base, + "equipment_name": value.id_car_equipment.name, } - options_by_parent[parent_id]['options'].append(option_data) + options_by_parent[parent_id]["options"].append(option_data) serialized_options = [ - {'parent_name': v['parent_name'], 'options': v['options']} + {"parent_name": v["parent_name"], "options": v["options"]} for v in options_by_parent.values() ] return JsonResponse(serialized_options, safe=False) @@ -827,14 +833,18 @@ class CustomerDetailView(LoginRequiredMixin, DetailView): context["estimates"] = entity.get_estimates().filter( customer__customer_name=name ) - context['notes'] = models.Notes.objects.filter(content_type__model='customer', object_id=self.object.id) - context['activities'] = models.Activity.objects.filter(content_type__model='customer', object_id=self.object.id) + context["notes"] = models.Notes.objects.filter( + content_type__model="customer", object_id=self.object.id + ) + context["activities"] = models.Activity.objects.filter( + content_type__model="customer", object_id=self.object.id + ) return context def add_note_to_customer(request, pk): customer = get_object_or_404(models.Customer, pk=pk) - if request.method == 'POST': + if request.method == "POST": form = forms.NoteForm(request.POST) if form.is_valid(): note = form.save(commit=False) @@ -842,24 +852,27 @@ def add_note_to_customer(request, pk): note.created_by = request.user note.save() - return redirect('customer_detail', pk=pk) + return redirect("customer_detail", pk=pk) else: form = forms.NoteForm() - return render(request, 'crm/add_note.html', {'form': form, 'customer': customer}) + return render(request, "crm/add_note.html", {"form": form, "customer": customer}) + def add_activity_to_customer(request, pk): customer = get_object_or_404(models.Customer, pk=pk) - if request.method == 'POST': + if request.method == "POST": form = forms.ActivityForm(request.POST) if form.is_valid(): activity = form.save(commit=False) activity.content_object = customer activity.created_by = request.user activity.save() - return redirect('customer_detail', pk=pk) + return redirect("customer_detail", pk=pk) else: form = forms.ActivityForm() - return render(request, 'crm/add_activity.html', {'form': form, 'customer': customer}) + return render( + request, "crm/add_activity.html", {"form": form, "customer": customer} + ) class CustomerCreateView( @@ -1467,6 +1480,7 @@ class RepresentativeListView(LoginRequiredMixin, ListView): data = models.Representative.objects.filter(dealer=dealer).all() return data + class RepresentativeDetailView(DetailView): model = models.Representative template_name = "representatives/representative_detail.html" @@ -1689,9 +1703,7 @@ class BankAccountListView(LoginRequiredMixin, ListView): def get_queryset(self): dealer = get_user_type(self.request) - return BankAccountModel.objects.filter( - entity_model=dealer.entity - ) + return BankAccountModel.objects.filter(entity_model=dealer.entity) class BankAccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): @@ -1807,7 +1819,12 @@ class AccountUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): template_name = "ledger/coa_accounts/account_form.html" success_url = reverse_lazy("account_list") success_message = "Account updated successfully." - + + def get_form(self, form_class=None): + form = super().get_form(form_class) + form.fields['_ref_node_id'].widget = HiddenInput() + form.fields['_position'].widget = HiddenInput() + return form @login_required def account_delete(request, pk): @@ -1990,6 +2007,7 @@ def create_estimate(request): for x in car_list ], } + print(context) return render(request, "sales/estimates/estimate_form.html", context) @@ -2208,9 +2226,8 @@ def invoice_create(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) entity = request.user.dealer.entity - form = InvoiceModelCreateForm(entity_slug=entity.slug, user_model=entity.admin) if request.method == "POST": - form = InvoiceModelCreateForm( + form = forms.InvoiceModelCreateForm( request.POST, entity_slug=entity.slug, user_model=entity.admin ) if form.is_valid(): @@ -2257,7 +2274,23 @@ def invoice_create(request, pk): invoice.save() messages.success(request, "Invoice created successfully!") return redirect("invoice_detail", pk=invoice.pk) - form.initial["customer"] = estimate.customer + form = forms.InvoiceModelCreateForm( + entity_slug=entity.slug, user_model=entity.admin + ) + + form.initial.update( + { + "customer": estimate.customer, + "cash_account": entity.get_default_coa_accounts().get(name="Cash"), + "prepaid_account": entity.get_default_coa_accounts().get( + name="Accounts Receivable" + ), + "unearned_account": entity.get_default_coa_accounts().get( + name="Deferred Revenue" + ), + } + ) + context = { "form": form, "estimate": estimate, @@ -2289,30 +2322,29 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView): def PaymentCreateView(request, pk=None): invoice = InvoiceModel.objects.filter(pk=pk).first() + entity = request.user.dealer.entity - form = forms.PaymentForm() if request.method == "POST": form = forms.PaymentForm(request.POST) if form.is_valid(): amount = form.cleaned_data.get("amount") invoice = form.cleaned_data.get("invoice") - if amount > invoice.amount_due: - messages.error( - request, "Payment amount is greater than invoice amount due" - ) - return redirect("payment_create", pk=invoice.pk) - if amount <= 0: - messages.error(request, "Payment amount must be greater than 0") - return redirect("payment_create", pk=invoice.pk) - if ( - invoice.amount_due == invoice.amount_paid - or invoice.invoice_status == "paid" - ): - messages.error(request, "Invoice is already fully paid") - return redirect("invoice_detail", pk=invoice.pk) + payment_method = form.cleaned_data.get("payment_method") ledger = None 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 + total_amount += models.Car.objects.get( + vin=x.item_model.name + ).finances.total_discount + ledger = LedgerModel.objects.filter( name=str(invoice.pk), entity=entity ).first() @@ -2323,13 +2355,29 @@ def PaymentCreateView(request, pk=None): locked=False, origin="Payment", ) - cash_account = entity.get_default_coa_accounts().get(name="Cash") - accounts_receivable = entity.get_default_coa_accounts().get( - name="Accounts Receivable" + 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=cash_account, # Debit Cash + account=debit_account, # Debit Cash amount=amount, # Payment amount tx_type="debit", description="Payment Received", @@ -2337,27 +2385,31 @@ def PaymentCreateView(request, pk=None): TransactionModel.objects.create( journal_entry=journal, - account=accounts_receivable, # Credit Accounts Receivable - amount=amount, # Payment amount + account=credit_account, # Credit Accounts Receivable + amount=total_amount, # Payment amount tx_type="credit", description="Payment Received", ) - journal.posted = True - invoice.make_payment(amount) - journal.save() - invoice.save() - if invoice.amount_due == invoice.amount_paid: - invoice.mark_as_paid( - entity_slug=entity.slug, user_model=entity.admin + 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.save() - ledger.post() - ledger.save() - messages.success(request, "Payment created successfully!") - return redirect("invoice_detail", pk=invoice.pk) + + invoice.make_payment(amount) + invoice.save() 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) + form = forms.PaymentForm() + form.initial["amount"] = invoice.amount_due + if invoice: form.initial["invoice"] = invoice return render( @@ -2375,7 +2427,39 @@ def PaymentListView(request): def PaymentDetailView(request, pk): journal = JournalEntryModel.objects.filter(pk=pk).first() - return render(request, "sales/payments/payment_details.html", {"journal": journal}) + transactions = ( + TransactionModel.objects.filter(journal_entry=journal) + .order_by("account__code") + .all() + ) + return render( + request, + "sales/payments/payment_details.html", + {"journal": journal, "transactions": transactions}, + ) + + +def payment_mark_as_paid(request, pk): + invoice = get_object_or_404(InvoiceModel, pk=pk) + if request.method == "POST": + try: + if invoice.amount_due == invoice.amount_paid: + if not invoice.is_paid() and invoice.can_pay(): + invoice.mark_as_paid( + entity_slug=invoice.ledger.entity.slug, + user_model=invoice.ledger.entity.admin, + ) + invoice.save() + + invoice.ledger.lock_journal_entries() + invoice.ledger.post_journal_entries() + + invoice.ledger.post() + invoice.ledger.save() + messages.success(request, "Payment created successfully!") + except Exception as e: + messages.error(request, f"Error: {str(e)}") + return redirect("invoice_detail", pk=invoice.pk) # activity log @@ -2395,8 +2479,8 @@ class UserActivityLogListView(ListView): # CRM RELATED VIEWS class LeadListView(ListView): model = models.Lead - template_name = 'crm/leads/lead_list.html' - context_object_name = 'leads' + template_name = "crm/leads/lead_list.html" + context_object_name = "leads" paginate_by = 10 def get_queryset(self): @@ -2404,24 +2488,31 @@ class LeadListView(ListView): leads = models.Lead.objects.filter(dealer=dealer).all() return leads + class LeadDetailView(DetailView): model = models.Lead - template_name = 'crm/leads/lead_detail.html' + template_name = "crm/leads/lead_detail.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['notes'] = models.Notes.objects.filter(content_type__model='lead', object_id=self.object.id) - context['activities'] = models.Activity.objects.filter(content_type__model='lead', object_id=self.object.id) - context['status_history'] = models.LeadStatusHistory.objects.filter(lead=self.object) + context["notes"] = models.Notes.objects.filter( + content_type__model="lead", object_id=self.object.id + ) + context["activities"] = models.Activity.objects.filter( + content_type__model="lead", object_id=self.object.id + ) + context["status_history"] = models.LeadStatusHistory.objects.filter( + lead=self.object + ) return context + class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin): model = models.Lead form_class = forms.LeadCreateForm - template_name = 'crm/leads/lead_create_form.html' + template_name = "crm/leads/lead_create_form.html" success_message = "Lead created successfully!" - success_url = reverse_lazy('lead_list') - + success_url = reverse_lazy("lead_list") def form_valid(self, form): print("Form data:", form.cleaned_data) # Debug form data @@ -2433,18 +2524,19 @@ class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin): class LeadUpdateView(UpdateView): model = models.Lead form_class = forms.LeadUpdateForm - template_name = 'crm/leads/lead_update_form.html' - success_url = reverse_lazy('lead_list') + template_name = "crm/leads/lead_update_form.html" + success_url = reverse_lazy("lead_list") + class LeadDeleteView(DeleteView): model = models.Lead - template_name = 'crm/leads/lead_confirm_delete.html' - success_url = reverse_lazy('lead_list') + template_name = "crm/leads/lead_confirm_delete.html" + success_url = reverse_lazy("lead_list") def add_note_to_lead(request, pk): lead = get_object_or_404(models.Lead, pk=pk) - if request.method == 'POST': + if request.method == "POST": form = forms.NoteForm(request.POST) if form.is_valid(): note = form.save(commit=False) @@ -2452,24 +2544,25 @@ def add_note_to_lead(request, pk): note.created_by = request.user note.save() - return redirect('lead_detail', pk=pk) + return redirect("lead_detail", pk=pk) else: form = forms.NoteForm() - return render(request, 'crm/add_note.html', {'form': form, 'lead': lead}) + return render(request, "crm/add_note.html", {"form": form, "lead": lead}) + def add_activity_to_lead(request, pk): lead = get_object_or_404(models.Lead, pk=pk) - if request.method == 'POST': + if request.method == "POST": form = forms.ActivityForm(request.POST) if form.is_valid(): activity = form.save(commit=False) activity.content_object = lead activity.created_by = request.user activity.save() - return redirect('lead_detail', pk=pk) + return redirect("lead_detail", pk=pk) else: form = forms.ActivityForm() - return render(request, 'crm/add_activity.html', {'form': form, 'lead': lead}) + return render(request, "crm/add_activity.html", {"form": form, "lead": lead}) class OpportunityCreateView(CreateView): diff --git a/templates/header.html b/templates/header.html index a3091b95..9006cb7d 100644 --- a/templates/header.html +++ b/templates/header.html @@ -138,7 +138,7 @@ - diff --git a/templates/sales/estimates/estimate_form.html b/templates/sales/estimates/estimate_form.html index 3e3c943a..bc2470fb 100644 --- a/templates/sales/estimates/estimate_form.html +++ b/templates/sales/estimates/estimate_form.html @@ -38,7 +38,7 @@
- + {% trans "Cancel" %}
diff --git a/templates/sales/invoices/invoice_detail.html b/templates/sales/invoices/invoice_detail.html index 367323b8..3f1f6ac7 100644 --- a/templates/sales/invoices/invoice_detail.html +++ b/templates/sales/invoices/invoice_detail.html @@ -27,6 +27,31 @@ + + + @@ -39,8 +64,11 @@ {% endif %} {% if invoice.invoice_status == 'approved' %} - {% trans 'Record Payment' %} + {% trans 'Record Payment' %} {% endif %} + {% if not invoice.is_paid %} + + {% endif %} {% trans 'Preview' %} @@ -54,20 +82,63 @@

{% trans 'Paid Amount' %}

-

${{invoice.amount_paid}}

+

${{invoice.amount_paid}}

Owned ${{invoice.get_amount_open}}
-
{{invoice.get_progress_percent}}%
+
{{invoice.get_progress_percent}}%
+
+ + + {% if 'net' in invoice.terms %} +
+
+
+
+

+
+ + + + + + + + + + + + + + + + + +
{% trans 'Terms' %}:{{invoice.terms}}
{% trans 'Date Due' %}:{{invoice.date_due}}
{% trans 'Due in Days' %}: + {{invoice.due_in_days}} +
{% trans 'Is Past Due' %}: + {% if invoice.is_past_due %} + {% trans 'Yes' %} + {% else %} + {% trans 'No' %} + {% endif %} +
+ +
-
+ + {% endif %}

{% trans 'Due Amount' %}

-

${{invoice.amount_due}}

+ {% if invoice.is_paid %} +

${{invoice.amount_due}}

+ {% else %} +

${{invoice.amount_due}}

+ {% endif %}
@@ -75,7 +146,7 @@ -
+
@@ -188,7 +259,7 @@
-
+
{% endblock %} {% block extra_js %} diff --git a/templates/sales/payments/payment_details.html b/templates/sales/payments/payment_details.html index ebf48084..d7612410 100644 --- a/templates/sales/payments/payment_details.html +++ b/templates/sales/payments/payment_details.html @@ -16,21 +16,21 @@ {% trans "Timestamp" %} {% trans "Account Name" %} {% trans "Account Code" %} - {% trans "Description" %} - {% trans "Credit" %} {% trans "Debit" %} + {% trans "Credit" %} + {% trans "Description" %} - {% for transaction in journal.transactionmodel_set.all %} + {% for transaction in transactions %} {{ forloop.counter }} {{ transaction.created|date}} - {{ transaction.account.name }} + {{ transaction.account.name }} {{ transaction.account.code }} - {{ transaction.description }} - {% if transaction.tx_type == "credit" %}${{ transaction.amount }}{% endif %} {% if transaction.tx_type == "debit" %}${{ transaction.amount }}{% endif %} + {% if transaction.tx_type == "credit" %}${{ transaction.amount }}{% endif %} + {{ transaction.description }} {% empty %} diff --git a/templates/sales/payments/payment_form.html b/templates/sales/payments/payment_form.html index 74a3371a..91e1f0c9 100644 --- a/templates/sales/payments/payment_form.html +++ b/templates/sales/payments/payment_form.html @@ -37,4 +37,3 @@ {% endblock content %} -