Compare commits

..

No commits in common. "ee8d8cf2878e85b27251d7c3b011da480aede93e" and "86792930f0447a253e9f4693f4805c567cb50998" have entirely different histories.

14 changed files with 219 additions and 474 deletions

View File

@ -6,64 +6,17 @@ logger = logging.getLogger(__name__)
def check_create_coa_accounts(task): def check_create_coa_accounts(task):
""" logger.info("Checking if all accounts are created")
Hook to verify account creation and handle failures instance = task.kwargs["dealer"]
""" entity = instance.entity
if task.success: coa = entity.get_default_coa()
logger.info("Account creation task completed successfully")
return
logger.warning("Account creation task failed, checking status...") for account_data in get_accounts_data():
if entity.get_all_accounts().filter(code=account_data["code"]).exists():
try: logger.info(f"Default account already exists: {account_data['code']}")
dealer_id = task.kwargs.get('dealer_id') continue
if not dealer_id: logger.info(f"Default account does not exist: {account_data['code']}")
logger.error("No dealer_id in task kwargs") create_account(entity, coa, account_data)
return
from .models import Dealer
instance = Dealer.objects.select_related('entity').get(id=dealer_id)
entity = instance.entity
if not entity:
logger.error(f"No entity for dealer {dealer_id}")
return
coa = entity.get_default_coa()
if not coa:
logger.error(f"No COA for entity {entity.id}")
return
# Check which accounts are missing and create them
from .utils import get_accounts_data, create_account
missing_accounts = []
for account_data in get_accounts_data():
if not entity.get_all_accounts().filter(code=account_data["code"]).exists():
missing_accounts.append(account_data)
logger.info(f"Missing account: {account_data['code']}")
if missing_accounts:
logger.info(f"Creating {len(missing_accounts)} missing accounts")
for account_data in missing_accounts:
create_account(entity, coa, account_data)
else:
logger.info("All accounts are already created")
except Exception as e:
logger.error(f"Error in check_create_coa_accounts hook: {e}")
# def check_create_coa_accounts(task):
# logger.info("Checking if all accounts are created")
# instance = task.kwargs["dealer"]
# entity = instance.entity
# coa = entity.get_default_coa()
# for account_data in get_accounts_data():
# if entity.get_all_accounts().filter(code=account_data["code"]).exists():
# logger.info(f"Default account already exists: {account_data['code']}")
# continue
# logger.info(f"Default account does not exist: {account_data['code']}")
# create_account(entity, coa, account_data)
def print_results(task): def print_results(task):

View File

