From 1c668d3f0aad182984311d8df19cf9e25102f863 Mon Sep 17 00:00:00 2001 From: Marwan Alwali Date: Mon, 13 Jan 2025 21:38:18 +0300 Subject: [PATCH] update --- api/migrations/0001_initial.py | 2 +- .../__pycache__/0001_initial.cpython-311.pyc | Bin 1068 -> 1068 bytes haikalbot/migrations/0001_initial.py | 2 +- haikalbot/migrations/0002_initial.py | 2 +- inventory/__pycache__/admin.cpython-311.pyc | Bin 6916 -> 7017 bytes inventory/__pycache__/forms.cpython-311.pyc | Bin 29986 -> 29089 bytes inventory/__pycache__/models.cpython-311.pyc | Bin 82841 -> 83059 bytes .../__pycache__/services.cpython-311.pyc | Bin 4711 -> 4778 bytes inventory/__pycache__/urls.cpython-311.pyc | Bin 20831 -> 21119 bytes inventory/__pycache__/utils.cpython-311.pyc | Bin 10566 -> 10515 bytes inventory/__pycache__/views.cpython-311.pyc | Bin 135223 -> 140435 bytes inventory/forms.py | 31 +- inventory/migrations/0001_initial.py | 236 +++++++---- .../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 | 16 + inventory/urls.py | 4 +- inventory/views.py | 83 +++- requirements.txt | 93 ++++- templates/crm/leads/lead_create_form.html | 388 ------------------ templates/crm/leads/lead_form.html | 20 + templates/crm/leads/lead_update_form.html | 232 ----------- templates/header.html | 42 +- templates/inventory/car_form.html | 3 +- templates/inventory/car_inventory.html | 281 ++++++------- templates/inventory/inventory_stats.html | 35 +- templates/inventory/scan_vin.html | 210 +++++----- .../bank_accounts/bank_account_list.html | 180 +++----- templates/partials/pagination.html | 40 ++ 43 files changed, 748 insertions(+), 1770 deletions(-) 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 delete mode 100644 templates/crm/leads/lead_create_form.html create mode 100644 templates/crm/leads/lead_form.html delete mode 100644 templates/crm/leads/lead_update_form.html create mode 100644 templates/partials/pagination.html diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py index de2ca279..b1728ba5 100644 --- a/api/migrations/0001_initial.py +++ b/api/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.4 on 2025-01-07 22:27 +# Generated by Django 5.1.4 on 2025-01-12 17:20 from django.db import migrations, models diff --git a/api/migrations/__pycache__/0001_initial.cpython-311.pyc b/api/migrations/__pycache__/0001_initial.cpython-311.pyc index 92bbc91c07bf1007000e915c6885895c8322f600..bc610fab234feee2250e7c3fca8d9028dd6968e5 100644 GIT binary patch delta 19 ZcmZ3(v4(?dIWI340}$N&xsgkW1pqPI1n~d> delta 19 ZcmZ3(v4(?dIWI340}w2_w~XSTPvnze%-N`($UgCa>g1X1;*;Chg&9>RFJKg#yn&sU zQEl=*c2!|@CWchTELM;%5J+Xt(txU9LzNCp z6(<+aJZ`vox)>_;z$z6up>AW;-yFjEnv2n3vV+J$ULNPfBGV`2cZSwUJrAeA{w9i(cqKL-aRM1=;13QeF2WvHwchO9Q&7A~MI+;CfTFjVP+ zRq+5-@xoLw>TRCH`I?JSe{!A3!Ogy+`HWoYKqoN*adFe;V`5>9j1MOJOE^r9m5`i# MK~jA4GKs@X0NOA#6#xJL diff --git a/inventory/__pycache__/forms.cpython-311.pyc b/inventory/__pycache__/forms.cpython-311.pyc index ee9a7a39e563e70860c99a98eeb1529a7955c80b..422785dac2757f948b0f435ef5655f52debaaa04 100644 GIT binary patch delta 320 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­`NR-G z$uV6eC$33Js57)XR;)J20wQB40`#CnlG3Y6b-U`}=zV8WyoeWna4w7&GbS{Xp8Mxz z7G)J=bQrxC?OBf<<;`S!XEskem3T4OK!GGiCHEEpmD0>2=3yj5iP#EydQYbc;@iCxgjC^%q> zbN&tA^&zp~B8QwmjS$J=P3PzYF>O3=JQK}o`w5OwFKS3G>W1bLi(e9gnY~3VmD$g! zN#GUdhY8|S&F5NZcsb1ZZlZYAn~qXNSSC)33U}^J5|0{MX~O9=7pL%QF7Xe1`CaGK zks<+iQ%uNm9vvw{M3(dHNHI&CarPS}a^iU(dSvfR(>Js{XO(xTrDm14sY&2%(w1$I z`j>uiXRF@%*(kBfhx+5IFMg{?dn(QT%6>R zWtmv=Tp+J#95BpOs-;WiaYZ3zeMBE*Z{|OqGUGyLH aPeS~=6Fble6U9iUVWJq(QZ`W}`~E-8Dj*C1 delta 13254 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 diff --git a/inventory/__pycache__/services.cpython-311.pyc b/inventory/__pycache__/services.cpython-311.pyc index 3af68731f914633e1066bd5bcebcdcf14b730c01..6d1672976cb664f1154a7be0cb3dd648a04995ad 100644 GIT binary patch delta 110 zcmaE^vPzYAIWI340}!lv(420zk@p!ZM+J~KouPzrvLc(xWO+6^lQ%8vT7FSAY za(+r`d|75*5f4z+cE!ooY}}I-*i=PR7=jrz8T~YwZgHig iCg-Q5#+PO0O>Si~Wwf5Wn@yJWD;tB5=;p6%Te$&wdKT#b diff --git a/inventory/__pycache__/urls.cpython-311.pyc b/inventory/__pycache__/urls.cpython-311.pyc index 23222acb73bb426a7b0f40ba3747e78f3bf3d63a..e5d0ce9740e48325a68a59227a115cd0ca248998 100644 GIT binary patch delta 2043 zcmY+EZA?>V6vw&rsoYit!8wKchOE6RAX5<)t!*tY1=>Pe3Q|O@+=^hOP~HSYSmxqB z>`e}4eo|)4blEaDN!((x#mp}zOSaqPE-IRsWt$&1H@i3YYTUWckz~6`|M&dP`9J5} z`#d+jdscXSPDnpLf4(sVui{6?+D~mdk-kK@^1gI;eySXYI<^>`>2H9jH^aB-X6-Hv zYgzqBMAzv6cg8w6opFWNn--kp_Xi7JPn9P$QwE^PqqWv9d6F+Yi8=3tz7R}VW{|UJ z?TUr5=}QYw>v#j-;uw6hm#9%|Vg$88v2Y@lWU&mZl4&}GzToG>Zc{A9%ho<4Y z+kD;~%*An@z{kUgsSzHUx_QI)rSwMkG$VYvbR#}bw*EU@Fa*1^a$(yt+=TnQ_`Iwo2%(Mk!(mX7B=&7B=x1Cg%2SbV6c zHxiDw9VnVHYCeMLGt%;T!gJFfZ+uOskK6GHz`NOIOb>9YGbY3F&0|6hq;1)-+KbhA zttNGv_)umt1O5AY+u|MFeI2o`x@fqoHy)S{Z#gRn?Z8UCLKFg}+k|d-wX{a)hufw3 z!r*k;wzx3&0j?JC*K1w`$F`3M(Fv=B?$y?{+8XN`)KTkt)Qi>?sGnQ6q8fH+>f}1Q zBOl|pxPG}q^FQWszD>(F+6qvQ*|csKd3?vV4&%9YO-t>oP+NHX5|1z1HUAxZA*#`# z=@v)foM>uBYO)@z4vQ{`sYypJ>SeAs9a^Kbotl<#jc}dZskQ!q$M<+_aB7(Xr#7I; zskJ`tEKK9A2LyQ5W;P58oDOr+4WT2mOzUgs8Y|P*Pn2mZuX6p7>(X+~Q^R$*TdA2X|Y>7OFn&-GaPhr6X3#u%bY$o^KS+{wrUzMy^Jqq(Dm|tc7 zWSAWLW*vnW`qiS!>s<tvKP*N%7 z#|XOr*SbRS>{VDe!NMvFCws^ehZoV`OVBs#FSrm<*HtUN|M>gKG5|A`Cf#6a6n3~) z(bq5xD_tgWgz85)j1nA${VsF<7(tot$qDfHbSl*$g*7Kwv&x#2)o|Hm7HbIH z@U_b#dI-FbS!EGx3F^RBWfpw|eu!4(hyj8Ic%=$YjbImigwR9~g8K-~1iQghEr~4z zdpNWb$S{PJ2*G~1fY45G0B#_35Ol(v8r*+^D2GD?eIR3{pI`t0VUXZ3e1b4UFbqE- zj1b_SxlM*q0y~2;_cI0u<2$mGL66%aI*H2Qj9U`R2`b<-gh~PzJaU`FDgrmG^jHiY z0xyG1J8m6O9gKQRqL082QwRZqdbsA1#2`Tf{EW~@unTg%IbsvRZg6@nVhh0@=<%Aw zR)W1S>6OGV!9KX=mGavNWcK9bi{Fc=B{hn_O<{6^$tshR5wh%uMYY9ZJHY|)A#@OQ z!ppVAYY!3(u#`E*@p{E*Iu%rxZTN7xqIV_qE>-VJ4wBvcy(eq9A!t)5V1b(=L5Fn_BuMmO+4Y0r`85=o_&rR+DhcAcyfPdKk0tVG3ST*obxcFP5OV&@4U}> z-qX{j4_1X6D?8_?B;VUem#sUDb@k&|ZBe?MrRl=L8#U zRv#{N;8UXPSfVZ#Zj7x0Uwj}Ij*d)Bjf{_Zj)lfz(bmMlBP)V12v?tT38S#jXcWev z*VrP&;9X<65Kn9xqe9NlxLUw>EcGb(YUZ)>{bY;52eZeXpK`1m6}zQ*(K z2KIomM)rXA#>y<-dRl-7bvo6Iz%|RIn8S*jjjW@(iDhjQ>lkWcD^K$Ll;=4 z620jjs_1=KQMndRJ=J_=EGhPEh&?H>C*4OC{jlCFOCiD#+(Qf#j)H8Jr4a&tXscZQ zJmJ{?x>}OX{-hY%5JM?3lzxFq#$XNW#|aY;>nqjwoGesU!;btr{stm;#ec zBgF~R95aMjjyb}Muw>Jf&l60FowMMdv>|B^B*ox{7)*)5v{}Iq(gK?{ozzUQg2Jws zYy>+TLbMPZ&}Y|4PJ#hd}SZuYqusV}vjPui~9aLKG5+7-0&2K*R~tP~eoM83LY} zQ=^(Ayrh7L^GVgqqy+^mJN43Wk_kS;Rc4Xzp&ougG!Pm=>(WR~1S^!mJ20G8bvDM;vs4{=Qo zp%-qsL3Nb^Ng;vBI!3tBYvSd^8 pleEJ-h!%nizDBqS9uT~ECVjG07Bjp@ecmQ{sSiBE4}~# diff --git a/inventory/__pycache__/utils.cpython-311.pyc b/inventory/__pycache__/utils.cpython-311.pyc index a6ad92f4985ce003ef7c07c3f2e653842908ea14..83420281910e8c190a6f1abbfa7a518b386d8d76 100644 GIT binary patch delta 1606 zcmZuxOKe+36!qANKk+9{;y9lY*Pk?wp>dl<=qLS9wIK*96s5Exf%5w5H*p`lPjctk zX-Y*4MS`L(2-8iGP`W@us>I45u|R?q5=*vE0;CEgDuk2;3q&{EJ5EB2u;k7V&?oRqJT7L!)?vJqUhy$%Q)iLFmO}IAKR$VXz zjN?tA)f@gb$O`hW=oH&4tFaQRwB}>)F*YUru95scl!5(ZBD_-5EbeUCy{_!Sq#TG> z*nW8;ewS6`{rKVV0a7?9i-{r&%b7$WoFHOaT8Ss8kJ2$m#@HKh1b;;o0fVH^Gwn#z z7`ukC)*6=XnXV(?G|E(3KO|g+E>5J*v#8uiy^vLTbqVem8n<=Rg0m?1+(+HhD0MfV zk#pVOAAT7rSRnnN9|6A7;KBnUMG#@TI7xEZ5F9jbZJEP)yqc3|(&y07z4S}7FDiYd z*0#m`sObn#cWPWPveS-ykjxldx6sfMxVE+}1Q$?fLC*AyV{M(DP>@YX=)08dMA{EI z?y4X2Oq(0r@^lEx&Av=(hOWXj;JU}*71^7qY8^;{YV#Of#%3J4P)DeA zzyzINCEx^k1VQRl=60-c;FxcdW2FIB@ajTqBXcNN4&%{}oYTE|a!gUcvqW_+nIJqr za*EeXTesxHYnt~GT}(`~omcyJYUT>Of9K#q()CB?vTOuaQw?O>vHf7i$C znhNn6nU0wrw`~`*uXKU(A3BLasS1R!q<$9(Xi5U6Py?PPwWQ#dWjb|j8O$1oRpR0` zuB|k5$1}Y(s3P-dn_&`(E92CpB~v~eDYBa~m%D>a{wnw9ane`b;e9%(%x}`^1A-m5 zKq12|g2(L3-*+Eiw`D1RRZZXLQLiu>m=|N!J9<(x3g;yvC~&* z%7dlc>`5fC%wlI?lSD+ZnyTu4NHpwrGXE*b4U!A<%O1W$Se2iYax5pmE=|)u!E>`U zFgh1BDY8o5PaCi6PTj4FOD=e|h9}g_`l&6t=HlR(7l8)VPi&D5&$I+|A_ZCEW2V#O zj_1N!dsO28Dlb7q{xp`|6C=67NA;^7=#FTw6QI=%V{)iG%Hr}^InNSOD-U0!j3Grp zOI58#wRqH4Q7b{cV)a(kc*6$C?Ioc6)U*0KdISsBu=p{!haBw&v4FT4VvM!^D4%5= GB>NBDn3EL% delta 1654 zcmZuxOKclO820$>_z{PsaULYGousZU#d)C8N78^q3xa}x6oN1`tHnD`macc5f5%RO zDg{&|LItAfz=2*`Dj|dr2jmmn;Q|un7OfO02c-4}H$*S||858k!m>ZleE-b5zsrOa-aG!kJSLxSMhyz+D;-HpBd{FB`9MT>@9M-xKN3ioJKq zgCcVxr+HPctHxJ<{NiVa(gYZfeXVFz%N&1NAlcSHT5 zBJT3-d!gU_EH8hJ9%U0U6B}iv_H67OHvcePOliEPHVvVub*pI#CsZ>mRTKj-g;JQN zBjMGmVsLX$_nVL>$;{Pwkxk1|{01w@@8c)K$B8{5Ctt=rR1k`OCjxx6$!!5d+I|Ge1^zin%#PBuIVfVI!C?ux=jB{_5tYdF z3uDjXzUX(adAgVr)lFNdW|iBF^q`mVuHlf1>xwLQ9lN(*bb)AVUs$zi*lNay=0v22c zOc*teywh92MoDLeSwl`{8W`e@OgYFz`^Uj+Y}2*9Nq1tp;PtwNQ5HMAC<7R!7g_)l z?R3xq#XSMkW^k6SC2ejPy1A@0K(BJwq!X`lWwoiALKhn_iKp(b8q6T^#t|xAO_z6u z^6avV4}Xj`n+o^HuZM3xLTKBWrBUvKyJf(1P5%$osnbgBbm-#M4j(v zzdTA=f-?mFr|K;fV3*L-aG1XDskCy_?3(-}x5P?U^XGD|*<+;SLxKYqiI|bQLlf*% zc{6_s&EC#WZMtS#B-<|xtl`#4Q|y#Xv2H7xDpV)T*L7RiaGJy&y8Ta=LW+nerqfNk z6R?|gFOvV{;}V@iEdrmESA&rJV6>l&$j?V-v+iT*@iJ~=_v@;mv{VBh6ybFvMF@KhA>i) zAwH&?Ep7@6HoU%y|9iOxG&CHt@(`J+TENThM4)l)^xdzqEI>7F!+UXY(q52%9v))} zIaJ89q?|7dY*N0UlVFs9W+l)JatDk%Ab=jb`-b2mttbI7K|t}l&+V`19HzTz@Ok(G iIfg&`0^;uh#@LQ;@Yccaj&J!RrZ diff --git a/inventory/__pycache__/views.cpython-311.pyc b/inventory/__pycache__/views.cpython-311.pyc index 81eaefb1ef13edb51b576d28a9f73cc1f33c6fab..a1f047979725568ea4bd01ab74c5c555b87e99bb 100644 GIT binary patch delta 36943 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 RGvqX`&9kIL$G}ef{{Tm3_Cx>x diff --git a/inventory/forms.py b/inventory/forms.py index 0f786bc0..41e5bb3a 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -567,36 +567,7 @@ class EmailForm(forms.Form): from_email = forms.EmailField() to_email = forms.EmailField(label="To") -class LeadCreateForm(forms.ModelForm): - class Meta: - model = Lead - fields = ['title', - 'first_name', - 'last_name', - 'email', - 'phone_number', - 'city', - 'salary', - 'obligations', - 'id_car_make', - 'id_car_model', - 'year', - 'source', - 'channel', - 'assigned', - 'priority', - ] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - if "id_car_make" in self.fields: - queryset = self.fields["id_car_make"].queryset.filter(is_sa_import=True) - self.fields["id_car_make"].choices = [ - (obj.id_car_make, obj.get_local_name()) for obj in queryset - ] - -class LeadUpdateForm(forms.ModelForm): +class LeadForm(forms.ModelForm): class Meta: model = Lead fields = ['title', diff --git a/inventory/migrations/0001_initial.py b/inventory/migrations/0001_initial.py index 9be8205c..a8b172e7 100644 --- a/inventory/migrations/0001_initial.py +++ b/inventory/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.4 on 2025-01-07 22:27 +# Generated by Django 5.1.4 on 2025-01-12 17:20 import django.db.models.deletion import inventory.mixins @@ -14,6 +14,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -57,7 +58,7 @@ 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=[(1, 'Car'), (2, 'Light Commercial'), (3, 'Heavy-Duty Tractors'), (4, 'Trailers'), (5, 'Medium Trucks'), (6, 'Buses'), (20, 'Motorcycles'), (21, 'Buggy'), (22, 'Moto ATV'), (23, 'Scooters'), (24, 'Karting'), (25, 'ATV'), (26, 'Snowmobiles')])), ], options={ 'verbose_name': 'Make', @@ -134,6 +135,41 @@ class Migration(migrations.Migration): ('created_at', models.DateTimeField(auto_now_add=True)), ], ), + migrations.CreateModel( + name='Activity', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('activity_type', models.CharField(choices=[('call', 'Call'), ('sms', 'SMS'), ('email', 'Email'), ('whatsapp', 'WhatsApp'), ('visit', 'Visit'), ('add_car', 'Add Car'), ('reserve_car', 'Reserve Car'), ('remove_car', 'Remove Car'), ('create_quotation', 'Create Quotation'), ('cancel_quotation', 'Cancel Quotation'), ('create_order', 'Create Order'), ('cancel_order', 'Cancel Order'), ('create_invoice', 'Create Invoice'), ('cancel_invoice', 'Cancel Invoice')], max_length=50, verbose_name='Activity Type')), + ('notes', models.TextField(blank=True, null=True, verbose_name='Notes')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Activity', + 'verbose_name_plural': 'Activities', + }, + ), + migrations.CreateModel( + name='AdditionalServices', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='Name')), + ('arabic_name', models.CharField(max_length=255, verbose_name='Arabic Name')), + ('description', models.TextField(verbose_name='Description')), + ('price', models.DecimalField(decimal_places=2, max_digits=14, verbose_name='Price')), + ('taxable', models.BooleanField(default=False, verbose_name='taxable')), + ('uom', models.CharField(choices=[('EA', 'Each'), ('PR', 'Pair'), ('SET', 'Set'), ('GAL', 'Gallon'), ('L', 'Liter'), ('M', 'Meter'), ('KG', 'Kilogram'), ('HR', 'Hour'), ('BX', 'Box'), ('RL', 'Roll'), ('PKG', 'Package'), ('DZ', 'Dozen'), ('SQ_M', 'Square Meter'), ('PC', 'Piece'), ('BDL', 'Bundle')], max_length=10, verbose_name='Unit of Measurement')), + ('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='django_ledger.itemmodel', verbose_name='Item')), + ], + options={ + 'verbose_name': 'Additional Services', + 'verbose_name_plural': 'Additional Services', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), migrations.CreateModel( name='CarFinance', fields=[ @@ -141,7 +177,7 @@ class Migration(migrations.Migration): ('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')), + ('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={ @@ -325,26 +361,6 @@ class Migration(migrations.Migration): ('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', - }, - ), migrations.CreateModel( name='CarLocation', fields=[ @@ -366,23 +382,80 @@ class Migration(migrations.Migration): name='dealer', field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'), ), + migrations.AddField( + model_name='additionalservices', + name='dealer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'), + ), migrations.CreateModel( - name='AdditionalServices', + name='Lead', 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')), + ('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')), + ('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': 'Additional Services', - 'verbose_name_plural': 'Additional Services', + 'verbose_name': 'Lead', + 'verbose_name_plural': 'Leads', + }, + ), + migrations.CreateModel( + name='Customer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(choices=[('mr', 'Mr'), ('mrs', 'Mrs'), ('ms', 'Ms'), ('miss', 'Miss'), ('dr', 'Dr'), ('prof', 'Prof'), ('prince', 'Prince'), ('princess', 'Princess'), ('company', 'Company'), ('na', 'N/A')], default='na', max_length=10, verbose_name='Title')), + ('first_name', models.CharField(max_length=50, verbose_name='First Name')), + ('middle_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='Middle Name')), + ('last_name', models.CharField(max_length=50, verbose_name='Last Name')), + ('gender', models.CharField(choices=[('m', 'Male'), ('f', 'Female')], max_length=1, verbose_name='Gender')), + ('dob', models.DateField(verbose_name='Date of Birth')), + ('email', models.EmailField(max_length=254, unique=True, verbose_name='Email')), + ('national_id', models.CharField(max_length=10, unique=True, verbose_name='National ID')), + ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', unique=True, verbose_name='Phone Number')), + ('city', models.CharField(blank=True, max_length=255, verbose_name='City')), + ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customers', to='inventory.dealer')), + ('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='converted', to='inventory.lead', verbose_name='Lead')), + ], + options={ + 'verbose_name': 'Customer', + 'verbose_name_plural': 'Customers', + }, + ), + migrations.CreateModel( + name='Notes', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('note', models.TextField(verbose_name='Note')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Note', + 'verbose_name_plural': 'Notes', }, - bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), migrations.CreateModel( name='Notification', @@ -390,46 +463,13 @@ class Migration(migrations.Migration): ('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')), + ('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_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', + 'ordering': ['-created'], }, ), migrations.CreateModel( @@ -540,8 +580,8 @@ class Migration(migrations.Migration): ('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')), + ('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)), ], @@ -551,29 +591,55 @@ class Migration(migrations.Migration): 'permissions': [], }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), + managers=[ + ('objects', inventory.models.StaffUserManager()), + ], ), migrations.CreateModel( - name='OpportunityLog', + name='Opportunity', 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')), + ('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': 'Log', - 'verbose_name_plural': 'Logs', - 'ordering': ['-created_at'], + 'verbose_name': 'Opportunity', + 'verbose_name_plural': 'Opportunities', + }, + ), + migrations.CreateModel( + name='LeadStatusHistory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('old_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status')), + ('new_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('canceled', 'Canceled')], max_length=50, verbose_name='New Status')), + ('changed_at', models.DateTimeField(auto_now_add=True, verbose_name='Changed At')), + ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='status_history', to='inventory.lead')), + ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='status_changes', to='inventory.staff')), + ], + options={ + 'verbose_name': 'Lead Status History', + 'verbose_name_plural': 'Lead Status Histories', }, ), migrations.AddField( - model_name='opportunity', - name='created_by', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deals_created', to='inventory.staff'), + model_name='lead', + name='assigned', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned', to='inventory.staff', verbose_name='Assigned'), + ), + migrations.AddField( + model_name='customer', + name='staff', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_staff', to='inventory.staff', verbose_name='Staff'), ), migrations.CreateModel( name='Subscription', 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..61ca451b 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -75,6 +75,22 @@ class VatRate(models.Model): def __str__(self): return f"Rate: {self.rate}%" +class CarType(models.IntegerChoices): + CAR = 1, _('Car') + LIGHT_COMMERCIAL = 2, _('Light Commercial') + HEAVY_DUTY_TRACTORS = 3, _('Heavy-Duty Tractors') + TRAILERS = 4, _('Trailers') + MEDIUM_TRUCKS = 5, _('Medium Trucks') + BUSES = 6, _('Buses') + MOTORCYCLES = 20, _('Motorcycles') + BUGGY = 21, _('Buggy') + MOTO_ATV = 22, _('Moto ATV') + SCOOTERS = 23, _('Scooters') + KARTING = 24, _('Karting') + ATV = 25, _('ATV') + SNOWMOBILES = 26, _('Snowmobiles') + + class CarMake(models.Model, LocalizedNameMixin): id_car_make = models.AutoField(primary_key=True) diff --git a/inventory/urls.py b/inventory/urls.py index 0ae7a21c..218b75f7 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -78,9 +78,11 @@ urlpatterns = [ path('cars/finance//update/', views.CarFinanceUpdateView.as_view(), name='car_finance_update'), path('cars/add/', views.CarCreateView.as_view(), name='car_add'), path('ajax/', views.AjaxHandlerView.as_view(), name='ajax_handler'), + path('cars/get-car-models/', views.get_car_models, name='get_car_models'), path('cars//add-color/', views.CarColorCreate.as_view(), name='add_color'), path('cars//location/add/', views.CarLocationCreateView.as_view(), name='add_car_location'), path('cars//location/update/', views.CarLocationUpdateView.as_view(), name='transfer'), + path('cars/inventory/search/', views.SearchCodeView.as_view(), name='car_search'), # path('cars//colors//update/',views.CarColorUpdateView.as_view(),name='color_update'), path('cars/reserve//', views.reserve_car_view, name='reserve_car'), @@ -183,6 +185,6 @@ handler500 = 'inventory.views.custom_error_view' handler403 = 'inventory.views.custom_permission_denied_view' handler400 = 'inventory.views.custom_bad_request_view' - + diff --git a/inventory/views.py b/inventory/views.py index 85c76335..bbdca039 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1,5 +1,7 @@ from decimal import Decimal from django.core.paginator import Paginator +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt from django_ledger.models import ( EntityModel, InvoiceModel, @@ -81,6 +83,10 @@ from django.contrib.auth.models import User from allauth.account import views from django.db.models import Count, F, Value from django.contrib.auth import authenticate +import cv2 +import numpy as np +from pyzbar.pyzbar import decode + logger = logging.getLogger(__name__) @@ -427,6 +433,38 @@ class AjaxHandlerView(LoginRequiredMixin, View): return JsonResponse(serialized_options, safe=False) +@method_decorator(csrf_exempt, name='dispatch') +class SearchCodeView(View): + template_name = "inventory/scan_vin.html" + + def get(self, request, *args, **kwargs): + """Render the form page.""" + return render(request, self.template_name) + + def post(self, request, *args, **kwargs): + image_file = request.FILES.get('image') + + if image_file: + print("image received!") + image = cv2.imdecode(np.frombuffer(image_file.read(), np.uint8), cv2.IMREAD_COLOR) + decoded_objects = decode(image) + if decoded_objects: + print("image decoded!") + print(decoded_objects[0]) + code = decoded_objects[0].data.decode('utf-8') + print("code received!") + print(code) + car = get_object_or_404(models.Car, vin=code) + name = car.id_car_make.get_local_name + print(name) + return redirect('car_detail', pk=car.pk) + else: + print("back to else statement") + return JsonResponse({'success': False, 'error': 'No code detected'}) + else: + return JsonResponse({'success': False, 'error': 'No image provided'}) + + class CarInventory(LoginRequiredMixin, ListView): model = models.Car home_label = _("inventory") @@ -594,9 +632,7 @@ class CarFinanceCreateView(LoginRequiredMixin, CreateView): def get_form(self, form_class=None): form = super().get_form(form_class) dealer = get_user_type(self.request) - form.fields[ - "additional_finances" - ].queryset = models.AdditionalServices.objects.filter(dealer=dealer) + form.fields["additional_finances"].queryset = models.AdditionalServices.objects.filter(dealer=dealer) return form # def get_initial(self): @@ -1702,17 +1738,16 @@ class BankAccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView) success_message = "Bank account created successfully." def form_valid(self, form): - form.instance.entity_model = self.request.user.dealer.entity + dealer = get_user_type(self.request) + form.instance.entity_model = dealer.entity return super().form_valid(form) def get_form_kwargs(self): - """ - Override this method to pass additional keyword arguments to the form. - """ - entity = self.request.user.dealer.entity + dealer = get_user_type(self.request) + entity = dealer.entity kwargs = super().get_form_kwargs() - kwargs["entity_slug"] = entity.slug # Get entity_slug from URL - kwargs["user_model"] = entity.admin # Get user_model from the request + kwargs["entity_slug"] = entity.slug + kwargs["user_model"] = entity.admin return kwargs @@ -1730,10 +1765,8 @@ class BankAccountUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView) success_message = "Bank account updated successfully." def get_form_kwargs(self): - """ - Override this method to pass additional keyword arguments to the form. - """ - entity = self.request.user.dealer.entity + dealer = get_user_type(self.request) + entity = dealer.entity kwargs = super().get_form_kwargs() kwargs["entity_slug"] = entity.slug # Get entity_slug from URL kwargs["user_model"] = entity.admin # Get user_model from the request @@ -1764,7 +1797,8 @@ class AccountListView(LoginRequiredMixin, ListView): paginate_by = 10 def get_queryset(self): - entity = self.request.user.dealer.entity + dealer = get_user_type(self.request) + entity = dealer.entity qs = entity.get_all_accounts() paginator = Paginator(qs, 20) page_number = self.request.GET.get("page", 1) # Default to page 1 @@ -2417,12 +2451,11 @@ class LeadDetailView(DetailView): class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin): model = models.Lead - form_class = forms.LeadCreateForm - template_name = 'crm/leads/lead_create_form.html' - success_message = "Lead created successfully!" + form_class = forms.LeadForm + template_name = 'crm/leads/lead_form.html' + # success_message = "Lead created successfully!" success_url = reverse_lazy('lead_list') - def form_valid(self, form): print("Form data:", form.cleaned_data) # Debug form data dealer = get_user_type(self.request) @@ -2430,10 +2463,18 @@ class LeadCreateView(CreateView, SuccessMessageMixin, LoginRequiredMixin): return super().form_valid(form) +def get_car_models(request): + make_id = request.GET.get('id_car_make') + if make_id: + car_models = models.CarModel.objects.filter(id_car_make=make_id).values('id_car_model', 'name', 'arabic_name') + return JsonResponse(list(car_models), safe=False) + return JsonResponse([], safe=False) + + class LeadUpdateView(UpdateView): model = models.Lead - form_class = forms.LeadUpdateForm - template_name = 'crm/leads/lead_update_form.html' + form_class = forms.LeadForm + template_name = 'crm/leads/lead_form.html' success_url = reverse_lazy('lead_list') class LeadDeleteView(DeleteView): diff --git a/requirements.txt b/requirements.txt index 02c91b3a..3839fa37 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,26 +1,43 @@ +aiohappyeyeballs==2.4.4 +aiohttp==3.11.11 +aiohttp-retry==2.8.3 +aiosignal==1.3.2 alabaster==1.0.0 +albucore==0.0.13 +albumentations==1.4.10 annotated-types==0.7.0 anyio==4.6.2.post1 +arabic-reshaper==3.0.0 asgiref==3.8.1 +astor==0.8.1 astroid==3.3.5 attrs==23.2.0 autopep8==2.3.1 babel==2.16.0 beautifulsoup4==4.12.3 +bleach==6.2.0 +blinker==1.9.0 Brotli==1.1.0 certifi==2024.8.30 cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.4.0 -click==8.1.7 +click==8.1.8 +colorama==0.4.6 +commonmark==0.9.1 +contourpy==1.3.1 crispy-bootstrap5==2024.10 cryptography==44.0.0 cssselect2==0.7.0 +cycler==0.12.1 +Cython==3.0.11 +decorator==5.1.1 +desert==2020.11.18 dill==0.3.9 distro==1.9.0 dj-rest-auth==7.0.0 dj-shop-cart==7.1.1 -Django==4.2.17 +Django==5.1.4 django-allauth==65.3.0 django-autoslug==1.9.9 django-bootstrap5==24.3 @@ -34,10 +51,13 @@ django-filter==24.3 django-formtools==2.5.1 django-ledger==0.7.0 django-money==3.5.3 +django-nine==0.2.7 +django-nonefield==0.4 django-phonenumber-field==8.0.0 django-prometheus==2.3.1 django-sekizai==4.1.0 django-silk==5.3.1 +django-sms==0.7.0 django-sslserver==0.22 django-tables2==2.7.0 django-treebeard==4.7.1 @@ -47,9 +67,13 @@ djangorestframework==3.15.2 djangorestframework-simplejwt==5.3.1 djangoviz==0.1.1 docutils==0.21.2 +easy-thumbnails==2.10 et_xmlfile==2.0.0 Faker==33.1.0 +fire==0.7.0 +Flask==3.1.0 fonttools==4.55.3 +frozenlist==1.5.0 gprof2dot==2024.6.6 graphqlclient==0.2.4 greenlet==3.1.1 @@ -57,80 +81,143 @@ h11==0.14.0 httpcore==1.0.7 httpx==0.28.0 idna==3.10 +imageio==2.36.1 imagesize==1.4.1 +imgaug==0.4.0 iso4217==1.12.20240625 isodate==0.7.2 isort==5.13.2 +itsdangerous==2.2.0 Jinja2==3.1.4 jiter==0.8.0 joblib==1.4.2 +kiwisolver==1.4.8 +lazy_loader==0.4 ledger==1.0.1 +lmdb==1.6.2 lxml==5.3.0 Markdown==3.7 markdown-it-py==3.0.0 MarkupSafe==3.0.2 +marshmallow==3.23.2 +matplotlib==3.10.0 mccabe==0.7.0 mdurl==0.1.2 +MouseInfo==0.1.3 +multidict==6.1.0 +mypy-extensions==1.0.0 +networkx==3.4.2 newrelic==10.3.1 nltk==3.9.1 +numpy==1.26.4 oauthlib==3.2.2 ofxtools==0.9.5 openai==1.56.2 +opencv-contrib-python==4.10.0.84 +opencv-python==4.10.0.84 +opencv-python-headless==4.10.0.84 openpyxl==3.1.5 +opt-einsum==3.3.0 +outcome==1.3.0.post0 packaging==24.2 +pandas==2.2.3 pango==0.0.1 pdfkit==1.0.0 phonenumbers==8.13.51 pillow==11.0.0 platformdirs==4.3.6 prometheus_client==0.21.1 +propcache==0.2.1 +protobuf==5.29.3 psycopg==3.2.3 psycopg-binary==3.2.3 +psycopg-c==3.2.3 py-moneyed==3.0 +PyAutoGUI==0.9.54 +pyclipper==1.3.0.post6 pycodestyle==2.12.1 pycparser==2.22 pydantic==2.10.3 pydantic_core==2.27.1 pydotplus==2.0.2 pydyf==0.11.0 +PyGetWindow==0.0.9 Pygments==2.18.0 PyJWT==2.10.1 pylint==3.3.2 +PyMsgBox==1.0.9 PyMySQL==1.1.1 +pyobjc-core==10.3.2 +pyobjc-framework-Cocoa==10.3.2 +pyobjc-framework-Quartz==10.3.2 pyparsing==3.2.0 +pyperclip==1.9.0 pyphen==0.17.0 pypng==0.20220715.0 +PyRect==0.2.0 +PyScreeze==1.0.1 pyserial==3.5 +PySocks==1.7.1 +python-bidi==0.6.3 python-dateutil==2.9.0.post0 +python-docx==1.1.2 python-openid==2.2.5 python3-saml==1.16.0 +pytweening==1.2.0 pytz==2024.2 pyvin==0.0.2 +pywa==2.4.0 +pywhat==5.1.0 +pywhatkit==5.4 +PyYAML==6.0.2 qrcode==8.0 +RapidFuzz==3.11.0 regex==2024.11.6 reportlab==4.2.5 requests==2.32.3 requests-oauthlib==2.0.0 rich==13.9.4 -setuptools==75.6.0 +rubicon-objc==0.4.9 +scikit-image==0.25.0 +scikit-learn==1.6.0 +scipy==1.14.1 +selenium==4.27.1 +shapely==2.0.6 six==1.16.0 sniffio==1.3.1 snowballstemmer==2.2.0 +sortedcontainers==2.4.0 soupsieve==2.6 SQLAlchemy==2.0.36 sqlparse==0.5.2 tablib==3.7.0 +termcolor==2.5.0 +threadpoolctl==3.5.0 +tifffile==2025.1.10 tinycss2==1.4.0 tinyhtml5==2.0.0 +tomli==2.2.1 tomlkit==0.13.2 tqdm==4.67.1 +trio==0.28.0 +trio-websocket==0.11.1 +twilio==9.4.1 +typing-inspect==0.9.0 typing_extensions==4.12.2 +tzdata==2024.2 +Unidecode==1.3.8 upgrade-requirements==1.7.0 urllib3==2.2.3 vin==0.6.2 vininfo==1.8.0 +vishap==0.1.5 +vpic-api==0.7.4 weasyprint==63.1 webencodings==0.5.1 +websocket-client==1.8.0 Werkzeug==3.1.3 +wikipedia==1.4.0 +wsproto==1.2.0 xmlsec==1.3.14 +yarl==1.18.3 zopfli==0.2.3.post1 diff --git a/templates/crm/leads/lead_create_form.html b/templates/crm/leads/lead_create_form.html deleted file mode 100644 index 4cd1f04a..00000000 --- a/templates/crm/leads/lead_create_form.html +++ /dev/null @@ -1,388 +0,0 @@ -{% extends "base.html" %} -{% load i18n static %} - -{% block content %} - - - -{% endblock %} \ No newline at end of file diff --git a/templates/crm/leads/lead_form.html b/templates/crm/leads/lead_form.html new file mode 100644 index 00000000..525b683f --- /dev/null +++ b/templates/crm/leads/lead_form.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} +{% load i18n static crispy_forms_filters %} +{% block content %} +

