diff --git a/car_inventory/__pycache__/settings.cpython-311.pyc b/car_inventory/__pycache__/settings.cpython-311.pyc index 979c4811..76807e67 100644 Binary files a/car_inventory/__pycache__/settings.cpython-311.pyc and b/car_inventory/__pycache__/settings.cpython-311.pyc differ diff --git a/inventory/__pycache__/admin.cpython-311.pyc b/inventory/__pycache__/admin.cpython-311.pyc index 44e0e8c8..c454076d 100644 Binary files a/inventory/__pycache__/admin.cpython-311.pyc and b/inventory/__pycache__/admin.cpython-311.pyc differ diff --git a/inventory/__pycache__/models.cpython-311.pyc b/inventory/__pycache__/models.cpython-311.pyc index 2b860c9b..7ca358df 100644 Binary files a/inventory/__pycache__/models.cpython-311.pyc and b/inventory/__pycache__/models.cpython-311.pyc differ diff --git a/inventory/__pycache__/tables.cpython-311.pyc b/inventory/__pycache__/tables.cpython-311.pyc index 2f54205a..a5eed614 100644 Binary files a/inventory/__pycache__/tables.cpython-311.pyc and b/inventory/__pycache__/tables.cpython-311.pyc differ diff --git a/inventory/__pycache__/urls.cpython-311.pyc b/inventory/__pycache__/urls.cpython-311.pyc index 2e5d0ba3..ffa9b697 100644 Binary files a/inventory/__pycache__/urls.cpython-311.pyc and b/inventory/__pycache__/urls.cpython-311.pyc differ diff --git a/inventory/__pycache__/views.cpython-311.pyc b/inventory/__pycache__/views.cpython-311.pyc index 7a5c78e5..d8666eb9 100644 Binary files a/inventory/__pycache__/views.cpython-311.pyc and b/inventory/__pycache__/views.cpython-311.pyc differ diff --git a/inventory/admin.py b/inventory/admin.py index cfbe9a25..031cd50a 100644 --- a/inventory/admin.py +++ b/inventory/admin.py @@ -1,7 +1,9 @@ +from appointment.models import Appointment from django.contrib import admin from . import models from django_ledger import models as ledger_models from django_pdf_actions.actions import export_to_pdf_landscape, export_to_pdf_portrait +from appointment import models as appointment_models admin.site.register(models.Dealer) @@ -32,12 +34,15 @@ admin.site.register(models.AdditionalServices) admin.site.register(models.Payment) admin.site.register(models.VatRate) admin.site.register(ledger_models.CustomerModel) +admin.site.register(ledger_models.VendorModel) +admin.site.register(ledger_models.ItemModel) admin.site.register(models.Opportunity) admin.site.register(models.Notification) admin.site.register(models.Lead) admin.site.register(models.Activity) admin.site.register(models.Schedule) admin.site.register(models.Notes) +# admin.site.register(appointment_models.Client) @admin.register(models.Car) @@ -126,4 +131,5 @@ class CarOptionAdmin(admin.ModelAdmin): @admin.register(ledger_models.ItemTransactionModel) class ItemTransactionModelAdmin(admin.ModelAdmin): - actions = [export_to_pdf_landscape, export_to_pdf_portrait] \ No newline at end of file + actions = [export_to_pdf_landscape, export_to_pdf_portrait] + diff --git a/inventory/migrations/0031_activity_dealer_alter_activity_content_type_and_more.py b/inventory/migrations/0031_activity_dealer_alter_activity_content_type_and_more.py new file mode 100644 index 00000000..9e7cd356 --- /dev/null +++ b/inventory/migrations/0031_activity_dealer_alter_activity_content_type_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.1.5 on 2025-02-17 14:05 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('inventory', '0030_alter_activity_activity_type_delete_carhistory'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='activity', + name='dealer', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='inventory.dealer'), + preserve_default=False, + ), + migrations.AlterField( + model_name='activity', + name='content_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='contenttypes.contenttype'), + ), + migrations.AlterField( + model_name='activity', + name='created_by', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created_by', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/inventory/migrations/0032_alter_carcolors_car.py b/inventory/migrations/0032_alter_carcolors_car.py new file mode 100644 index 00000000..d7d0d5a9 --- /dev/null +++ b/inventory/migrations/0032_alter_carcolors_car.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.6 on 2025-02-17 17:40 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0031_activity_dealer_alter_activity_content_type_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='carcolors', + name='car', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='colors', to='inventory.car'), + ), + ] diff --git a/inventory/models.py b/inventory/models.py index c0665c5f..e7780b34 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -644,7 +644,7 @@ class InteriorColors(models.Model, LocalizedNameMixin): class CarColors(models.Model): - car = models.ForeignKey("Car", on_delete=models.CASCADE, related_name="colors") + car = models.OneToOneField("Car", on_delete=models.CASCADE, related_name="colors") exterior = models.ForeignKey( "ExteriorColors", on_delete=models.CASCADE, related_name="colors" ) @@ -1386,7 +1386,8 @@ class Email(models.Model): class Activity(models.Model): - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="activities") + content_type = models.ForeignKey(ContentType, on_delete=models.DO_NOTHING) object_id = models.PositiveIntegerField() content_object = GenericForeignKey("content_type", "object_id") activity_type = models.CharField( @@ -1394,7 +1395,7 @@ class Activity(models.Model): ) notes = models.TextField(blank=True, null=True, verbose_name=_("Notes")) created_by = models.ForeignKey( - User, on_delete=models.DO_NOTHING, related_name="activities_created" + User, on_delete=models.DO_NOTHING, related_name="activities_created_by" ) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) diff --git a/inventory/tables.py b/inventory/tables.py index 8987794c..e0fcf33a 100644 --- a/inventory/tables.py +++ b/inventory/tables.py @@ -5,7 +5,7 @@ from . import models from django_tables2.utils import A import django_tables2 as tables from django import forms -from inventory.models import Car, SaleQuotation, SaleQuotationCar + class ImageColumn(tables.Column): @@ -19,3 +19,88 @@ class CustomerTable(tables.Table): first_name = tables.Column() +import django_tables2 as tables +from django.utils.translation import gettext_lazy as _ +from .models import Car, CarFinance, ExteriorColors, InteriorColors, CarColors + +class CarTable(tables.Table): + # Car fields + vin = tables.Column(verbose_name=_("VIN")) + dealer = tables.Column(verbose_name=_("Dealer")) + vendor = tables.Column(verbose_name=_("Vendor")) + id_car_make = tables.Column(verbose_name=_("Make")) + id_car_model = tables.Column(verbose_name=_("Model")) + year = tables.Column(verbose_name=_("Year")) + id_car_serie = tables.Column(verbose_name=_("Series")) + id_car_trim = tables.Column(verbose_name=_("Trim")) + status = tables.Column(verbose_name=_("Status")) + stock_type = tables.Column(verbose_name=_("Stock Type")) + remarks = tables.Column(verbose_name=_("Remarks")) + mileage = tables.Column(verbose_name=_("Mileage")) + receiving_date = tables.Column(verbose_name=_("Receiving Date")) + hash = tables.Column(verbose_name=_("Hash")) + + # CarFinance fields + cost_price = tables.Column(accessor="finances.cost_price", verbose_name=_("Cost Price")) + selling_price = tables.Column(accessor="finances.selling_price", verbose_name=_("Selling Price")) + discount_amount = tables.Column(accessor="finances.discount_amount", verbose_name=_("Discount Amount")) + + # ExteriorColors fields (accessed through CarColors) + exterior_color = tables.Column(accessor="colors.exterior.name", verbose_name=_("Exterior Color")) + exterior_color_rgb = tables.Column(accessor="colors.exterior.rgb", verbose_name=_("Exterior Color RGB")) + + # InteriorColors fields (accessed through CarColors) + interior_color = tables.Column(accessor="colors.interior.name", verbose_name=_("Interior Color")) + interior_color_rgb = tables.Column(accessor="colors.interior.rgb", verbose_name=_("Interior Color RGB")) + + class Meta: + model = Car + template_name = "django_tables2/bootstrap.html" + fields = ( + "vin", + "dealer", + "vendor", + "id_car_make", + "id_car_model", + "year", + "id_car_serie", + "id_car_trim", + "status", + "stock_type", + "remarks", + "mileage", + "receiving_date", + "hash", + "cost_price", + "selling_price", + "discount_amount", + "exterior_color", + "exterior_color_rgb", + "interior_color", + "interior_color_rgb", + ) + attrs = {"class": "table table-striped table-bordered"} + + def render_dealer(self, value): + return str(value) + + def render_vendor(self, value): + return str(value) + + def render_id_car_make(self, value): + return str(value) + + def render_id_car_model(self, value): + return str(value) + + def render_id_car_serie(self, value): + return str(value) + + def render_id_car_trim(self, value): + return str(value) + + def render_exterior_color(self, value): + return str(value) + + def render_interior_color(self, value): + return str(value) \ No newline at end of file diff --git a/inventory/urls.py b/inventory/urls.py index bf0e59d7..49abe150 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -1,6 +1,7 @@ from django.urls import path from . import views from allauth.account import views as allauth_views +from django_tables2.export.views import TableExport urlpatterns = [ @@ -45,6 +46,8 @@ urlpatterns = [ "dashboards/accounting/", views.AccountingDashboard.as_view(), name="accounting" ), path("test/", views.TestView.as_view(), name="test"), + path('cars/inventory/table/', views.car_list, name="car_table"), + path('export//', TableExport, name='export'), # Dealer URLs path("dealers//", views.DealerDetailView.as_view(), name="dealer_detail"), path( diff --git a/inventory/views.py b/inventory/views.py index 6defc1ce..f86dba16 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -3,7 +3,7 @@ from appointment.models import Appointment,AppointmentRequest,Service,StaffMembe from datetime import timedelta from calendar import month_name from random import randint - +from django_tables2 import RequestConfig from django_pdf_actions.actions import export_to_pdf_landscape from reportlab.lib.pagesizes import landscape, A4 from rich import print @@ -90,7 +90,7 @@ from .services import ( get_make, get_model, ) -from . import models, forms +from . import models, forms, tables from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.messages.views import SuccessMessageMixin from django.contrib.auth.models import Group @@ -145,6 +145,7 @@ from django_ledger.views.mixins import ( from django_pdf_actions import actions + logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -4386,3 +4387,11 @@ def apply_search_filters(queryset, query): return queryset.filter(search_filters).distinct() +def car_list(request): + dealer = get_user_type(request) + queryset = models.Car.objects.select_related( + "finances", "colors__exterior", "colors__interior" + ).filter(dealer=dealer) + table = tables.CarTable(queryset) + RequestConfig(request).configure(table) + return render(request, 'inventory/car_list_table.html', {'table': table}) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 22a137d2..ad22e107 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,24 +1,24 @@ -aiohappyeyeballs==2.4.4 -aiohttp==3.11.11 +aiohappyeyeballs==2.4.6 +aiohttp==3.11.12 aiohttp-retry==2.9.1 aiosignal==1.3.2 alabaster==1.0.0 albucore==0.0.23 -albumentations==2.0.2 +albumentations==2.0.4 annotated-types==0.7.0 anyio==4.8.0 arabic-reshaper==3.0.0 asgiref==3.8.1 astor==0.8.1 astroid==3.3.8 -attrs==23.2.0 +attrs==25.1.0 autopep8==2.3.2 Babel==2.15.0 -beautifulsoup4==4.12.3 +beautifulsoup4==4.13.3 bleach==6.2.0 blinker==1.9.0 Brotli==1.1.0 -certifi==2024.12.14 +certifi==2025.1.31 cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.4.1 @@ -27,7 +27,7 @@ colorama==0.4.6 commonmark==0.9.1 contourpy==1.3.1 crispy-bootstrap5==2024.10 -cryptography==44.0.0 +cryptography==44.0.1 cssselect2==0.7.0 ctranslate2==4.5.0 cycler==0.12.1 @@ -39,18 +39,18 @@ dill==0.3.9 distro==1.9.0 dj-rest-auth==7.0.1 dj-shop-cart==8.0.0a2 -Django==5.1.5 -django-allauth==65.3.1 +Django==5.1.6 +django-allauth==65.4.1 django-appointment==3.8.0 django-autoslug==1.9.9 django-bootstrap5==24.3 django-classy-tags==4.1.0 -django-cors-headers==4.6.0 +django-cors-headers==4.7.0 django-countries==7.6.1 django-crispy-forms==2.3 django-debug-toolbar==5.0.1 django-extensions==3.2.3 -django-filter==24.3 +django-filter==25.1 django-formtools==2.5.1 django-ledger==0.7.4.1 django-model-utils==5.0.0 @@ -59,7 +59,7 @@ django-next-url-mixin==0.4.0 django-nine==0.2.7 django-nonefield==0.4 django-ordered-model==3.7.4 -django-pdf-actions==0.1.38 +django-pdf-actions==0.1.39 django-phonenumber-field==8.0.0 django-picklefield==3.2 django-plans==1.2.0 @@ -82,19 +82,19 @@ docutils==0.21.2 easy-thumbnails==2.10 emoji==2.14.1 et_xmlfile==2.0.0 -Faker==35.0.0 +Faker==36.1.1 filelock==3.17.0 fire==0.7.0 Flask==3.1.0 -fonttools==4.55.7 +fonttools==4.56.0 fpdf2==2.8.2 frozenlist==1.5.0 -fsspec==2024.12.0 +fsspec==2025.2.0 gprof2dot==2024.6.6 graphqlclient==0.2.4 greenlet==3.1.1 h11==0.14.0 -h2==4.1.0 +h2==4.2.0 hpack==4.1.0 hstspreload==2025.1.1 httpcore==1.0.7 @@ -117,11 +117,11 @@ lazy_loader==0.4 ledger==1.0.1 libretranslatepy==2.1.4 lmdb==1.6.2 -lxml==5.3.0 +lxml==5.3.1 Markdown==3.7 markdown-it-py==3.0.0 MarkupSafe==3.0.2 -marshmallow==3.26.0 +marshmallow==3.26.1 matplotlib==3.10.0 mccabe==0.7.0 mdurl==0.1.2 @@ -130,13 +130,13 @@ mpmath==1.3.0 multidict==6.1.0 mypy-extensions==1.0.0 networkx==3.4.2 -newrelic==10.4.0 +newrelic==10.6.0 nltk==3.9.1 num2words==0.5.14 -numpy==2.2.2 +libquadmath==2.2.3 oauthlib==3.2.2 ofxtools==0.9.5 -openai==1.60.2 +openai==1.63.1 opencv-contrib-python==4.11.0.86 opencv-python==4.11.0.86 opencv-python-headless==4.11.0.86 @@ -147,8 +147,8 @@ packaging==24.2 pandas==2.2.3 pango==0.0.1 pdfkit==1.0.0 -phonenumbers==8.13.42 -pillow==10.4.0 +phonenumbers==8.13.55 +pillow==11.1.0 platformdirs==4.3.6 prometheus_client==0.21.1 propcache==0.2.1 @@ -162,7 +162,7 @@ pyclipper==1.3.0.post6 pycodestyle==2.12.1 pycparser==2.22 pydantic==2.10.6 -pydantic_core==2.27.2 +pydantic_core==2.29.0 pydotplus==2.0.2 pydyf==0.11.0 PyGetWindow==0.0.9 @@ -190,41 +190,41 @@ python-openid==2.2.5 python-stdnum==1.20 python3-saml==1.16.0 pytweening==1.2.0 -pytz==2024.2 +pytz==2025.1 pyvin==0.0.2 pywa==2.7.0 pywhat==5.1.0 pywhatkit==5.4 PyYAML==6.0.2 pyzbar==0.1.9 -qrcode==7.4.2 -RapidFuzz==3.11.0 +qrcode==8.0 +RapidFuzz==3.12.1 regex==2024.11.6 -reportlab==4.2.5 +reportlab==4.3.1 requests==2.32.3 requests-oauthlib==2.0.0 rfc3986==2.0.0 -rich==10.16.2 +rich==13.9.4 rubicon-objc==0.5.0 sacremoses==0.1.1 scikit-image==0.25.1 -scikit-learn==1.6.1 -scipy==1.15.1 +libomp runtime library==1.6.1 +libquadmath==1.15.2 selenium==4.28.1 sentencepiece==0.2.0 -shapely==2.0.6 +shapely==2.0.7 simsimd==6.2.1 six==1.17.0 sniffio==1.3.1 snowballstemmer==2.2.0 sortedcontainers==2.4.0 soupsieve==2.6 -SQLAlchemy==2.0.37 +SQLAlchemy==2.0.38 sqlparse==0.5.3 stanza==1.10.1 stringzilla==3.11.3 suds==1.2.0 -swapper==1.3.0 +swapper==1.4.0 sympy==1.13.1 tablib==3.8.0 termcolor==2.5.0 @@ -234,11 +234,11 @@ tinycss2==1.4.0 tinyhtml5==2.0.0 tomli==2.2.1 tomlkit==0.13.2 -torch==2.5.1 +torch==2.6.0 tqdm==4.67.1 -trio==0.28.0 -trio-websocket==0.11.1 -twilio==9.4.4 +trio==0.29.0 +trio-websocket==0.12.0 +twilio==9.4.5 typing-inspect==0.9.0 typing_extensions==4.12.2 tzdata==2025.1 @@ -249,7 +249,7 @@ vin==0.6.2 vininfo==1.8.0 vishap==0.1.5 vpic-api==0.7.4 -weasyprint==63.1 +weasyprint==64.0 webencodings==0.5.1 websocket-client==1.8.0 Werkzeug==3.1.3 diff --git a/templates/inventory/car_list_table.html b/templates/inventory/car_list_table.html new file mode 100644 index 00000000..6fd3d636 --- /dev/null +++ b/templates/inventory/car_list_table.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} +{% load static i18n django_tables2%} + + +{% block content %} + +{% load export_url from django_tables2 %} + + +{% render_table table %} + + +Export to Excel +Export to PDF +{% endblock %} \ No newline at end of file diff --git a/templates/organizations/organization_list.html b/templates/organizations/organization_list.html index 8026722f..6fc20b4b 100644 --- a/templates/organizations/organization_list.html +++ b/templates/organizations/organization_list.html @@ -115,8 +115,8 @@ - {{ org.additional_info.organization_info.crn }} - {{ org.additional_info.organization_info.vrn }} + {{ org.additional_info.info.crn }} + {{ org.additional_info.info.vrn }} {{ org.phone }} {{ org.address_1 }}