@ -685,7 +685,7 @@ class Car(Base):
) )
# #
additional_services = models.ManyToManyField( additional_services = models.ManyToManyField(
AdditionalServices, related_name="additionals" AdditionalServices, related_name="additionals", blank=True, null=True
) )
cost_price = models.DecimalField( cost_price = models.DecimalField(
max_digits=14, max_digits=14,
@ -3307,7 +3307,6 @@ class CustomGroup(models.Model):
"payment", "payment",
"vendor", "vendor",
"additionalservices", "additionalservices",
'customer'
], ],
other_perms=[ other_perms=[
"view_car", "view_car",

View File

@ -135,101 +135,55 @@ def create_car_location(sender, instance, created, **kwargs):
print(f"Failed to create CarLocation for car {instance.vin}: {e}") print(f"Failed to create CarLocation for car {instance.vin}: {e}")
# Create Entity
@receiver(post_save, sender=models.Dealer) @receiver(post_save, sender=models.Dealer)
def create_ledger_entity(sender, instance, created, **kwargs): def create_ledger_entity(sender, instance, created, **kwargs):
"""
Signal handler for creating ledger entities and initializing accounts for a new Dealer instance upon creation.
This signal is triggered when a new Dealer instance is saved to the database. It performs the following actions:
1. Creates a ledger entity for the Dealer with necessary configurations.
2. Generates a chart of accounts (COA) for the entity and assigns it as the default.
3. Creates predefined unit of measures (UOMs) related to the entity.
4. Initializes and assigns default accounts under various roles (e.g., assets, liabilities) for the entity with their
respective configurations (e.g., account code, balance type).
This function ensures all necessary financial records and accounts are set up when a new Dealer is added, preparing the
system for future financial transactions and accounting operations.
:param sender: The model class that sent the signal (in this case, Dealer).
:param instance: The instance of the model being saved.
:param created: A boolean indicating whether a new record was created.
:param kwargs: Additional keyword arguments passed by the signal.
:return: None
"""
if created: if created:
try: entity_name = instance.user.dealer.name
# Use transaction to ensure atomicity entity = EntityModel.create_entity(
with transaction.atomic(): name=entity_name,
entity_name = instance.user.dealer.name admin=instance.user,
entity = EntityModel.create_entity( use_accrual_method=True,
name=entity_name, fy_start_month=1,
admin=instance.user, )
use_accrual_method=True,
fy_start_month=1,
)
if entity: if entity:
instance.entity = entity instance.entity = entity
instance.save(update_fields=['entity']) instance.save()
coa = entity.create_chart_of_accounts(
assign_as_default=True, commit=True, coa_name=_(f"{entity_name}-COA")
)
if coa:
# Create unit of measures
entity.create_uom(name="Unit", unit_abbr="unit")
for u in models.UnitOfMeasure.choices:
entity.create_uom(name=u[1], unit_abbr=u[0])
# Create COA synchronously first # Create COA accounts, background task
coa = entity.create_chart_of_accounts(
assign_as_default=True, commit=True,
coa_name=_(f"{entity_name}-COA")
)
if coa:
# Create essential UOMs synchronously
entity.create_uom(name="Unit", unit_abbr="unit")
# Schedule async task after successful synchronous operations
async_task( async_task(
func="inventory.tasks.create_coa_accounts", func="inventory.tasks.create_coa_accounts",
dealer_id=instance.id, # Pass ID instead of object dealer=instance,
hook="inventory.hooks.check_create_coa_accounts", hook="inventory.hooks.check_create_coa_accounts",
ack_failure=True, # Ensure task failures are acknowledged
sync=False # Explicitly set to async
) )
except Exception as e:
logger.error(f"Failed to create ledger entity for dealer {instance.id}: {e}")
# Schedule retry task
async_task(
func="inventory.tasks.retry_entity_creation",
dealer_id=instance.id,
retry_count=0
)
# Create Entity
# @receiver(post_save, sender=models.Dealer)
# def create_ledger_entity(sender, instance, created, **kwargs):
# """
# Signal handler for creating ledger entities and initializing accounts for a new Dealer instance upon creation.
# This signal is triggered when a new Dealer instance is saved to the database. It performs the following actions:
# 1. Creates a ledger entity for the Dealer with necessary configurations.
# 2. Generates a chart of accounts (COA) for the entity and assigns it as the default.
# 3. Creates predefined unit of measures (UOMs) related to the entity.
# 4. Initializes and assigns default accounts under various roles (e.g., assets, liabilities) for the entity with their
# respective configurations (e.g., account code, balance type).
# This function ensures all necessary financial records and accounts are set up when a new Dealer is added, preparing the
# system for future financial transactions and accounting operations.
# :param sender: The model class that sent the signal (in this case, Dealer).
# :param instance: The instance of the model being saved.
# :param created: A boolean indicating whether a new record was created.
# :param kwargs: Additional keyword arguments passed by the signal.
# :return: None
# """
# if created:
# entity_name = instance.user.dealer.name
# entity = EntityModel.create_entity(
# name=entity_name,
# admin=instance.user,
# use_accrual_method=True,
# fy_start_month=1,
# )
# if entity:
# instance.entity = entity
# instance.save()
# coa = entity.create_chart_of_accounts(
# assign_as_default=True, commit=True, coa_name=_(f"{entity_name}-COA")
# )
# if coa:
# # Create unit of measures
# entity.create_uom(name="Unit", unit_abbr="unit")
# for u in models.UnitOfMeasure.choices:
# entity.create_uom(name=u[1], unit_abbr=u[0])
# # Create COA accounts, background task
# async_task(
# func="inventory.tasks.create_coa_accounts",
# dealer=instance,
# hook="inventory.hooks.check_create_coa_accounts",
# )
# async_task('inventory.tasks.check_create_coa_accounts', instance, schedule_type='O', schedule_time=timedelta(seconds=20)) # async_task('inventory.tasks.check_create_coa_accounts', instance, schedule_type='O', schedule_time=timedelta(seconds=20))
# create_settings(instance.pk) # create_settings(instance.pk)

View File

@ -62,117 +62,14 @@ def create_settings(pk):
) )
def create_coa_accounts(dealer_id, **kwargs): def create_coa_accounts(**kwargs):
""" logger.info("creating all accounts are created")
Create COA accounts with retry logic and proper error handling instance = kwargs.get("dealer")
""" entity = instance.entity
from .models import Dealer coa = entity.get_default_coa()
from .utils import create_account, get_accounts_data
max_retries = 3 for account_data in get_accounts_data():
retry_delay = 2 # seconds create_account(entity, coa, account_data)
for attempt in range(max_retries):
try:
logger.info(f"Attempt {attempt + 1} to create accounts for dealer {dealer_id}")
# Get fresh instance from database
instance = Dealer.objects.select_related('entity').get(id=dealer_id)
entity = instance.entity
if not entity:
logger.error(f"No entity found for dealer {dealer_id}")
return False
coa = entity.get_default_coa()
if not coa:
logger.error(f"No COA found for entity {entity.id}")
return False
logger.info("Creating default accounts")
accounts_created = 0
with transaction.atomic():
for account_data in get_accounts_data():
logger.info(f"Creating account: {account_data['code']}")
if create_account(entity, coa, account_data):
accounts_created += 1
logger.info(f"Successfully created {accounts_created} accounts")
return True
except Exception as e:
logger.error(f"Attempt {attempt + 1} failed: {e}")
if attempt < max_retries - 1:
logger.info(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay * (attempt + 1)) # Exponential backoff
else:
logger.error(f"All {max_retries} attempts failed for dealer {dealer_id}")
# Schedule a cleanup or notification task
async_task(
"inventory.tasks.handle_account_creation_failure",
dealer_id=dealer_id,
error=str(e)
)
return False
def retry_entity_creation(dealer_id, retry_count=0):
"""
Retry entity creation if initial attempt failed
"""
from .models import Dealer
from yourapp.models import EntityModel
max_retries = 3
if retry_count >= max_retries:
logger.error(f"Max retries reached for dealer {dealer_id}")
return
try:
instance = Dealer.objects.get(id=dealer_id)
if not instance.entity:
# Retry entity creation
entity_name = instance.user.dealer.name
entity = EntityModel.create_entity(
name=entity_name,
admin=instance.user,
use_accrual_method=True,
fy_start_month=1,
)
if entity:
instance.entity = entity
instance.save()
logger.info(f"Successfully created entity on retry {retry_count + 1}")
# Now trigger account creation
async_task(
"inventory.tasks.create_coa_accounts",
dealer_id=dealer_id
)
except Exception as e:
logger.error(f"Retry {retry_count + 1} failed: {e}")
# Schedule another retry
async_task(
"inventory.tasks.retry_entity_creation",
dealer_id=dealer_id,
retry_count=retry_count + 1
)
# def create_coa_accounts(**kwargs):
# logger.info("creating all accounts are created")
# instance = kwargs.get("dealer")
# logger.info(f"Dealer Instance : {instance}")
# entity = instance.entity
# coa = entity.get_default_coa()
# logger.info("Creating default accounts")
# for account_data in get_accounts_data():
# logger.info(f"Creating account: {account_data['code']}")
# create_account(entity, coa, account_data)
# def create_coa_accounts1(pk): # def create_coa_accounts1(pk):