{% if object %}Update{% else %}Create{% endif %}

+
+ {% csrf_token %} + {{ form|crispy }} + + + {{ _("Cancel") }} + +
+{% endblock %} \ No newline at end of file diff --git a/templates/crm/leads/lead_update_form.html b/templates/crm/leads/lead_update_form.html deleted file mode 100644 index 3adc6662..00000000 --- a/templates/crm/leads/lead_update_form.html +++ /dev/null @@ -1,232 +0,0 @@ -{% extends "base.html" %} -{% load i18n static%} - -{% block content %} - -
-
-

{% if form.instance.pk %}{{ _("Edit Lead") }}{% else %}{{ _("Add New Lead") }}{% endif %}

-
-
-
- {% csrf_token %} - -
-
- - -
- {{ form.title.errors }} -
- - -
-
- - -
- {{ form.first_name.errors }} -
- - -
-
- - -
- {{ form.last_name.errors }} -
- - -
-
- - -
- {{ form.email.errors }} -
- - -
-
- - -
- {{ form.phone_number.errors }} -
- - -
-
- - -
- {{ form.salary.errors }} -
- - -
-
- - -
- {{ form.obligations.errors }} -
- - -
-
- - -
- {{ form.id_car_make.errors }} -
- - -
-
- - -
- {{ form.id_car_model.errors }} -
- - -
-
- - -
- {{ form.year.errors }} -
- - -
-
- - -
- {{ form.source.errors }} -
- - -
-
- - -
- {{ form.channel.errors }} -
- - -
-
- - -
- {{ form.assigned.errors }} -
- - -
-
- - -
- {{ form.priority.errors }} -
-
- - {{ _("Cancel") }} -
-
-
-
- - -{% endblock %} \ No newline at end of file diff --git a/templates/header.html b/templates/header.html index a3091b95..44b60608 100644 --- a/templates/header.html +++ b/templates/header.html @@ -10,31 +10,31 @@ diff --git a/templates/inventory/car_form.html b/templates/inventory/car_form.html index 197859b0..609a647a 100644 --- a/templates/inventory/car_form.html +++ b/templates/inventory/car_form.html @@ -282,7 +282,7 @@
-
-

- {% if form.instance.pk %} - {{ _("Edit Lead") }} - {% else %} - {{ _("Add New Lead") }} - {% endif %} -

-
- -
- -
- {% csrf_token %} - - -
-
- - -
- {{ form.title.errors }} -
- - -
-
- - -
- {{ form.first_name.errors }} -
- - -
-
- - -
- {{ form.last_name.errors }} -
- - -
-
- - -
- {{ form.email.errors }} -
- - -
-
- - -
- {{ form.phone_number.errors }} -
- - -
-
- - -
- {{ form.salary.errors }} -
- - -
-
- - -
- {{ form.obligations.errors }} -
- - -
-
- - -
- -
- - -
-
- - -
-
- - -
-
- - -
- {{ form.year.errors }} -
- - -
-
- - -
- {{ form.source.errors }} -
- - -
-
- - -
- {{ form.channel.errors }} -
- - -
-
- - -
- {{ form.assigned.errors }} -
- - -
-
- - -
- {{ form.priority.errors }} -
- - -
-
-
-