diff --git a/.gitignore b/.gitignore index 8ac15553..4e09b8e3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,11 +15,12 @@ car*.json car_inventory/settings.py car_inventory/__pycache__ scripts/dsrpipe.py -# Backup files # -*.bak +def_venv +# Backup files # +*.bak -# If you are using PyCharm # +# If you are using PyCharm # # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml @@ -55,95 +56,95 @@ out/ # JIRA plugin atlassian-ide-plugin.xml -# Python # -*.py[cod] -*$py.class +# Python # +*.py[cod] +*$py.class -# Distribution / packaging -.Python build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ +# Distribution / packaging +.Python build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ *.whl -*.egg-info/ -.installed.cfg -*.egg -*.manifest -*.spec +*.egg-info/ +.installed.cfg +*.egg +*.manifest +*.spec inventory/management/commands/run.py -# Installer logs -pip-log.txt -pip-delete-this-directory.txt +# Installer logs +pip-log.txt +pip-delete-this-directory.txt -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -.pytest_cache/ -nosetests.xml -coverage.xml -*.cover -.hypothesis/ +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ -# Jupyter Notebook -.ipynb_checkpoints +# Jupyter Notebook +.ipynb_checkpoints -# pyenv -.python-version +# pyenv +.python-version -# celery -celerybeat-schedule.* +# celery +celerybeat-schedule.* -# SageMath parsed files -*.sage.py +# SageMath parsed files +*.sage.py -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ -# mkdocs documentation -/site +# mkdocs documentation +/site -# mypy -.mypy_cache/ +# mypy +.mypy_cache/ -# Sublime Text # -*.tmlanguage.cache -*.tmPreferences.cache -*.stTheme.cache -*.sublime-workspace -*.sublime-project +# Sublime Text # +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache +*.sublime-workspace +*.sublime-project -# sftp configuration file -sftp-config.json +# sftp configuration file +sftp-config.json -# Package control specific files Package -Control.last-run -Control.ca-list -Control.ca-bundle -Control.system-ca-bundle -GitHub.sublime-settings +# Package control specific files Package +Control.last-run +Control.ca-list +Control.ca-bundle +Control.system-ca-bundle +GitHub.sublime-settings -# Visual Studio Code # -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json +# Visual Studio Code # +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json .history \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index eacc006c..8dcfb0ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,18 +4,23 @@ FROM python:3.11.11-slim-bullseye # Set the working directory to /app WORKDIR /app +# Install the dependencies +RUN apt-get update && apt-get install -y libgl1 +RUN apt-get update && apt-get install -y libglib2.0-dev +RUN apt-get update && apt-get install -y libzbar0 +RUN apt-get update && apt-get install -y cmake build-essential xmlsec1 libxmlsec1-dev +RUN apt-get install pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl + +COPY requirements_dev.txt . + +RUN pip install --upgrade pip + +RUN pip install -r requirements_dev.txt # Create a new user and group RUN groupadd -r appgroup RUN useradd -r -g appgroup -G appgroup -m -d /app -s /bin/false appuser # Copy the requirements file -COPY requirements.txt . - -# Install the dependencies -RUN pip install -r requirements.txt -RUN apt-get update && apt-get install -y libgl1 -RUN apt-get update && apt-get install -y libglib2.0-dev -RUN apt-get update && apt-get install -y libzbar0 # Copy the application code COPY . . @@ -23,17 +28,12 @@ COPY . . # Expose the port EXPOSE 8000 -# Copy the entrypoint script -COPY entrypoint.sh /app/entrypoint.sh # Make the script executable -RUN chmod +x /app/entrypoint.sh +RUN chmod +x *.sh # Change ownership of the app directory to the new user RUN chown -R appuser:appgroup /app -RUN find /app -path "*/migrations/*.py" -not -name "__init__.py" -delete -RUN find /app -path "*/migrations/*.pyc" -delete - # Set the entrypoint to execute the script as the new user -ENTRYPOINT ["sh", "-c", "python3 manage.py makemigrations && python3 manage.py migrate && python3 manage.py collectstatic --no-input && python3 manage.py runserver 0.0.0.0:8000"] \ No newline at end of file +ENTRYPOINT ["sh", "-c", "python3 manage.py runserver 0.0.0.0:8000"] \ No newline at end of file diff --git a/api/views.py b/api/views.py index 56de2c53..84153c37 100644 --- a/api/views.py +++ b/api/views.py @@ -11,7 +11,7 @@ from django.shortcuts import render from inventory.utils import get_user_type from . import models, serializers from .services import get_car_data, get_from_cardatabase -from rest_framework.authtoken.models import Token +# from rest_framework.authtoken.models import Token from django.utils.decorators import method_decorator from inventory import models as inventory_models @@ -20,20 +20,20 @@ from inventory import models as inventory_models class LoginView(APIView): permission_classes = [permissions.AllowAny,] - def post(self, request, *args, **kwargs): - username = request.data.get('username') - password = request.data.get('password') + # def post(self, request, *args, **kwargs): + # username = request.data.get('username') + # password = request.data.get('password') - if username is None or password is None: - return Response({'error': 'Please provide both username and password.'}, status=status.HTTP_400_BAD_REQUEST) + # if username is None or password is None: + # return Response({'error': 'Please provide both username and password.'}, status=status.HTTP_400_BAD_REQUEST) - user = authenticate(username=username, password=password) + # user = authenticate(username=username, password=password) - if not user: - return Response({'error': 'Invalid credentials.'}, status=status.HTTP_401_UNAUTHORIZED) + # if not user: + # return Response({'error': 'Invalid credentials.'}, status=status.HTTP_401_UNAUTHORIZED) - token, created = Token.objects.get_or_create(user=user) - return Response({'token': token.key, 'user_id': user.id, 'username': user.username}, status=status.HTTP_200_OK) + # token, created = Token.objects.get_or_create(user=user) + # return Response({'token': token.key, 'user_id': user.id, 'username': user.username}, status=status.HTTP_200_OK) class CarVINViewSet(APIView): diff --git a/apply_initial_migrations.sh b/apply_initial_migrations.sh new file mode 100755 index 00000000..386ff6af --- /dev/null +++ b/apply_initial_migrations.sh @@ -0,0 +1,21 @@ +#!/bin/sh +echo "Delete Old Migrations" +find ./inventory -type f -iname "000*.py" -delete + +echo "Delete Old Cache" +find ./car_inventory -type d -iname "__pycache__"|xargs rm -rf +find ./inventory -type d -iname "__pycache__"|xargs rm -rf + +echo "Apply Base Migrate" +python3 manage.py migrate + +echo "Apply Make Migratinos" +python3 manage.py makemigrations + +echo "Apply Final Migrate" +python3 manage.py migrate + +echo "Collect Static" +python3 manage.py collectstatic --no-input + +echo "Done" diff --git a/car_inventory/urls.py b/car_inventory/urls.py index ecb50987..d74d43e0 100644 --- a/car_inventory/urls.py +++ b/car_inventory/urls.py @@ -4,16 +4,16 @@ from django.conf.urls.static import static from django.conf import settings from django.conf.urls.i18n import i18n_patterns from inventory import views -import debug_toolbar -from schema_graph.views import Schema +# import debug_toolbar +# from schema_graph.views import Schema # from two_factor.urls import urlpatterns as tf_urls urlpatterns = [ - path('__debug__/', include(debug_toolbar.urls)), - path('silk/', include('silk.urls', namespace='silk')), + # path('__debug__/', include(debug_toolbar.urls)), + # path('silk/', include('silk.urls', namespace='silk')), path('api-auth/', include('rest_framework.urls')), - path('api/', include('api.urls')), - path('dj-rest-auth/', include('dj_rest_auth.urls')), + # path('api/', include('api.urls')), + # path('dj-rest-auth/', include('dj_rest_auth.urls')), ] @@ -21,13 +21,13 @@ urlpatterns += i18n_patterns( path('admin/', admin.site.urls), path('switch_language/', views.switch_language, name='switch_language'), path('accounts/', include('allauth.urls')), - path('prometheus/', include('django_prometheus.urls')), + # path('prometheus/', include('django_prometheus.urls')), path('', include('inventory.urls')), path('ledger/', include('django_ledger.urls', namespace='django_ledger')), - path("haikalbot/", include("haikalbot.urls")), + # path("haikalbot/", include("haikalbot.urls")), path('appointment/', include('appointment.urls')), path('plans/', include('plans.urls')), - path("schema/", Schema.as_view()), + # path("schema/", Schema.as_view()), # path('', include(tf_urls)), ) diff --git a/generate.py b/generate.py index 1fc182bc..f9cc4445 100644 --- a/generate.py +++ b/generate.py @@ -5,7 +5,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "car_inventory.settings") django.setup() from inventory.models import * -from rich import print +# from rich import print import random import datetime diff --git a/haikalbot/migrations/0001_initial.py b/haikalbot/migrations/0001_initial.py deleted file mode 100644 index 9c202e5c..00000000 --- a/haikalbot/migrations/0001_initial.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.20 on 2025-03-20 17:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='ChatLog', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('user_message', models.TextField()), - ('chatbot_response', models.TextField()), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ], - ), - ] diff --git a/haikalbot/migrations/0002_initial.py b/haikalbot/migrations/0002_initial.py deleted file mode 100644 index 62877afe..00000000 --- a/haikalbot/migrations/0002_initial.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.20 on 2025-03-20 17:15 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('inventory', '0001_initial'), - ('haikalbot', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='chatlog', - name='dealer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chatlogs', to='inventory.dealer'), - ), - ] diff --git a/inventory/admin.py b/inventory/admin.py index bd45258a..2c15bd11 100644 --- a/inventory/admin.py +++ b/inventory/admin.py @@ -1,9 +1,9 @@ -from appointment.models import Appointment +# 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 +# from django_pdf_actions.actions import export_to_pdf_landscape, export_to_pdf_portrait +# from appointment import models as appointment_models from import_export.admin import ExportMixin from import_export.resources import ModelResource @@ -72,14 +72,14 @@ admin.site.register(models.UserActivityLog) @admin.register(models.Car) class CarAdmin(admin.ModelAdmin): search_fields = ('vin',) - actions = [export_to_pdf_landscape, export_to_pdf_portrait] + # actions = [export_to_pdf_landscape, export_to_pdf_portrait] @admin.register(models.CarMake) class CarMakeAdmin(admin.ModelAdmin): list_display = ('name', 'arabic_name', 'is_sa_import') search_fields = ('name', 'arabic_name') list_filter = ('is_sa_import', 'name',) - actions = [export_to_pdf_landscape, export_to_pdf_portrait] + # actions = [export_to_pdf_landscape, export_to_pdf_portrait] class Meta: verbose_name = "Car Make" @@ -152,9 +152,9 @@ class CarOptionAdmin(admin.ModelAdmin): # search_fields = ('user__username', 'action') # list_filter = ('timestamp',) -@admin.register(ledger_models.ItemTransactionModel) -class ItemTransactionModelAdmin(admin.ModelAdmin): +# @admin.register(ledger_models.ItemTransactionModel) +# class ItemTransactionModelAdmin(admin.ModelAdmin): - actions = [export_to_pdf_landscape, export_to_pdf_portrait] +# actions = [export_to_pdf_landscape, export_to_pdf_portrait] diff --git a/inventory/apps.py b/inventory/apps.py index f316aa12..7a84f50f 100644 --- a/inventory/apps.py +++ b/inventory/apps.py @@ -6,7 +6,7 @@ class InventoryConfig(AppConfig): def ready(self): import inventory.signals - # from decimal import Decimal - # from inventory.models import VatRate - # VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True) + #from decimal import Decimal + #from inventory.models import VatRate + #VatRate.objects.get_or_create(rate=Decimal('0.15'), is_active=True) diff --git a/inventory/forms.py b/inventory/forms.py index 571d76aa..1cc9586e 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -1,19 +1,12 @@ from django.core.cache import cache from django.contrib.auth.models import Permission -from django.contrib.auth.models import Group -from appointment.models import Appointment, Service, StaffMember -from django.urls import reverse -from django_countries.widgets import CountrySelectWidget -from django_ledger.models import CustomerModel +from appointment.models import Service from phonenumber_field.formfields import PhoneNumberField from django.core.validators import MinLengthValidator -from django.core.validators import RegexValidator from django import forms from django.contrib.auth import get_user_model -from phonenumber_field.phonenumber import PhoneNumber from .models import CustomGroup, Status, Stage from .mixins import AddClassMixin -from django.forms.models import inlineformset_factory from django_ledger.forms.invoice import ( InvoiceModelCreateForm as InvoiceModelCreateFormBase, ) @@ -21,17 +14,14 @@ from django_ledger.forms.estimate import ( EstimateModelCreateForm as EstimateModelCreateFormBase, ) -# from django_ledger.forms.ledger import LedgerModelCreateForm as LedgerModelCreateFormBase from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase from django_ledger.forms.journal_entry import JournalEntryModelCreateForm as JournalEntryModelCreateFormBase from .models import ( Dealer, DealersMake, - # Branch, Vendor, Schedule, - Customer, Car, CarTransfer, CarFinance, @@ -40,16 +30,11 @@ from .models import ( CarColors, ExteriorColors, InteriorColors, - # SaleQuotation, CarLocation, - Representative, - - # SaleQuotationCar, AdditionalServices, Staff, Opportunity, - Lead, Activity, Notes, @@ -65,7 +50,6 @@ from django.forms import ( ) from django.utils.translation import gettext_lazy as _ import django_tables2 as tables -from django.forms import formset_factory User = get_user_model() @@ -90,14 +74,6 @@ class AdditionalServiceForm(forms.ModelForm): model = AdditionalServices fields = ["name", "price", "description", "taxable", "uom"] - -# class PaymentForm(forms.ModelForm): -# invoice = forms.ModelChoiceField(queryset=InvoiceModel.objects.all(),label="Invoice", required=True) -# class Meta: -# model = Payment -# fields = ['amount','payment_method', 'reference_number'] - - class StaffForm(forms.ModelForm): """ Represents a form for managing Staff entities, including associated user email updates @@ -128,22 +104,6 @@ class StaffForm(forms.ModelForm): model = Staff fields = ["name", "arabic_name", "phone_number", "staff_type"] - # def __init__(self, *args, **kwargs): - # user_instance = kwargs.get("instance") - # if user_instance and user_instance.user: - # initial = kwargs.setdefault("initial", {}) - # initial["email"] = user_instance.user.email - # super().__init__(*args, **kwargs) - # - # def save(self, commit=True): - # user_instance = super().save(commit=False) - # user = user_instance.user - # user.email = self.cleaned_data["email"] - # if commit: - # user.save() - # user_instance.save() - # return user_instance - # Dealer Form class DealerForm(forms.ModelForm): @@ -258,28 +218,6 @@ class OrganizationForm(CustomerForm): logo = forms.ImageField(required=False) -# class CustomerForm(forms.ModelForm, AddClassMixin): -# class Meta: -# model = Customer -# fields = [ -# "title", -# "first_name", -# "middle_name", -# "last_name", -# "gender", -# "dob", -# "email", -# "national_id", - -# "phone_number", -# "address", -# ] -# widgets = { -# "phone_number": forms.TextInput(attrs={"class": "phone"}), -# "dob": forms.DateInput(attrs={"type": "date"}), -# } - - class CarForm( forms.ModelForm, AddClassMixin, @@ -338,10 +276,6 @@ class CarForm( self.fields["vendor"].queryset = ledger_models.VendorModel.objects.filter( active=True ) - # queryset = self.fields["vendor"].queryset - # self.fields["vendor"].choices = [ - # (obj.pk, obj.get_local_name()) for obj in queryset - # ] class CarUpdateForm(forms.ModelForm, AddClassMixin): @@ -382,19 +316,6 @@ class CarUpdateForm(forms.ModelForm, AddClassMixin): dealer = kwargs.pop("dealer", None) super().__init__(*args, **kwargs) - # if dealer and 'branch' in self.fields: - # self.fields['branch'].queryset = Branch.objects.filter(dealer=dealer) - # self.fields['branch'].choices = [ - # (branch.id, branch.get_local_name()) for branch in self.fields['branch'].queryset - # ] - - # if "vendor" in self.fields: - # queryset = self.fields["vendor"].queryset - # if queryset: - # self.fields["vendor"].choices = [ - # (vendor.id, vendor.get_local_name()) for vendor in queryset - # ] - class CarFinanceForm(forms.ModelForm): """ @@ -424,13 +345,6 @@ class CarFinanceForm(forms.ModelForm): "additional_services", ] - # def __init__(self, *args, **kwargs): - # super().__init__(*args, **kwargs) - # if self.instance.pk: - # self.fields[ - # "additional_finances" - # ].initial = self.instance.additional_services.all() - def save(self, commit=True): instance = super().save() instance.additional_services.set(self.cleaned_data["additional_finances"]) @@ -617,43 +531,6 @@ class CarColorsForm(forms.ModelForm): return cleaned_data -# class QuotationForm(forms.ModelForm): -# cars = ModelMultipleChoiceField( -# queryset=Car.objects.none(), # Default empty queryset -# widget=forms.CheckboxSelectMultiple, -# label="Select Cars", -# ) -# -# class Meta: -# model = SaleQuotation -# fields = ["customer", "cars", "remarks"] -# -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# -# self.fields["cars"].queryset = Car.objects.filter( -# finances__isnull=False -# ).distinct() - - -# class OrganizationForm(forms.ModelForm): -# class Meta: -# model = Organization -# fields = [ -# "name", -# "arabic_name", -# "crn", -# "vrn", -# "phone_number", -# "address", -# "logo", -# ] - -# def __init__(self, *args, **kwargs): -# dealer = kwargs.pop("dealer", None) -# super().__init__(*args, **kwargs) - - class RepresentativeForm(forms.ModelForm): """ A form for creating or updating instances of the Representative model. @@ -958,17 +835,6 @@ class WizardForm3(forms.Form): }, ) - # def clean(self): - # cleaned_data = super().clean() - # password = cleaned_data.get("password") - # confirm_password = cleaned_data.get("confirm_password") - - # if password != confirm_password: - # raise forms.ValidationError("Passwords do not match.") - # else: - # return cleaned_data - - class ItemForm(forms.Form): """ A form for handling item-related inputs in the application. @@ -991,9 +857,6 @@ class ItemForm(forms.Form): validators=[MinLengthValidator(5)], ) quantity = forms.DecimalField(label="Quantity", required=True) - # unit = forms.DecimalField(label="Unit", required=True) - # unit_cost = forms.DecimalField(label="Unit Cost", required=True) - # unit_sales_price = forms.DecimalField(label="Unit Sales Price", required=True) class PaymentForm(forms.Form): @@ -1278,10 +1141,6 @@ class BillModelCreateForm(BillModelCreateFormBase): 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"}) - # ) - class SaleOrderForm(forms.ModelForm): """ @@ -1568,6 +1427,3 @@ class JournalEntryModelCreateForm(JournalEntryModelCreateFormBase): :type bar: int """ pass - -# class LedgerModelCreateForm(LedgerModelCreateFormBase): -# pass \ No newline at end of file diff --git a/inventory/management/__pycache__/__init__.cpython-311.pyc b/inventory/management/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 98179969..00000000 Binary files a/inventory/management/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/inventory/management/commands/__pycache__/.DS_Store b/inventory/management/commands/__pycache__/.DS_Store deleted file mode 100644 index 004cc1ac..00000000 Binary files a/inventory/management/commands/__pycache__/.DS_Store and /dev/null differ diff --git a/inventory/management/commands/__pycache__/__init__.cpython-311.pyc b/inventory/management/commands/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 0e0a9e4b..00000000 Binary files a/inventory/management/commands/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/inventory/management/commands/__pycache__/create_fake_data.cpython-311.pyc b/inventory/management/commands/__pycache__/create_fake_data.cpython-311.pyc deleted file mode 100644 index b04e8944..00000000 Binary files a/inventory/management/commands/__pycache__/create_fake_data.cpython-311.pyc and /dev/null differ diff --git a/inventory/management/commands/__pycache__/export_models.cpython-311.pyc b/inventory/management/commands/__pycache__/export_models.cpython-311.pyc deleted file mode 100644 index 4584ab1a..00000000 Binary files a/inventory/management/commands/__pycache__/export_models.cpython-311.pyc and /dev/null differ diff --git a/inventory/management/commands/__pycache__/transfer_data.cpython-311.pyc b/inventory/management/commands/__pycache__/transfer_data.cpython-311.pyc deleted file mode 100644 index 3e51c4b1..00000000 Binary files a/inventory/management/commands/__pycache__/transfer_data.cpython-311.pyc and /dev/null differ diff --git a/inventory/management/commands/__pycache__/translate.cpython-311.pyc b/inventory/management/commands/__pycache__/translate.cpython-311.pyc deleted file mode 100644 index 836ceffb..00000000 Binary files a/inventory/management/commands/__pycache__/translate.cpython-311.pyc and /dev/null differ diff --git a/inventory/migrations/0001_initial.py b/inventory/migrations/0001_initial.py index 1c658e0f..17b2bbcb 100644 --- a/inventory/migrations/0001_initial.py +++ b/inventory/migrations/0001_initial.py @@ -1,13 +1,13 @@ -# Generated by Django 4.2.20 on 2025-03-20 17:15 +# Generated by Django 5.1.7 on 2025-03-27 20:55 import datetime -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): @@ -15,55 +15,14 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.DJANGO_LEDGER_INVOICE_MODEL), - ('contenttypes', '0002_remove_content_type_name'), - migrations.swappable_dependency(settings.DJANGO_LEDGER_CUSTOMER_MODEL), - migrations.swappable_dependency(settings.DJANGO_LEDGER_ESTIMATE_MODEL), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - migrations.swappable_dependency(settings.DJANGO_LEDGER_ACCOUNT_MODEL), - migrations.swappable_dependency(settings.DJANGO_LEDGER_ENTITY_MODEL), - migrations.swappable_dependency(settings.DJANGO_LEDGER_VENDOR_MODEL), - migrations.swappable_dependency(settings.DJANGO_LEDGER_ITEM_MODEL), + ('appointment', '0001_initial'), ('auth', '0012_alter_user_first_name_max_length'), - ('appointment', '__first__'), + ('contenttypes', '0002_remove_content_type_name'), + ('django_ledger', '0021_alter_bankaccountmodel_account_model_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] 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=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('vin', models.CharField(max_length=17, unique=True, verbose_name='VIN')), - ('year', models.IntegerField(verbose_name='Year')), - ('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved'), ('transfer', 'Transfer')], default='available', max_length=10, verbose_name='Status')), - ('stock_type', models.CharField(choices=[('new', 'New'), ('used', 'Used')], default='new', max_length=10, verbose_name='Stock Type')), - ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), - ('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')), - ('receiving_date', models.DateTimeField(verbose_name='Receiving Date')), - ('hash', models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash')), - ], - options={ - 'verbose_name': 'Car', - 'verbose_name_plural': 'Cars', - }, - ), migrations.CreateModel( name='CarEquipment', fields=[ @@ -92,86 +51,6 @@ class Migration(migrations.Migration): }, 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='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=settings.DJANGO_LEDGER_ENTITY_MODEL)), - ('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=[ @@ -200,6 +79,385 @@ class Migration(migrations.Migration): }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), + migrations.CreateModel( + name='Payment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='amount')), + ('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('SADAD', 'SADAD')], max_length=50, verbose_name='method')), + ('reference_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='reference number')), + ('payment_date', models.DateField(auto_now_add=True, verbose_name='date')), + ], + options={ + 'verbose_name': 'payment', + 'verbose_name_plural': 'payments', + }, + ), + migrations.CreateModel( + name='VatRate', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rate', models.DecimalField(decimal_places=2, default=Decimal('0.15'), max_digits=5)), + ('is_active', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name='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='Car', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vin', models.CharField(max_length=17, unique=True, verbose_name='VIN')), + ('year', models.IntegerField(verbose_name='Year')), + ('status', models.CharField(choices=[('available', 'Available'), ('sold', 'Sold'), ('hold', 'Hold'), ('damaged', 'Damaged'), ('reserved', 'Reserved'), ('transfer', 'Transfer')], default='available', max_length=10, verbose_name='Status')), + ('stock_type', models.CharField(choices=[('new', 'New'), ('used', 'Used')], default='new', max_length=10, verbose_name='Stock Type')), + ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), + ('mileage', models.IntegerField(blank=True, null=True, verbose_name='Mileage')), + ('receiving_date', models.DateTimeField(verbose_name='Receiving Date')), + ('hash', models.CharField(blank=True, max_length=64, null=True, verbose_name='Hash')), + ('vendor', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='django_ledger.vendormodel', verbose_name='Vendor')), + ('id_car_make', 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')), + ], + options={ + 'verbose_name': 'Car', + 'verbose_name_plural': 'Cars', + }, + ), + 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.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.AddField( + 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.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', + 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(blank=True, max_length=1, null=True, verbose_name='Text 2')), + ('text3', models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 3')), + ('registration_date', models.DateTimeField(verbose_name='Registration Date')), + ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')), + ], + options={ + 'verbose_name': 'Registration', + 'verbose_name_plural': 'Registrations', + }, + ), + 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=[ + ('id_car_trim', 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)), + ('start_production_year', models.IntegerField(blank=True, null=True)), + ('end_production_year', models.IntegerField(blank=True, null=True)), + ('id_car_serie', models.ForeignKey(db_column='id_car_serie', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie')), + ], + options={ + 'verbose_name': 'Trim', + }, + bases=(models.Model, inventory.mixins.LocalizedNameMixin), + ), + migrations.CreateModel( + name='CarSpecificationValue', + fields=[ + ('id_car_specification_value', models.AutoField(primary_key=True, serialize=False)), + ('value', models.CharField(max_length=500)), + ('unit', models.CharField(blank=True, max_length=255, null=True)), + ('id_car_specification', models.ForeignKey(db_column='id_car_specification', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')), + ('id_car_trim', models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim')), + ], + options={ + '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', + 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')), + ], + options={ + 'verbose_name': 'Custom Card', + 'verbose_name_plural': 'Custom Cards', + }, + ), + 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='CustomGroup', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='inventory.dealer')), + ], + ), + 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')), + ('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')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='customer_profile', to=settings.AUTH_USER_MODEL)), + ('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='CarTransfer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('transfer_date', models.DateTimeField(auto_now_add=True, verbose_name='Transfer Date')), + ('quantity', models.IntegerField(default=1, verbose_name='Quantity')), + ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), + ('status', models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accepted', 'Accepted'), ('success', 'Success'), ('reject', 'Reject'), ('cancelled', 'Cancelled')])), + ('is_approved', models.BooleanField(default=False)), + ('active', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_logs', to='inventory.car', verbose_name='Car')), + ('from_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_out', to='inventory.dealer', verbose_name='From Dealer')), + ('to_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_in', to='inventory.dealer', verbose_name='To Dealer')), + ], + options={ + 'verbose_name': 'Car Transfer Log', + 'verbose_name_plural': 'Car Transfer Logs', + }, + ), + migrations.CreateModel( + name='CarLocation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.TextField(blank=True, help_text='Optional description about the showroom placement.', null=True, verbose_name='Description')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Updated')), + ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='location', to='inventory.car', verbose_name='Car')), + ('owner', models.ForeignKey(help_text='Dealer who owns the car.', on_delete=django.db.models.deletion.CASCADE, related_name='owned_cars', to='inventory.dealer', verbose_name='Owner')), + ('showroom', models.ForeignKey(help_text='Dealer where the car is displayed (can be the owner).', on_delete=django.db.models.deletion.CASCADE, related_name='showroom_cars', to='inventory.dealer', verbose_name='Showroom')), + ], + options={ + 'verbose_name': 'Car Location', + 'verbose_name_plural': 'Car Locations', + }, + ), + migrations.AddField( + model_name='car', + name='dealer', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to='inventory.dealer', verbose_name='Dealer'), + ), + migrations.AddField( + model_name='additionalservices', + name='dealer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inventory.dealer', verbose_name='Dealer'), + ), + migrations.CreateModel( + name='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'), ('sale_car', 'Sale Car'), ('reserve_car', 'Reserve Car'), ('transfer_car', 'Transfer 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.DO_NOTHING, to='contenttypes.contenttype')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created_by', to=settings.AUTH_USER_MODEL)), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='inventory.dealer')), + ], + options={ + 'verbose_name': 'Activity', + 'verbose_name_plural': 'Activities', + }, + ), + migrations.CreateModel( + name='DealerSettings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('additional_info', models.JSONField(blank=True, default=dict, null=True)), + ('bill_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_cash', to='django_ledger.accountmodel')), + ('bill_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid', to='django_ledger.accountmodel')), + ('bill_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_unearned', to='django_ledger.accountmodel')), + ('dealer', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='inventory.dealer')), + ('invoice_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_cash', to='django_ledger.accountmodel')), + ('invoice_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to='django_ledger.accountmodel')), + ('invoice_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_unearned', to='django_ledger.accountmodel')), + ], + ), + migrations.CreateModel( + name='Email', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('from_email', models.TextField(blank=True, null=True, verbose_name='From Email')), + ('to_email', models.TextField(blank=True, null=True, verbose_name='To Email')), + ('subject', models.TextField(blank=True, null=True, verbose_name='Subject')), + ('message', models.TextField(blank=True, null=True, verbose_name='Message')), + ('status', models.CharField(choices=[('SENT', 'Sent'), ('FAILED', 'Failed'), ('DELIVERED', 'Delivered'), ('OPEN', 'Open'), ('DRAFT', 'Draft')], default='OPEN', max_length=20, verbose_name='Status')), + ('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='emails_created', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Email', + 'verbose_name_plural': 'Emails', + }, + ), migrations.CreateModel( name='Lead', fields=[ @@ -219,7 +477,7 @@ class Migration(migrations.Migration): ('status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], db_index=True, default='new', max_length=50, verbose_name='Status')), ('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Created')), ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), - ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)), + ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='django_ledger.customermodel')), ('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')), @@ -229,6 +487,37 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Leads', }, ), + migrations.CreateModel( + name='Notes', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('note', models.TextField(verbose_name='Note')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='notes_created', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Note', + 'verbose_name_plural': 'Notes', + }, + ), + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('message', models.CharField(max_length=255, verbose_name='Message')), + ('is_read', models.BooleanField(default=False, verbose_name='Is Read')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Notification', + 'verbose_name_plural': 'Notifications', + 'ordering': ['-created'], + }, + ), migrations.CreateModel( name='Organization', fields=[ @@ -251,62 +540,71 @@ class Migration(migrations.Migration): bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), migrations.CreateModel( - name='Payment', + 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')), - ('payment_method', models.CharField(choices=[('cash', 'cash'), ('credit', 'credit'), ('transfer', 'transfer'), ('debit', 'debit'), ('SADAD', 'SADAD')], max_length=50, verbose_name='method')), - ('reference_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='reference number')), - ('payment_date', models.DateField(auto_now_add=True, verbose_name='date')), + ('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': 'payment', - 'verbose_name_plural': 'payments', + 'verbose_name': 'refund', + 'verbose_name_plural': 'refunds', }, ), migrations.CreateModel( - name='VatRate', + name='Representative', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('rate', models.DecimalField(decimal_places=2, default=Decimal('0.15'), max_digits=5)), - ('is_active', models.BooleanField(default=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ], - ), - migrations.CreateModel( - name='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')), + ('name', models.CharField(max_length=255, verbose_name='Name')), ('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')), + ('id_number', models.CharField(max_length=10, unique=True, verbose_name='ID Number')), ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), ('email', models.EmailField(max_length=255, verbose_name='Email Address')), ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), - ('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')), + ('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': 'Vendor', - 'verbose_name_plural': 'Vendors', + 'verbose_name': 'Representative', + 'verbose_name_plural': 'Representatives', }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), migrations.CreateModel( - name='UserActivityLog', + name='SaleOrder', 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)), + ('payment_method', models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease'), ('credit_card', 'Credit Card'), ('bank_transfer', 'Bank Transfer'), ('SADAD', 'SADAD')], max_length=20)), + ('comments', models.TextField(blank=True, null=True)), + ('formatted_order_id', models.CharField(editable=False, max_length=10, unique=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('estimate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.estimatemodel', verbose_name='Estimate')), + ('invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.invoicemodel', verbose_name='Invoice')), ], options={ - 'verbose_name': 'User Activity Log', - 'verbose_name_plural': 'User Activity Logs', - 'ordering': ['-timestamp'], + 'ordering': ['-created'], + }, + ), + migrations.CreateModel( + name='Schedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('purpose', models.CharField(choices=[('Product Demo', 'Product Demo'), ('Follow-Up Call', 'Follow-Up Call'), ('Contract Discussion', 'Contract Discussion'), ('Sales Meeting', 'Sales Meeting'), ('Support Call', 'Support Call'), ('Other', 'Other')], max_length=200)), + ('scheduled_at', models.DateTimeField()), + ('scheduled_type', models.CharField(choices=[('Call', 'Call'), ('Meeting', 'Meeting'), ('Email', 'Email')], default='Call', max_length=200)), + ('duration', models.DurationField(default=datetime.timedelta(seconds=300))), + ('notes', models.TextField(blank=True, null=True)), + ('status', models.CharField(choices=[('Scheduled', 'Scheduled'), ('Completed', 'Completed'), ('Canceled', 'Canceled')], default='Scheduled', max_length=200)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='django_ledger.customermodel')), + ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='inventory.lead')), + ('scheduled_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-scheduled_at'], }, ), migrations.CreateModel( @@ -332,74 +630,6 @@ class Migration(migrations.Migration): ('objects', inventory.models.StaffUserManager()), ], ), - migrations.CreateModel( - name='Schedule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('purpose', models.CharField(choices=[('Product Demo', 'Product Demo'), ('Follow-Up Call', 'Follow-Up Call'), ('Contract Discussion', 'Contract Discussion'), ('Sales Meeting', 'Sales Meeting'), ('Support Call', 'Support Call'), ('Other', 'Other')], max_length=200)), - ('scheduled_at', models.DateTimeField()), - ('scheduled_type', models.CharField(choices=[('Call', 'Call'), ('Meeting', 'Meeting'), ('Email', 'Email')], default='Call', max_length=200)), - ('duration', models.DurationField(default=datetime.timedelta(seconds=300))), - ('notes', models.TextField(blank=True, null=True)), - ('status', models.CharField(choices=[('Scheduled', 'Scheduled'), ('Completed', 'Completed'), ('Canceled', 'Canceled')], default='Scheduled', max_length=200)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to=settings.DJANGO_LEDGER_CUSTOMER_MODEL)), - ('lead', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to='inventory.lead')), - ('scheduled_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'ordering': ['-scheduled_at'], - }, - ), - migrations.CreateModel( - name='SaleOrder', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('payment_method', models.CharField(choices=[('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease'), ('credit_card', 'Credit Card'), ('bank_transfer', 'Bank Transfer'), ('SADAD', 'SADAD')], max_length=20)), - ('comments', models.TextField(blank=True, null=True)), - ('formatted_order_id', models.CharField(editable=False, max_length=10, unique=True)), - ('created', models.DateTimeField(auto_now_add=True)), - ('estimate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to=settings.DJANGO_LEDGER_ESTIMATE_MODEL, verbose_name='Estimate')), - ('invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to=settings.DJANGO_LEDGER_INVOICE_MODEL, verbose_name='Invoice')), - ], - options={ - 'ordering': ['-created'], - }, - ), - 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, unique=True, verbose_name='ID Number')), - ('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region='SA', verbose_name='Phone Number')), - ('email', models.EmailField(max_length=255, verbose_name='Email Address')), - ('address', models.CharField(blank=True, max_length=200, null=True, verbose_name='Address')), - ('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.CreateModel( name='Opportunity', fields=[ @@ -412,9 +642,9 @@ class Migration(migrations.Migration): ('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=settings.DJANGO_LEDGER_CUSTOMER_MODEL)), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='django_ledger.customermodel')), ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.dealer')), - ('estimate', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to=settings.DJANGO_LEDGER_ESTIMATE_MODEL)), + ('estimate', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to='django_ledger.estimatemodel')), ('lead', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunity', to='inventory.lead')), ('staff', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to='inventory.staff', verbose_name='Owner')), ], @@ -423,37 +653,6 @@ class Migration(migrations.Migration): '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=[ @@ -461,8 +660,8 @@ class Migration(migrations.Migration): ('old_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('canceled', 'Canceled')], max_length=50, verbose_name='Old Status')), ('new_status', models.CharField(choices=[('new', 'New'), ('pending', 'Pending'), ('in_progress', 'In Progress'), ('qualified', 'Qualified'), ('contacted', 'Contacted'), ('converted', 'Converted'), ('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')), + ('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', @@ -475,270 +674,41 @@ class Migration(migrations.Migration): 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.CreateModel( - name='Email', + name='UserActivityLog', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('object_id', models.PositiveIntegerField()), - ('from_email', models.TextField(blank=True, null=True, verbose_name='From Email')), - ('to_email', models.TextField(blank=True, null=True, verbose_name='To Email')), - ('subject', models.TextField(blank=True, null=True, verbose_name='Subject')), - ('message', models.TextField(blank=True, null=True, verbose_name='Message')), - ('status', models.CharField(choices=[('SENT', 'Sent'), ('FAILED', 'Failed'), ('DELIVERED', 'Delivered'), ('OPEN', 'Open'), ('DRAFT', 'Draft')], default='OPEN', max_length=20, verbose_name='Status')), - ('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='emails_created', to=settings.AUTH_USER_MODEL)), + ('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': 'Email', - 'verbose_name_plural': 'Emails', + 'verbose_name': 'User Activity Log', + 'verbose_name_plural': 'User Activity Logs', + 'ordering': ['-timestamp'], }, ), migrations.CreateModel( - name='DealerSettings', + name='Vendor', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('additional_info', models.JSONField(blank=True, default=dict, null=True)), - ('bill_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_cash', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('bill_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('bill_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bill_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('dealer', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='settings', to='inventory.dealer')), - ('invoice_cash_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_cash', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('invoice_prepaid_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_prepaid', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ('invoice_unearned_account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_unearned', to=settings.DJANGO_LEDGER_ACCOUNT_MODEL)), - ], - ), - migrations.CreateModel( - name='CustomGroup', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groups', to='inventory.dealer')), - ('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='')), - ], - ), - 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')), + ('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')), - ('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')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='customer_profile', to=settings.AUTH_USER_MODEL)), + ('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': 'Customer', - 'verbose_name_plural': 'Customers', - }, - ), - migrations.CreateModel( - name='CustomCard', - 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')), - ], - options={ - 'verbose_name': 'Custom Card', - 'verbose_name_plural': 'Custom Cards', - }, - ), - migrations.CreateModel( - name='CarTrim', - fields=[ - ('id_car_trim', 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)), - ('start_production_year', models.IntegerField(blank=True, null=True)), - ('end_production_year', models.IntegerField(blank=True, null=True)), - ('id_car_serie', models.ForeignKey(db_column='id_car_serie', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carserie')), - ], - options={ - 'verbose_name': 'Trim', + 'verbose_name': 'Vendor', + 'verbose_name_plural': 'Vendors', }, bases=(models.Model, inventory.mixins.LocalizedNameMixin), ), - migrations.CreateModel( - name='CarTransfer', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('transfer_date', models.DateTimeField(auto_now_add=True, verbose_name='Transfer Date')), - ('quantity', models.IntegerField(default=1, verbose_name='Quantity')), - ('remarks', models.TextField(blank=True, null=True, verbose_name='Remarks')), - ('status', models.CharField(default='draft', max_length=10, verbose_name=[('draft', 'Draft'), ('approved', 'Approved'), ('pending', 'Pending'), ('accepted', 'Accepted'), ('success', 'Success'), ('reject', 'Reject'), ('cancelled', 'Cancelled')])), - ('is_approved', models.BooleanField(default=False)), - ('active', models.BooleanField(default=True)), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), - ('car', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfer_logs', to='inventory.car', verbose_name='Car')), - ('from_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_out', to='inventory.dealer', verbose_name='From Dealer')), - ('to_dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transfers_in', to='inventory.dealer', verbose_name='To Dealer')), - ], - options={ - 'verbose_name': 'Car Transfer Log', - 'verbose_name_plural': 'Car Transfer Logs', - }, - ), - migrations.CreateModel( - name='CarSpecificationValue', - fields=[ - ('id_car_specification_value', models.AutoField(primary_key=True, serialize=False)), - ('value', models.CharField(max_length=500)), - ('unit', models.CharField(blank=True, max_length=255, null=True)), - ('id_car_specification', models.ForeignKey(db_column='id_car_specification', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.carspecification')), - ('id_car_trim', models.ForeignKey(db_column='id_car_trim', on_delete=django.db.models.deletion.DO_NOTHING, to='inventory.cartrim')), - ], - options={ - 'verbose_name': 'Specification Value', - }, - ), - migrations.CreateModel( - name='CarRegistration', - 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(blank=True, max_length=1, null=True, verbose_name='Text 2')), - ('text3', models.CharField(blank=True, max_length=1, null=True, verbose_name='Text 3')), - ('registration_date', models.DateTimeField(verbose_name='Registration Date')), - ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='inventory.car', verbose_name='Car')), - ], - options={ - 'verbose_name': 'Registration', - 'verbose_name_plural': 'Registrations', - }, - ), - 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='CarLocation', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('description', models.TextField(blank=True, help_text='Optional description about the showroom placement.', null=True, verbose_name='Description')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Updated')), - ('car', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='location', to='inventory.car', verbose_name='Car')), - ('owner', models.ForeignKey(help_text='Dealer who owns the car.', on_delete=django.db.models.deletion.CASCADE, related_name='owned_cars', to='inventory.dealer', verbose_name='Owner')), - ('showroom', models.ForeignKey(help_text='Dealer where the car is displayed (can be the owner).', on_delete=django.db.models.deletion.CASCADE, related_name='showroom_cars', to='inventory.dealer', verbose_name='Showroom')), - ], - options={ - 'verbose_name': 'Car Location', - '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.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='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='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='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(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cars', to=settings.DJANGO_LEDGER_VENDOR_MODEL, 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=settings.DJANGO_LEDGER_ITEM_MODEL, 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'), ('sale_car', 'Sale Car'), ('reserve_car', 'Reserve Car'), ('transfer_car', 'Transfer 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.DO_NOTHING, to='contenttypes.contenttype')), - ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='activities_created_by', to=settings.AUTH_USER_MODEL)), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='inventory.dealer')), - ], - options={ - 'verbose_name': 'Activity', - 'verbose_name_plural': 'Activities', - }, - ), - migrations.CreateModel( - name='DealersMake', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('added_at', models.DateTimeField(auto_now_add=True)), - ('car_make', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='car_dealers', to='inventory.carmake')), - ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dealer_makes', to='inventory.dealer')), - ], - options={ - 'unique_together': {('dealer', 'car_make')}, - }, - ), migrations.CreateModel( name='CarReservation', fields=[ @@ -755,6 +725,18 @@ class Migration(migrations.Migration): 'unique_together': {('car', 'reserved_until')}, }, ), + migrations.CreateModel( + name='DealersMake', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('added_at', models.DateTimeField(auto_now_add=True)), + ('car_make', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='car_dealers', to='inventory.carmake')), + ('dealer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dealer_makes', to='inventory.dealer')), + ], + options={ + 'unique_together': {('dealer', 'car_make')}, + }, + ), migrations.CreateModel( name='CarColors', fields=[ diff --git a/inventory/services.py b/inventory/services.py index 91585363..cd6f15d9 100644 --- a/inventory/services.py +++ b/inventory/services.py @@ -28,9 +28,9 @@ def get_make(item): :rtype: CarMake or None """ data = CarMake.objects.filter(name__iexact=item).first() - if not data: + if not data: r = item.split(" ") - for i in r: + for i in r: if data:= CarMake.objects.filter(name__iexact=i).first(): break return data @@ -48,9 +48,9 @@ def get_model(item,make): or None if no match is found. """ data = make.carmodel_set.filter(name__iexact=item).first() - if not data: + if not data: r = item.split(" ") - for i in r: + for i in r: if data:=make.carmodel_set.filter(name__iexact=i).first(): break return data @@ -72,6 +72,7 @@ def normalize_name(name): def decodevin(vin): + """ Decodes a Vehicle Identification Number (VIN) using multiple decoding functions and returns the decoded result. This function attempts to decode the VIN using @@ -112,7 +113,7 @@ def decode_vin(vin): """ v = VIN(vin) data = {} - if v: + if v: data = { "maker": v.Make, "model": v.Model, @@ -159,4 +160,5 @@ def elm(vin): "model": response["data"]["model"], "modelYear": response["data"]["modelYear"], } + print(data) return data if all([x for x in data.values()]) else None diff --git a/inventory/signals.py b/inventory/signals.py index 4cd796ef..53e6774c 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -1,3 +1,4 @@ +from inventory.tasks import create_coa_accounts,create_accounts_for_make,create_settings from django.contrib.auth.models import Group from decimal import Decimal from django.db.models.signals import post_save, post_delete, pre_delete, pre_save @@ -142,519 +143,10 @@ def create_ledger_entity(sender, instance, created, **kwargs): for u in models.UnitOfMeasure.choices: entity.create_uom(name=u[1], unit_abbr=u[0]) - # Cash Account - asset_ca_cash = entity.create_account( - coa_model=coa, - code="1101", - role=roles.ASSET_CA_CASH, - name=_("Cash"), - balance_type="debit", - active=True, - ) - asset_ca_cash.role_default = True - asset_ca_cash.save() - - # Accounts Receivable Account - asset_ca_receivables = entity.create_account( - coa_model=coa, - code="1102", - role=roles.ASSET_CA_RECEIVABLES, - name=_("Accounts Receivable"), - balance_type="debit", - active=True, - ) - asset_ca_receivables.role_default = True - asset_ca_receivables.save() - - # Inventory Account - asset_ca_inventory = entity.create_account( - coa_model=coa, - code="1103", - 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="1104", - role=roles.ASSET_CA_PREPAID, - name=_("Prepaid Expenses"), - balance_type="debit", - active=True, - ) - asset_ca_prepaid.role_default = True - asset_ca_prepaid.save() - - # Employee Expenses Account - asset_ca_prepaid_employee = entity.create_account( - coa_model=coa, - code="1105", - role=roles.ASSET_CA_PREPAID, - name=_("Employee Advance"), - balance_type="debit", - active=True, - ) - - - # VAT Payable Account - liability_ltl_vat_receivable = entity.create_account( - coa_model=coa, - code="1106", - 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_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() - - # intangible Account - asset_lti_land_intangable = entity.create_account( - coa_model=coa, - code="1202", - role=roles.ASSET_INTANGIBLE_ASSETS, - name=_("Intangible Assets"), - balance_type="debit", - active=True, - ) - asset_lti_land_intangable.role_default = True - asset_lti_land_intangable.save() - - # investment property Account - asset_lti_land_investment = entity.create_account( - coa_model=coa, - code="1204", - role=roles.ASSET_LTI_SECURITIES, - name=_("Investments"), - balance_type="debit", - active=True, - ) - 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, - code="1301", - role=roles.ASSET_PPE_BUILDINGS, - name=_("Buildings"), - balance_type="debit", - active=True, - ) - asset_ppe_buildings.role_default = True - asset_ppe_buildings.save() - - - - # Accounts Payable Account - liability_cl_acc_payable = entity.create_account( - coa_model=coa, - code="2101", - role=roles.LIABILITY_CL_ACC_PAYABLE, - name=_("Accounts Payable"), - balance_type="credit", - active=True, - ) - liability_cl_acc_payable.role_default = True - liability_cl_acc_payable.save() - - # Deferred Revenue Account - liability_cl_def_rev = entity.create_account( - coa_model=coa, - code="2103", - role=roles.LIABILITY_CL_DEFERRED_REVENUE, - name=_("Deferred Revenue"), - balance_type="credit", - active=True, - ) - liability_cl_def_rev.role_default = True - liability_cl_def_rev.save() - - # Wages Payable Account - liability_cl_wages_payable = entity.create_account( - coa_model=coa, - code="2102", - role=roles.LIABILITY_CL_WAGES_PAYABLE, - name=_("Wages Payable"), - balance_type="credit", - active=True, - ) - liability_cl_wages_payable.role_default = True - liability_cl_wages_payable.save() - - # Long-Term Notes Payable Account - liability_ltl_notes_payable = entity.create_account( - coa_model=coa, - code="2201", - role=roles.LIABILITY_LTL_NOTES_PAYABLE, - name=_("Long-Term Notes Payable"), - balance_type="credit", - active=True, - ) - liability_ltl_notes_payable.role_default = True - liability_ltl_notes_payable.save() - - # VAT Payable Account - liability_ltl_vat_payable = entity.create_account( - coa_model=coa, - code="2106", - 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_CL_OTHER, - name=_("Taxes Payable"), - balance_type="credit", - active=True, - ) - - # social insurance Payable Account - liability_ltl_social_insurance_payable = entity.create_account( - coa_model=coa, - code="2108", - role=roles.LIABILITY_LTL_NOTES_PAYABLE, - name=_("Social Insurance Payable"), - balance_type="credit", - 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, - code="2203", - role=roles.LIABILITY_LTL_MORTGAGE_PAYABLE, - name=_("Mortgage Payable"), - balance_type="credit", - active=True, - ) - liability_ltl_mortgage_payable.role_default = True - liability_ltl_mortgage_payable.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="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( - coa_model=coa, - code="4101", - role=roles.INCOME_OPERATIONAL, - name=_("Sales Revenue"), - balance_type="credit", - active=True, - ) - income_operational.role_default = True - income_operational.save() - - # Interest Income Account - income_interest = entity.create_account( - coa_model=coa, - code="4102", - role=roles.INCOME_INTEREST, - name=_("Interest Income"), - balance_type="credit", - active=True, - ) - income_interest.role_default = True - income_interest.save() - - # Uneared Income Account - income_unearned = entity.create_account( - coa_model=coa, - code="4103", - role=roles.INCOME_OTHER, - name=_("Unearned Income"), - balance_type="credit", - 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( - coa_model=coa, - code="5101", - role=roles.COGS, - name=_("Cost of Goods Sold"), - balance_type="debit", - active=True, - ) - expense_cogs.role_default = True - expense_cogs.save() - - - # accrued Expenses Account - expense_cogs = entity.create_account( - coa_model=coa, - code="6117", - role=roles.EXPENSE_OPERATIONAL, - name=_("Accrued Expenses"), - balance_type="debit", - active=True, - ) - - # accrued salaries Account - expense_cogs = entity.create_account( - coa_model=coa, - code="6118", - role=roles.EXPENSE_OPERATIONAL, - name=_("Accrued Salaries"), - balance_type="debit", - active=True, - ) - - # Rent Expense Account - expense_rent = entity.create_account( - coa_model=coa, - code="6102", - role=roles.EXPENSE_OPERATIONAL, - name=_("Rent Expense"), - balance_type="debit", - active=True, - ) - # expense_rent.role_default = True - # expense_rent.save() - - # Salaries and Administrative Fees - expense_salaries = entity.create_account( - coa_model=coa, - code="6103", - role=roles.EXPENSE_OPERATIONAL, - name=_("Salaries and Administrative Fees"), - balance_type="debit", - active=True, - ) - - # Medical Insurance - expense_medical_insurance = entity.create_account( - coa_model=coa, - code="6104", - role=roles.EXPENSE_OPERATIONAL, - name=_("Medical Insurance"), - balance_type="debit", - active=True, - ) - - # Marketing and Advertising Expenses - expense_marketing = entity.create_account( - coa_model=coa, - code="6105", - role=roles.EXPENSE_OPERATIONAL, - name=_("Marketing and Advertising Expenses"), - balance_type="debit", - active=True, - ) - - # Commissions and Incentives - expense_commissions = entity.create_account( - coa_model=coa, - code="6106", - role=roles.EXPENSE_OPERATIONAL, - name=_("Commissions and Incentives"), - balance_type="debit", - active=True, - ) - - # Travel Tickets - expense_travel = entity.create_account( - coa_model=coa, - code="6107", - role=roles.EXPENSE_OPERATIONAL, - name=_("Travel Tickets"), - balance_type="debit", - active=True, - ) - - # Social Insurance - expense_other = entity.create_account( - coa_model=coa, - code="6108", - role=roles.EXPENSE_OPERATIONAL, - name=_("Social Insurance"), - balance_type="debit", - active=True, - ) - - # Government Fees - expense_other = entity.create_account( - coa_model=coa, - code="6109", - role=roles.EXPENSE_OPERATIONAL, - name=_("Government Fees"), - balance_type="debit", - active=True, - ) - - # Fees and Subscriptions - expense_other = entity.create_account( - coa_model=coa, - code="6110", - role=roles.EXPENSE_OPERATIONAL, - name=_("Fees and Subscriptions"), - balance_type="debit", - active=True, - ) - - # Office Services Expenses - expense_other = entity.create_account( - coa_model=coa, - code="6111", - role=roles.EXPENSE_OPERATIONAL, - name=_("Office Services Expenses"), - balance_type="debit", - active=True, - ) - - # Office Supplies and Printing - expense_other = entity.create_account( - coa_model=coa, - code="6112", - role=roles.EXPENSE_OPERATIONAL, - name=_("Office Supplies and Printing"), - balance_type="debit", - active=True, - ) - - # Hospitality Expenses - expense_other = entity.create_account( - coa_model=coa, - code="6113", - role=roles.EXPENSE_OPERATIONAL, - name=_("Hospitality Expenses"), - balance_type="debit", - active=True, - ) - - # Bank Commissions - expense_other = entity.create_account( - coa_model=coa, - code="6114", - role=roles.EXPENSE_OPERATIONAL, - name=_("Bank Commissions"), - balance_type="debit", - active=True, - ) - - # Other Expenses - expense_other = entity.create_account( - coa_model=coa, - code="6115", - role=roles.EXPENSE_OPERATIONAL, - name=_("Other Expenses"), - balance_type="debit", - active=True, - ) - - # Transportation Expenses - expense_other = entity.create_account( - coa_model=coa, - code="6116", - role=roles.EXPENSE_OPERATIONAL, - name=_("Transportation Expenses"), - balance_type="debit", - 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 COA accounts, background task + create_coa_accounts(instance.pk) + create_settings(instance.pk) + # create_accounts_for_make(instance.pk) @receiver(post_save, sender=models.Dealer) def create_dealer_groups(sender, instance, created, **kwargs): @@ -679,7 +171,6 @@ def create_dealer_groups(sender, instance, created, **kwargs): group_manager,created = models.CustomGroup.objects.get_or_create(name=group_name, dealer=instance, group=group) group_manager.set_default_permissions() instance.user.groups.add(group) - transaction.on_commit(create_groups) # Create Vendor @receiver(post_save, sender=models.Vendor) @@ -1137,8 +628,6 @@ def create_dealer_settings(sender, instance, created, **kwargs): # current_staff_count = instance.dealer.staff.count() # if current_staff_count > allowed_users: # raise ValidationError(_("You have reached the maximum number of staff users allowed for your plan.")) - - @receiver(post_save, sender=models.Dealer) def create_vat(sender, instance, created, **kwargs): """ @@ -1176,21 +665,21 @@ def create_make_ledger_accounts(sender, instance, created, **kwargs): entity = instance.entity coa = entity.get_default_coa() - last_account = entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES).order_by('-created').first() - if len(last_account.code) == 4: - code = f"{int(last_account.code)}{1:03d}" - elif len(last_account.code) > 4: - code = f"{int(last_account.code)+1}" + # for make in models.CarMake.objects.all(): + # last_account = entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES).order_by('-created').first() + # if len(last_account.code) == 4: + # code = f"{int(last_account.code)}{1:03d}" + # elif len(last_account.code) > 4: + # code = f"{int(last_account.code)+1}" + # entity.create_account( + # name=make.name, + # code=code, + # role=roles.ASSET_CA_RECEIVABLES, + # coa_model=coa, + # balance_type="credit", + # active=True + # ) - for make in models.CarMake.objects.all(): - entity.create_account( - name=make.name, - code=code, - role=roles.ASSET_CA_RECEIVABLES, - coa_model=coa, - balance_type="credit", - active=True - ) # @receiver(post_save, sender=VendorModel) @@ -1333,4 +822,4 @@ def update_finance_cost(sender, instance, created, **kwargs): # else: # save_journal(instance,ledger,vendor) # else: - # save_journal(instance,ledger,vendor) \ No newline at end of file + # save_journal(instance,ledger,vendor) diff --git a/inventory/tasks.py b/inventory/tasks.py new file mode 100644 index 00000000..adb82e9b --- /dev/null +++ b/inventory/tasks.py @@ -0,0 +1,580 @@ +from django_ledger.io import roles +from django.core.mail import send_mail +from background_task import background +from django.utils.translation import gettext_lazy as _ +from inventory.models import DealerSettings,CarMake,Dealer + +@background +def create_coa_accounts(pk): + instance = Dealer.objects.get(pk=pk) + entity = instance.entity + coa = entity.get_default_coa() + + # Cash Account + asset_ca_cash = entity.create_account( + coa_model=coa, + code="1101", + role=roles.ASSET_CA_CASH, + name=_("Cash"), + balance_type="debit", + active=True, + ) + asset_ca_cash.role_default = True + asset_ca_cash.save() + + # Accounts Receivable Account + asset_ca_receivables = entity.create_account( + coa_model=coa, + code="1102", + role=roles.ASSET_CA_RECEIVABLES, + name=_("Accounts Receivable"), + balance_type="debit", + active=True, + ) + asset_ca_receivables.role_default = True + asset_ca_receivables.save() + + # Inventory Account + asset_ca_inventory = entity.create_account( + coa_model=coa, + code="1103", + 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="1104", + role=roles.ASSET_CA_PREPAID, + name=_("Prepaid Expenses"), + balance_type="debit", + active=True, + ) + asset_ca_prepaid.role_default = True + asset_ca_prepaid.save() + + # Employee Expenses Account + asset_ca_prepaid_employee = entity.create_account( + coa_model=coa, + code="1105", + role=roles.ASSET_CA_PREPAID, + name=_("Employee Advance"), + balance_type="debit", + active=True, + ) + + + # VAT Payable Account + liability_ltl_vat_receivable = entity.create_account( + coa_model=coa, + code="1106", + 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_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() + + # intangible Account + asset_lti_land_intangable = entity.create_account( + coa_model=coa, + code="1202", + role=roles.ASSET_INTANGIBLE_ASSETS, + name=_("Intangible Assets"), + balance_type="debit", + active=True, + ) + asset_lti_land_intangable.role_default = True + asset_lti_land_intangable.save() + + # investment property Account + asset_lti_land_investment = entity.create_account( + coa_model=coa, + code="1204", + role=roles.ASSET_LTI_SECURITIES, + name=_("Investments"), + balance_type="debit", + active=True, + ) + 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, + code="1301", + role=roles.ASSET_PPE_BUILDINGS, + name=_("Buildings"), + balance_type="debit", + active=True, + ) + asset_ppe_buildings.role_default = True + asset_ppe_buildings.save() + + + + # Accounts Payable Account + liability_cl_acc_payable = entity.create_account( + coa_model=coa, + code="2101", + role=roles.LIABILITY_CL_ACC_PAYABLE, + name=_("Accounts Payable"), + balance_type="credit", + active=True, + ) + liability_cl_acc_payable.role_default = True + liability_cl_acc_payable.save() + + # Deferred Revenue Account + liability_cl_def_rev = entity.create_account( + coa_model=coa, + code="2103", + role=roles.LIABILITY_CL_DEFERRED_REVENUE, + name=_("Deferred Revenue"), + balance_type="credit", + active=True, + ) + liability_cl_def_rev.role_default = True + liability_cl_def_rev.save() + + # Wages Payable Account + liability_cl_wages_payable = entity.create_account( + coa_model=coa, + code="2102", + role=roles.LIABILITY_CL_WAGES_PAYABLE, + name=_("Wages Payable"), + balance_type="credit", + active=True, + ) + liability_cl_wages_payable.role_default = True + liability_cl_wages_payable.save() + + # Long-Term Notes Payable Account + liability_ltl_notes_payable = entity.create_account( + coa_model=coa, + code="2201", + role=roles.LIABILITY_LTL_NOTES_PAYABLE, + name=_("Long-Term Notes Payable"), + balance_type="credit", + active=True, + ) + liability_ltl_notes_payable.role_default = True + liability_ltl_notes_payable.save() + + # VAT Payable Account + liability_ltl_vat_payable = entity.create_account( + coa_model=coa, + code="2106", + 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_CL_OTHER, + name=_("Taxes Payable"), + balance_type="credit", + active=True, + ) + + # social insurance Payable Account + liability_ltl_social_insurance_payable = entity.create_account( + coa_model=coa, + code="2108", + role=roles.LIABILITY_LTL_NOTES_PAYABLE, + name=_("Social Insurance Payable"), + balance_type="credit", + 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, + code="2203", + role=roles.LIABILITY_LTL_MORTGAGE_PAYABLE, + name=_("Mortgage Payable"), + balance_type="credit", + active=True, + ) + liability_ltl_mortgage_payable.role_default = True + liability_ltl_mortgage_payable.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="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( + coa_model=coa, + code="4101", + role=roles.INCOME_OPERATIONAL, + name=_("Sales Revenue"), + balance_type="credit", + active=True, + ) + income_operational.role_default = True + income_operational.save() + + # Interest Income Account + income_interest = entity.create_account( + coa_model=coa, + code="4102", + role=roles.INCOME_INTEREST, + name=_("Interest Income"), + balance_type="credit", + active=True, + ) + income_interest.role_default = True + income_interest.save() + + # Uneared Income Account + income_unearned = entity.create_account( + coa_model=coa, + code="4103", + role=roles.INCOME_OTHER, + name=_("Unearned Income"), + balance_type="credit", + 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( + coa_model=coa, + code="5101", + role=roles.COGS, + name=_("Cost of Goods Sold"), + balance_type="debit", + active=True, + ) + expense_cogs.role_default = True + expense_cogs.save() + + + # accrued Expenses Account + expense_cogs = entity.create_account( + coa_model=coa, + code="6117", + role=roles.EXPENSE_OPERATIONAL, + name=_("Accrued Expenses"), + balance_type="debit", + active=True, + ) + + # accrued salaries Account + expense_cogs = entity.create_account( + coa_model=coa, + code="6118", + role=roles.EXPENSE_OPERATIONAL, + name=_("Accrued Salaries"), + balance_type="debit", + active=True, + ) + + # Rent Expense Account + expense_rent = entity.create_account( + coa_model=coa, + code="6102", + role=roles.EXPENSE_OPERATIONAL, + name=_("Rent Expense"), + balance_type="debit", + active=True, + ) + # expense_rent.role_default = True + # expense_rent.save() + + # Salaries and Administrative Fees + expense_salaries = entity.create_account( + coa_model=coa, + code="6103", + role=roles.EXPENSE_OPERATIONAL, + name=_("Salaries and Administrative Fees"), + balance_type="debit", + active=True, + ) + + # Medical Insurance + expense_medical_insurance = entity.create_account( + coa_model=coa, + code="6104", + role=roles.EXPENSE_OPERATIONAL, + name=_("Medical Insurance"), + balance_type="debit", + active=True, + ) + + # Marketing and Advertising Expenses + expense_marketing = entity.create_account( + coa_model=coa, + code="6105", + role=roles.EXPENSE_OPERATIONAL, + name=_("Marketing and Advertising Expenses"), + balance_type="debit", + active=True, + ) + + # Commissions and Incentives + expense_commissions = entity.create_account( + coa_model=coa, + code="6106", + role=roles.EXPENSE_OPERATIONAL, + name=_("Commissions and Incentives"), + balance_type="debit", + active=True, + ) + + # Travel Tickets + expense_travel = entity.create_account( + coa_model=coa, + code="6107", + role=roles.EXPENSE_OPERATIONAL, + name=_("Travel Tickets"), + balance_type="debit", + active=True, + ) + + # Social Insurance + expense_other = entity.create_account( + coa_model=coa, + code="6108", + role=roles.EXPENSE_OPERATIONAL, + name=_("Social Insurance"), + balance_type="debit", + active=True, + ) + + # Government Fees + expense_other = entity.create_account( + coa_model=coa, + code="6109", + role=roles.EXPENSE_OPERATIONAL, + name=_("Government Fees"), + balance_type="debit", + active=True, + ) + + # Fees and Subscriptions + expense_other = entity.create_account( + coa_model=coa, + code="6110", + role=roles.EXPENSE_OPERATIONAL, + name=_("Fees and Subscriptions"), + balance_type="debit", + active=True, + ) + + # Office Services Expenses + expense_other = entity.create_account( + coa_model=coa, + code="6111", + role=roles.EXPENSE_OPERATIONAL, + name=_("Office Services Expenses"), + balance_type="debit", + active=True, + ) + + # Office Supplies and Printing + expense_other = entity.create_account( + coa_model=coa, + code="6112", + role=roles.EXPENSE_OPERATIONAL, + name=_("Office Supplies and Printing"), + balance_type="debit", + active=True, + ) + + # Hospitality Expenses + expense_other = entity.create_account( + coa_model=coa, + code="6113", + role=roles.EXPENSE_OPERATIONAL, + name=_("Hospitality Expenses"), + balance_type="debit", + active=True, + ) + + # Bank Commissions + expense_other = entity.create_account( + coa_model=coa, + code="6114", + role=roles.EXPENSE_OPERATIONAL, + name=_("Bank Commissions"), + balance_type="debit", + active=True, + ) + + # Other Expenses + expense_other = entity.create_account( + coa_model=coa, + code="6115", + role=roles.EXPENSE_OPERATIONAL, + name=_("Other Expenses"), + balance_type="debit", + active=True, + ) + + # Transportation Expenses + expense_other = entity.create_account( + coa_model=coa, + code="6116", + role=roles.EXPENSE_OPERATIONAL, + name=_("Transportation Expenses"), + balance_type="debit", + 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) + + +# @background +# def create_groups(instance): +# group_names = ["Inventory", "Accountant", "Sales"] +# for group_name in group_names: +# group, _ = Group.objects.get_or_create(name=f"{instance.pk}_{group_name}") +# group_manager,_ = CustomGroup.objects.get_or_create(name=group_name, dealer=instance, group=group) +# group_manager.set_default_permissions() +# instance.user.groups.add(group) + +@background +def create_settings(pk): + instance = Dealer.objects.get(pk=pk) + + DealerSettings.objects.create( + dealer=instance, + invoice_cash_account=instance.entity.get_all_accounts().filter(role=roles.ASSET_CA_CASH).first(), + invoice_prepaid_account=instance.entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES).first(), + invoice_unearned_account=instance.entity.get_all_accounts().filter(role=roles.LIABILITY_CL_DEFERRED_REVENUE).first(), + bill_cash_account=instance.entity.get_all_accounts().filter(role=roles.ASSET_CA_CASH).first(), + bill_prepaid_account=instance.entity.get_all_accounts().filter(role=roles.ASSET_CA_PREPAID).first(), + bill_unearned_account=instance.entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE).first() + ) + +@background +def create_accounts_for_make(pk): + instance = Dealer.objects.get(pk=pk) + entity = instance.entity + coa = entity.get_default_coa() + + for make in CarMake.objects.all(): + last_account = entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES).order_by('-created').first() + if len(last_account.code) == 4: + code = f"{int(last_account.code)}{1:03d}" + elif len(last_account.code) > 4: + code = f"{int(last_account.code)+1}" + entity.create_account( + name=make.name, + code=code, + role=roles.ASSET_CA_RECEIVABLES, + coa_model=coa, + balance_type="credit", + active=True + ) + + + +@background +def send_email(from_, to_, subject, message): + subject = subject + message = message + from_email = from_ + recipient_list = [to_] + send_mail(subject, message, from_email, recipient_list) \ No newline at end of file diff --git a/inventory/templatetags/__pycache__/custom_filters.cpython-311.pyc b/inventory/templatetags/__pycache__/custom_filters.cpython-311.pyc deleted file mode 100644 index 5eefe0ce..00000000 Binary files a/inventory/templatetags/__pycache__/custom_filters.cpython-311.pyc and /dev/null differ diff --git a/inventory/views.py b/inventory/views.py index cb3c7679..d9c3189a 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -4,13 +4,15 @@ import json import logging import datetime import numpy as np -from rich import print +# from rich import print from random import randint from decimal import Decimal from calendar import month_name from pyzbar.pyzbar import decode from urllib.parse import urlparse, urlunparse +##################################################################### + from django.db.models.deletion import RestrictedError # Django @@ -66,12 +68,27 @@ from django_ledger.views import ( LedgerModelDeleteView as LedgerModelDeleteViewBase, LedgerModelCreateView as LedgerModelCreateViewBase ) +from django_ledger.views.invoice import ( + InvoiceModelDetailView as InvoiceModelDetailViewBase, +) +from django_ledger.views import ( + LedgerModelListView as LedgerModelListViewBase, + JournalEntryModelTXSDetailView as JournalEntryModelTXSDetailViewBase, + LedgerModelModelActionView as LedgerModelModelActionViewBase, + LedgerModelDeleteView as LedgerModelDeleteViewBase, + LedgerModelCreateView as LedgerModelCreateViewBase +) from django_ledger.forms.account import AccountModelCreateForm, AccountModelUpdateForm from django_ledger.views.entity import ( EntityModelDetailBaseView, EntityModelDetailHandlerView, ) from django_ledger.forms.ledger import LedgerModelCreateForm +from django_ledger.views.entity import ( + EntityModelDetailBaseView, + EntityModelDetailHandlerView, +) +from django_ledger.forms.ledger import LedgerModelCreateForm from django_ledger.forms.item import ( ExpenseItemCreateForm, ExpenseItemUpdateForm, @@ -142,18 +159,20 @@ from .utils import ( get_financial_values, get_item_transactions, reserve_car, - send_email, + # send_email, get_user_type, set_bill_payment, set_invoice_payment, CarTransfer, ) +from .tasks import send_email logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) + class Hash(Func): """ Represents a function used to compute a hash value. @@ -570,7 +589,6 @@ class CarCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): template_name = "inventory/car_form.html" permission_required = ["inventory.add_car"] - def get_form(self, form_class=None): form = super().get_form(form_class) dealer = get_user_type(self.request) @@ -1012,6 +1030,7 @@ class CarListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): ) return context + def get_queryset(self): dealer = get_user_type(self.request) qs = super().get_queryset() @@ -1048,6 +1067,7 @@ class CarListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): return qs + @login_required def inventory_stats_view(request): """ @@ -1463,6 +1483,7 @@ class CarTransferCreateView(LoginRequiredMixin, CreateView): return reverse_lazy("car_detail", kwargs={"pk": self.object.car.pk}) + class CarTransferDetailView(LoginRequiredMixin, SuccessMessageMixin, DetailView): """ Provides a detailed view of a specific car transfer record. @@ -1493,6 +1514,7 @@ class CarTransferDetailView(LoginRequiredMixin, SuccessMessageMixin, DetailView) return context + @login_required def car_transfer_approve(request, car_pk, transfer_pk): """ @@ -1786,7 +1808,7 @@ class DealerDetailView(LoginRequiredMixin, DetailView): return models.Dealer.objects.annotate( staff_count=Coalesce( Count("staff"), Value(0) - ) + ) # Get the number of staff members ) def get_context_data(self, **kwargs): diff --git a/load_initial_data.sh b/load_initial_data.sh new file mode 100755 index 00000000..80f6b64a --- /dev/null +++ b/load_initial_data.sh @@ -0,0 +1,25 @@ +#!/bin/bash +echo "Loading initial data" + +echo "Loading carmake" +python3 manage.py loaddata --app carmake carmake_backup.json + +echo "Loading carmodel" +python3 manage.py loaddata --app carmodel carmodel_backup.json + +echo "Loading carserie" +python3 manage.py loaddata --app carserie carserie_backup.json + +echo "Loading cartrim" +python3 manage.py loaddata --app cartrim cartrim_backup.json + +echo "Loading caroption" +python3 manage.py loaddata --app caroption caroption_backup.json + +echo "Loading carequipment" +python3 manage.py loaddata --app carequipment carequipment_backup.json + +echo "Populating colors" +python3 manage.py populate_colors + +echo "Done" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 6f10ed53..ad9d08cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ aiohttp-retry aiosignal alabaster albucore -albumentations annotated-types anyio arabic-reshaper @@ -32,7 +31,6 @@ cryptography cycler Cython decorator -defusedxml desert diff-match-patch dill @@ -83,7 +81,6 @@ docopt docutils easy-thumbnails emoji -et_xmlfile Faker filelock fire @@ -117,8 +114,6 @@ kiwisolver lazy_loader ledger libretranslatepy -lmdb -lxml Markdown markdown-it-py MarkupSafe @@ -195,9 +190,6 @@ rfc3986 rich rubicon-objc sacremoses -scikit-image -scikit-learn -scipy selenium sentencepiece shapely @@ -211,7 +203,6 @@ sqlparse stanza stringzilla suds -sympy tablib termcolor threadpoolctl diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 00000000..9c6c1f79 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,68 @@ +annotated-types==0.7.0 +anyio==4.9.0 +asgiref==3.8.1 +Babel==2.15.0 +certifi==2025.1.31 +cffi==1.17.1 +charset-normalizer==3.4.1 +colorama==0.4.6 +crispy-bootstrap5==2024.10 +cryptography==44.0.2 +diff-match-patch==20241021 +distro==1.9.0 +Django==5.1.7 +django-allauth==65.6.0 +django-appointment==3.8.0 +django-background-tasks==1.2.8 +django-bootstrap5==25.1 +django-countries==7.6.1 +django-crispy-forms==2.3 +django-extensions==3.2.3 +django-filter==25.1 +django-import-export==4.3.7 +django-ledger==0.7.6.1 +django-next-url-mixin==0.4.0 +django-ordered-model==3.7.4 +django-phonenumber-field==8.0.0 +django-plans==2.0.0 +django-sequences==3.0 +django-tables2==2.7.5 +django-treebeard==4.7.1 +djangorestframework==3.15.2 +docopt==0.6.2 +Faker==37.1.0 +fpdf==1.7.2 +h11==0.14.0 +httpcore==1.0.7 +httpx==0.28.1 +icalendar==6.1.2 +idna==3.10 +jiter==0.9.0 +jwt==1.3.1 +Markdown==3.7 +num2words==0.5.14 +numpy==2.2.4 +ofxtools==0.9.5 +openai==1.68.2 +opencv-python==4.11.0.86 +phonenumbers==8.13.42 +pillow==10.4.0 +pycparser==2.22 +pydantic==2.10.6 +pydantic_core==2.27.2 +python-dateutil==2.9.0.post0 +python-stdnum==1.20 +pytz==2025.2 +pyvin==0.0.2 +pyzbar==0.1.9 +requests==2.32.3 +six==1.17.0 +sniffio==1.3.1 +sqlparse==0.5.3 +suds==1.2.0 +swapper==1.3.0 +tablib==3.8.0 +tqdm==4.67.1 +typing_extensions==4.13.0 +tzdata==2025.2 +urllib3==2.3.0 diff --git a/scripts/generate.py b/scripts/generate.py index 864598a1..347058c5 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -1,6 +1,6 @@ from inventory.models import * from django_ledger.models import VendorModel -from rich import print +# from rich import print import random import datetime diff --git a/scripts/run.py b/scripts/run.py index 43362480..43b7dc69 100644 --- a/scripts/run.py +++ b/scripts/run.py @@ -16,7 +16,7 @@ from django_ledger.models import ( CustomerModel, EntityManagementModel, ) -from rich import print +# from rich import print from datetime import date from inventory.models import ( Car, @@ -30,6 +30,7 @@ from inventory.models import ( ) from inventory.utils import CarFinanceCalculator from appointment.models import Appointment, AppointmentRequest, Service, StaffMember +from appointment.models import Appointment, AppointmentRequest, Service, StaffMember from django.contrib.auth import get_user_model from django_ledger.io.io_core import get_localdate from datetime import datetime, timedelta @@ -42,6 +43,8 @@ User = get_user_model() load_dotenv(".env") + + def run(): # print(Service.objects.first().pk) # print(Appointment.objects.first().client) @@ -142,6 +145,9 @@ def run(): # info = item.additional_info["car_info"] # finance = item.additional_info["car_finance"] # print({"vin":info["make"],"mode":info["model"],"year":info["year"],"trim":info["trim"],"mileage":info["mileage"],"cost_price":finance["cost_price"],"selling_price":finance["selling_price"]}) + # info = item.additional_info["car_info"] + # finance = item.additional_info["car_finance"] + # print({"vin":info["make"],"mode":info["model"],"year":info["year"],"trim":info["trim"],"mileage":info["mileage"],"cost_price":finance["cost_price"],"selling_price":finance["selling_price"]}) # for account in AccountModel.objects.all(): # print(account.path) @@ -175,6 +181,7 @@ def run(): # print(Permission.objects.filter(codename__icontains='customermodel').first().codename) # print(os.getenv("DJANGO_ALLOWED_HOSTS")) + car_makes = CarMake.objects.all()[:10] # Fetch the entity and COGS account @@ -186,6 +193,8 @@ def run(): for make in range(len(car_makes)): # Start from 0 to include all items # Generate a unique code + # Generate a unique code + # Create the account # account = AccountModel.objects.create( # name=car_makes[make].name, @@ -211,10 +220,17 @@ def run(): .order_by("-created") .first() ) + last_account = ( + entity.get_all_accounts() + .filter(role=roles.COGS) + .order_by("-created") + .first() + ) if len(last_account.code) == 4: code = f"{int(last_account.code)}{1:03d}" elif len(last_account.code) > 4: code = f"{int(last_account.code) + 1}" + code = f"{int(last_account.code) + 1}" # account = entity.create_account( # name=car_makes[make].name, @@ -240,12 +256,24 @@ def run(): .order_by("-created") .first() ) + path = "" + depth=3, + ) + # 00060004001S + last_account = ( + entity.get_all_accounts() + .filter(role=roles.COGS) + .order_by("-created") + .first() + ) path = "" if len(last_account.code) == 12: path = f"{int(last_account.path)}{1:03d}" elif len(last_account.code) > 12: path = f"{int(last_account.path) + 1}" + path = f"{int(last_account.path) + 1}" # account.path = path + try: try: account = cogs.add_child(instance=account) account.move(cogs, pos="sorted-sibling") @@ -254,6 +282,7 @@ def run(): except Exception as e: print(e) + # form_data = { # 'name': car_makes[make].name, # 'code': code, @@ -263,6 +292,7 @@ def run(): # 'coa_model': coa # Ensure the COA model is included # } + # Create the form instance with the data # create_form = AccountModelCreateForm(data=form_data, coa_model=coa) # # Validate and save the form @@ -286,3 +316,26 @@ def run(): # print(f"Account {account.name} created successfully.") # else: # print(f"Failed to create account {account.name}. Errors: {form.errors}") + + # create_form = AccountModelCreateForm(data=form_data, coa_model=coa) + # # Validate and save the form + # if create_form.is_valid(): + # account = create_form.save(commit=False) + # account.coa_model = coa # Set the entity for the account + + # Add the account as a child of the COGS account + # cogs.add_child(instance=account) + + # print(f"Account '{account.name}' created successfully.") + # else: + # print(f"Failed to create account. Errors: {create_form.errors}") + # form = AccountModelUpdateForm(instance=account) + + # if form.is_valid(): + # instance = form.save(commit=False) + # instance._position = "sorted-sibling" + # instance._ref_node_id = cogs.pk + # instance.save() + # print(f"Account {account.name} created successfully.") + # else: + # print(f"Failed to create account {account.name}. Errors: {form.errors}") diff --git a/scripts/run1.py b/scripts/run1.py index ee02fa03..ed0be9b3 100644 --- a/scripts/run1.py +++ b/scripts/run1.py @@ -8,7 +8,7 @@ from django_ledger.models.invoice import InvoiceModel from django_ledger.utils import accruable_net_summary from decimal import Decimal from django_ledger.models import EstimateModel,EntityModel,ItemModel,ItemTransactionModel,AccountModel,CustomerModel,EntityManagementModel -from rich import print +# from rich import print from datetime import date from inventory.models import Car, Dealer, VatRate,Lead,CarMake,CarModel,Schedule,CustomGroup from inventory.utils import CarFinanceCalculator @@ -30,19 +30,19 @@ def run(): entity = EntityModel.objects.get(admin__email="ismail.mosa.ibrahim@gmail.com") coa = entity.get_default_coa() cogs = entity.get_default_coa_accounts().filter(role=roles.COGS).first() - + last_account = entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE).order_by('-created').first() if len(last_account.code) == 4: code = f"{int(last_account.code)}{1:03d}" elif len(last_account.code) > 4: code = f"{int(last_account.code)+1}" - + print(code) - + # # Loop through car makes and create accounts # for make in range(len(car_makes)): # Start from 0 to include all items # # Generate a unique code - # code = f"{cogs.code}{make + 1:03d}" # Example: "COGS-001", "COGS-002", etc. + # code = f"{cogs.code}{make + 1:03d}" # Example: "COGS-001", "COGS-002", etc. # account = entity.create_account( # name=car_makes[make].name, # code=code, @@ -50,11 +50,11 @@ def run(): # coa_model=coa, # balance_type="debit", # active=True - # ) + # ) # try: # account = cogs.add_child(instance=account) # account.move(cogs, pos="sorted-sibling") - + # account.refresh_from_db() # account.save() # except Exception as e: @@ -67,5 +67,4 @@ def run(): # 'active': True, # 'coa_model': coa # Ensure the COA model is included # } - - \ No newline at end of file + diff --git a/staticfiles/account/js/account.js b/staticfiles/account/js/account.js new file mode 100644 index 00000000..fbc5135c --- /dev/null +++ b/staticfiles/account/js/account.js @@ -0,0 +1,20 @@ +(function () { + const allauth = window.allauth = window.allauth || {} + + function manageEmailForm (o) { + const actions = document.getElementsByName('action_remove') + if (actions.length) { + actions[0].addEventListener('click', function (e) { + if (!window.confirm(o.i18n.confirmDelete)) { + e.preventDefault() + } + }) + } + } + + allauth.account = { + forms: { + manageEmailForm + } + } +})() diff --git a/staticfiles/account/js/onload.js b/staticfiles/account/js/onload.js new file mode 100644 index 00000000..1a224c9c --- /dev/null +++ b/staticfiles/account/js/onload.js @@ -0,0 +1,12 @@ +(function () { + document.addEventListener('DOMContentLoaded', function () { + Array.from(document.querySelectorAll('script[data-allauth-onload]')).forEach(scriptElt => { + const funcRef = scriptElt.dataset.allauthOnload + if (typeof funcRef === 'string' && funcRef.startsWith('allauth.')) { + const funcArg = JSON.parse(scriptElt.textContent) + const func = funcRef.split('.').reduce((acc, part) => acc && acc[part], window) + func(funcArg) + } + }) + }) +})() diff --git a/staticfiles/admin/js/collapse.js b/staticfiles/admin/js/collapse.js new file mode 100644 index 00000000..c6c7b0f6 --- /dev/null +++ b/staticfiles/admin/js/collapse.js @@ -0,0 +1,43 @@ +/*global gettext*/ +'use strict'; +{ + window.addEventListener('load', function() { + // Add anchor tag for Show/Hide link + const fieldsets = document.querySelectorAll('fieldset.collapse'); + for (const [i, elem] of fieldsets.entries()) { + // Don't hide if fields in this fieldset have errors + if (elem.querySelectorAll('div.errors, ul.errorlist').length === 0) { + elem.classList.add('collapsed'); + const h2 = elem.querySelector('h2'); + const link = document.createElement('a'); + link.id = 'fieldsetcollapser' + i; + link.className = 'collapse-toggle'; + link.href = '#'; + link.textContent = gettext('Show'); + h2.appendChild(document.createTextNode(' (')); + h2.appendChild(link); + h2.appendChild(document.createTextNode(')')); + } + } + // Add toggle to hide/show anchor tag + const toggleFunc = function(ev) { + if (ev.target.matches('.collapse-toggle')) { + ev.preventDefault(); + ev.stopPropagation(); + const fieldset = ev.target.closest('fieldset'); + if (fieldset.classList.contains('collapsed')) { + // Show + ev.target.textContent = gettext('Hide'); + fieldset.classList.remove('collapsed'); + } else { + // Hide + ev.target.textContent = gettext('Show'); + fieldset.classList.add('collapsed'); + } + } + }; + document.querySelectorAll('fieldset.module').forEach(function(el) { + el.addEventListener('click', toggleFunc); + }); + }); +} diff --git a/staticfiles/css/SaudiRiyalFont.ttf b/staticfiles/css/SaudiRiyalFont.ttf new file mode 100644 index 00000000..d6b5ebce Binary files /dev/null and b/staticfiles/css/SaudiRiyalFont.ttf differ diff --git a/staticfiles/css/SaudiRiyalFont.woff b/staticfiles/css/SaudiRiyalFont.woff new file mode 100644 index 00000000..7d733e98 Binary files /dev/null and b/staticfiles/css/SaudiRiyalFont.woff differ diff --git a/staticfiles/css/SaudiRiyalFont.woff2 b/staticfiles/css/SaudiRiyalFont.woff2 new file mode 100644 index 00000000..f8072477 Binary files /dev/null and b/staticfiles/css/SaudiRiyalFont.woff2 differ diff --git a/templates/account/signup-wizard.html b/templates/account/signup-wizard.html index eaf5b7c9..db411d17 100644 --- a/templates/account/signup-wizard.html +++ b/templates/account/signup-wizard.html @@ -39,7 +39,7 @@