View File

@ -494,13 +494,8 @@ def po_item_formset_table(context, po_model, itemtxs_formset, user):
@register.inclusion_tag("bill/tags/bill_item_formset.html", takes_context=True) @register.inclusion_tag("bill/tags/bill_item_formset.html", takes_context=True)
def bill_item_formset_table(context, item_formset): def bill_item_formset_table(context, item_formset):
bill = BillModel.objects.get(uuid=context["view"].kwargs["bill_pk"]) bill = BillModel.objects.get(uuid=context["view"].kwargs["bill_pk"])
for form in item_formset.forms:
form.fields["item_model"].queryset = form.fields["item_model"].queryset.exclude(
item_role="product"
)
for item in item_formset: for item in item_formset:
if item: if item:
print(item.fields["item_model"])
item.initial["quantity"] = item.instance.po_quantity item.initial["quantity"] = item.instance.po_quantity
item.initial["unit_cost"] = item.instance.po_unit_cost item.initial["unit_cost"] = item.instance.po_unit_cost
# print(item.instance.po_quantity) # print(item.instance.po_quantity)

View File

@ -15,7 +15,6 @@ from django.utils import timezone
from django.db import transaction from django.db import transaction
from django_ledger.io import roles from django_ledger.io import roles
from django.contrib import messages from django.contrib import messages
from django.db import IntegrityError
from django.shortcuts import redirect from django.shortcuts import redirect
from django_q.tasks import async_task from django_q.tasks import async_task
from django.core.mail import send_mail from django.core.mail import send_mail
@ -2387,19 +2386,7 @@ def get_accounts_data():
def create_account(entity, coa, account_data): def create_account(entity, coa, account_data):
"""
Create account with proper validation and error handling
"""
try: try:
# Check if account already exists
existing_account = entity.get_all_accounts().filter(
code=account_data["code"]
).first()
if existing_account:
logger.info(f"Account already exists: {account_data['code']}")
return True
account = entity.create_account( account = entity.create_account(
coa_model=coa, coa_model=coa,
code=account_data["code"], code=account_data["code"],
@ -2408,37 +2395,11 @@ def create_account(entity, coa, account_data):
balance_type=_(account_data["balance_type"]), balance_type=_(account_data["balance_type"]),
active=True, active=True,
) )
account.role_default = account_data["default"]
if account: account.save()
account.role_default = account_data["default"] logger.info(f"Created default account: {account}")
account.save()
logger.info(f"Successfully created account: {account_data['code']}")
return True
except IntegrityError:
logger.warning(f"Account {account_data['code']} already exists (IntegrityError)")
return True
except Exception as e: except Exception as e:
logger.error(f"Error creating account {account_data['code']}: {e}") logger.error(f"Error creating default account: {account_data['code']}, {e}")
return False
return False
# def create_account(entity, coa, account_data):
# try:
# account = entity.create_account(
# coa_model=coa,
# code=account_data["code"],
# name=account_data["name"],
# role=account_data["role"],
# balance_type=_(account_data["balance_type"]),
# active=True,
# )
# logger.info(f"Created account: {account}")
# account.role_default = account_data["default"]
# account.save()
# logger.info(f"Created default account: {account}")
# except Exception as e:
# logger.error(f"Error creating default account: {account_data['code']}, {e}")
def get_or_generate_car_image(car): def get_or_generate_car_image(car):
@ -2503,9 +2464,9 @@ def force_regenerate_car_image(car):
class CarImageAPIClient: class CarImageAPIClient:
"""Simple client to handle authenticated requests to the car image API""" """Simple client to handle authenticated requests to the car image API"""
BASE_URL = settings.TENHAL_IMAGE_GENERATOR_URL BASE_URL = "http://10.10.1.111:8888"
USERNAME = settings.TENHAL_IMAGE_GENERATOR_USERNAME USERNAME = "faheed"
PASSWORD = settings.TENHAL_IMAGE_GENERATOR_PASSWORD PASSWORD = "Tenhal@123"
def __init__(self): def __init__(self):
self.session = None self.session = None
@ -2568,7 +2529,7 @@ class CarImageAPIClient:
"make": payload["make"], "make": payload["make"],
"model": payload["model"], "model": payload["model"],
"exterior_color": payload["color"], "exterior_color": payload["color"],
"angle": "front three-quarter", "angle": "3/4 rear",
"reference_image": "", "reference_image": "",
} }

View File

@ -1,156 +1,132 @@
annotated-types==0.7.0 annotated-types
anyio==4.9.0 anyio
arrow==1.3.0 arrow
asgiref==3.9.1 asgiref
attrs==25.3.0 attrs
autobahn==24.4.2 Babel
Automat==25.4.16 beautifulsoup4
Babel==2.15.0 blessed
beautifulsoup4==4.13.4 cattrs
blessed==1.21.0 certifi
cattrs==25.1.1 cffi
certifi==2025.7.9 charset-normalizer
cffi==1.17.1 click
channels==4.2.2 colorama
charset-normalizer==3.4.2 crispy-bootstrap5
click==8.2.1 cryptography
colorama==0.4.6 cssbeautifier
constantly==23.10.4 defusedxml
crispy-bootstrap5==2025.6 diff-match-patch
cryptography==45.0.5 distro
cssbeautifier==1.15.4 Django
daphne==4.2.1 django-allauth
defusedxml==0.7.1 django-appconf
diff-match-patch==20241021 django-appointment
distro==1.9.0 django-background-tasks
Django==5.2.4 django-bootstrap5
django-allauth==65.10.0 django-ckeditor
django-appconf==1.1.0 django-cors-headers
django-appointment==3.8.0 django-countries
django-background-tasks==1.2.8 django-crispy-forms
django-bootstrap5==25.1 django-debug-toolbar
django-ckeditor==6.7.3 django-easy-audit
django-cors-headers==4.7.0 django-extensions
django-countries==7.6.1 django-filter
django-crispy-forms==2.4 django-imagekit
django-debug-toolbar==5.2.0 django-import-export
django-easy-audit==1.3.7 django-js-asset
django-extensions==4.1 django-ledger
django-filter==25.1 django-manager-utils
django-imagekit==5.0.0 django-next-url-mixin
django-import-export==4.3.8 django-ordered-model
django-js-asset==3.1.2 django-phonenumber-field
django-ledger==0.7.6.1 django-picklefield
django-manager-utils==3.1.5 django-plans
django-next-url-mixin==0.4.0 django-q2
django-ordered-model==3.7.4 django-query-builder
django-phonenumber-field==8.0.0 django-schema-graph
django-picklefield==3.3 django-sequences
django-plans==2.0.0 django-tables2
django-prometheus==2.4.1 django-treebeard
django-q2==1.8.0 django-widget-tweaks
django-query-builder==3.2.0 djangorestframework
django-schema-graph==3.1.0 djhtml
django-sequences==3.0 djlint
django-tables2==2.7.5 docopt
django-treebeard==4.7.1 EditorConfig
django-widget-tweaks==1.5.0 Faker
djangorestframework==3.16.0 fleming
djhtml==3.0.8 fonttools
djlint==1.36.4 fpdf
docopt==0.6.2 fpdf2
EditorConfig==0.17.1 greenlet
Faker==37.4.0 h11
fleming==0.7.0 httpcore
fonttools==4.58.5 httpx
# fpdf==1.7.2 icalendar
fpdf2==2.8.3 idna
greenlet==3.2.3 jiter
gunicorn==23.0.0 jsbeautifier
h11==0.16.0 json5
h2==4.2.0 jsonpatch
hpack==4.1.0 jsonpointer
httpcore==1.0.9 jwt
httpx==0.28.1 langchain
hyperframe==6.1.0 langchain-core
hyperlink==21.0.0 langchain-ollama
icalendar==6.3.1 langchain-text-splitters
idna==3.10 langsmith
incremental==24.7.2 luhnchecker
jiter==0.10.0 Markdown
jsbeautifier==1.15.4 markdown-it-py
json5==0.12.0 mdurl
jsonpatch==1.33 num2words
jsonpointer==3.0.0 numpy
jwt==1.4.0 ofxtools
langchain==0.3.26 ollama
langchain-core==0.3.68 openai
langchain-ollama==0.3.4 opencv-python
langchain-text-splitters==0.3.8 orjson
langsmith==0.4.4 packaging
luhnchecker==0.0.12 pandas
Markdown==3.8.2 pathspec
markdown-it-py==3.0.0 phonenumbers
mdurl==0.1.2 pilkit
num2words==0.5.14 pillow
numpy==2.3.1 psycopg2-binary
ofxtools==0.9.5 pycparser
ollama==0.5.1 pydantic
openai==1.93.3 pydantic_core
opencv-python==4.11.0.86 Pygments
orjson==3.10.18 python-dateutil
packaging==24.2 python-slugify
pandas==2.3.1 python-stdnum
pathspec==0.12.1 pytz
phonenumbers==8.13.42 pyvin
pilkit==3.0 PyYAML
pillow==10.4.0 pyzbar
priority==1.3.0 redis
prometheus_client==0.22.1 regex
psycopg2-binary==2.9.10 requests
pyasn1==0.6.1 requests-toolbelt
pyasn1_modules==0.4.2 rich
pycparser==2.22 ruff
pydantic==2.11.7 setuptools
pydantic_core==2.33.2 six
Pygments==2.19.2 sniffio
pyOpenSSL==25.1.0 soupsieve
python-dateutil==2.9.0.post0 SQLAlchemy
python-dotenv==1.1.1 sqlparse
python-slugify==8.0.4 suds
python-stdnum==2.1 swapper
pytz==2025.2 tablib
pyvin==0.0.2 tenacity
PyYAML==6.0.2 text-unidecode
pyzbar==0.1.9 tqdm
redis==6.2.0 types-python-dateutil
regex==2024.11.6 typing-inspection
requests==2.32.4 typing_extensions
requests-toolbelt==1.0.0 tzdata
rich==14.0.0 urllib3
ruff==0.12.2 wcwidth
service-identity==24.2.0 zstandard
setuptools==80.9.0
six==1.17.0
sniffio==1.3.1
soupsieve==2.7
SQLAlchemy==2.0.41
sqlparse==0.5.3
suds==1.2.0
swapper==1.3.0
tablib==3.8.0
tenacity==9.1.2
text-unidecode==1.3
tqdm==4.67.1
Twisted==25.5.0
txaio==25.6.1
types-python-dateutil==2.9.0.20250708
typing-inspection==0.4.1
typing_extensions==4.14.1
tzdata==2025.2
urllib3==2.5.0
uvicorn==0.35.0
uvicorn-worker==0.3.0
wcwidth==0.2.13
zope.interface==7.2
zstandard==0.23.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 KiB

View File

@ -2,9 +2,19 @@
{% load static %} {% load static %}
{% load django_ledger %} {% load django_ledger %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% if bill.get_itemtxs_data.1.total_amount__sum > 0 %}
<form id="bill-update-form" <form id="bill-update-form"
action="{% url 'bill-update-items' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill_pk %}" action="{% url 'bill-update-items' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill_pk %}"
method="post"> method="post">
{% else %}
<form id="bill-update-form"
hx-trigger="load delay:300ms"
hx-swap="outerHTML"
hx-target="#bill-update-form"
hx-select="#bill-update-form"
hx-post="{% url 'bill-update-items' dealer_slug=dealer_slug entity_slug=entity_slug bill_pk=bill_pk %}"
method="post">
{% endif %}
<div class="container-fluid py-4"> <div class="container-fluid py-4">
<!-- Page Header --> <!-- Page Header -->
<div class="row mb-4"> <div class="row mb-4">
@ -115,13 +125,13 @@
<div class="row mt-4"> <div class="row mt-4">
<div class="col-12"> <div class="col-12">
<div class="d-flex justify-content-start gap-2"> <div class="d-flex justify-content-start gap-2">
{% comment %} {% if not item_formset.has_po %} {% if not item_formset.has_po %}
<a href="{% url 'django_ledger:product-create' entity_slug=entity_slug %}" <a href="{% url 'django_ledger:product-create' entity_slug=entity_slug %}"
class="btn btn-phoenix-primary"> class="btn btn-phoenix-primary">
<i class="fas fa-plus me-1"></i> <i class="fas fa-plus me-1"></i>
{% trans 'New Item' %} {% trans 'New Item' %}
</a> </a>
{% endif %} {% endcomment %} {% endif %}
<button type="submit" class="btn btn-phoenix-primary"> <button type="submit" class="btn btn-phoenix-primary">
<i class="fas fa-save me-1"></i> <i class="fas fa-save me-1"></i>
{% trans 'Save Changes' %} {% trans 'Save Changes' %}

View File

@ -50,15 +50,15 @@
{% trans 'Filters' %} <i class="fas fa-sliders-h ms-2"></i> {% trans 'Filters' %} <i class="fas fa-sliders-h ms-2"></i>
</h2> </h2>
<form method="GET" class="row g-3 align-items-end"> <form method="GET" class="row g-3 align-items-end">
<div class="col-md-4"> <div class="col-md-3">
<label for="start_date" class="form-label">{% trans 'Start Date' %}</label> <label for="start_date" class="form-label">{% trans 'Start Date' %}</label>
<input type="date" class="form-control" id="start_date" name="start_date" value="{{ start_date|default_if_none:'' }}"> <input type="date" class="form-control" id="start_date" name="start_date" value="{{ start_date|default_if_none:'' }}">
</div> </div>
<div class="col-md-4"> <div class="col-md-3">
<label for="end_date" class="form-label">{% trans 'End Date' %}</label> <label for="end_date" class="form-label">{% trans 'End Date' %}</label>
<input type="date" class="form-control" id="end_date" name="end_date" value="{{ end_date|default_if_none:'' }}"> <input type="date" class="form-control" id="end_date" name="end_date" value="{{ end_date|default_if_none:'' }}">
</div> </div>
<div class="col-md-4"> <div class="col-md-2">
<button type="submit" class="btn btn-primary w-100"> <button type="submit" class="btn btn-primary w-100">
<i class="fas fa-filter me-2"></i>{% trans 'Filter' %} <i class="fas fa-filter me-2"></i>{% trans 'Filter' %}
</button> </button>