Compare commits
7 Commits
86792930f0
...
ee8d8cf287
| Author | SHA1 | Date | |
|---|---|---|---|
| ee8d8cf287 | |||
| a079ceec6d | |||
| 05b18e9da3 | |||
| f0ab5d1888 | |||
| 2fb00fadaf | |||
| 1a647442c4 | |||
| e1364d4f5a |
@ -6,17 +6,64 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def check_create_coa_accounts(task):
|
def check_create_coa_accounts(task):
|
||||||
logger.info("Checking if all accounts are created")
|
"""
|
||||||
instance = task.kwargs["dealer"]
|
Hook to verify account creation and handle failures
|
||||||
entity = instance.entity
|
"""
|
||||||
coa = entity.get_default_coa()
|
if task.success:
|
||||||
|
logger.info("Account creation task completed successfully")
|
||||||
|
return
|
||||||
|
|
||||||
for account_data in get_accounts_data():
|
logger.warning("Account creation task failed, checking status...")
|
||||||
if entity.get_all_accounts().filter(code=account_data["code"]).exists():
|
|
||||||
logger.info(f"Default account already exists: {account_data['code']}")
|
try:
|
||||||
continue
|
dealer_id = task.kwargs.get('dealer_id')
|
||||||
logger.info(f"Default account does not exist: {account_data['code']}")
|
if not dealer_id:
|
||||||
create_account(entity, coa, account_data)
|
logger.error("No dealer_id in task kwargs")
|
||||||
|
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):
|
||||||
|
|||||||
@ -685,7 +685,7 @@ class Car(Base):
|
|||||||
)
|
)
|
||||||
#
|
#
|
||||||
additional_services = models.ManyToManyField(
|
additional_services = models.ManyToManyField(
|
||||||
AdditionalServices, related_name="additionals", blank=True, null=True
|
AdditionalServices, related_name="additionals"
|
||||||
)
|
)
|
||||||
cost_price = models.DecimalField(
|
cost_price = models.DecimalField(
|
||||||
max_digits=14,
|
max_digits=14,
|
||||||
@ -1711,7 +1711,7 @@ class Customer(models.Model):
|
|||||||
national_id = models.CharField(
|
national_id = models.CharField(
|
||||||
max_length=10, unique=True, verbose_name=_("National ID"), null=True, blank=True
|
max_length=10, unique=True, verbose_name=_("National ID"), null=True, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
phone_number = models.CharField(
|
phone_number = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_("Phone Number"),
|
verbose_name=_("Phone Number"),
|
||||||
@ -3307,6 +3307,7 @@ class CustomGroup(models.Model):
|
|||||||
"payment",
|
"payment",
|
||||||
"vendor",
|
"vendor",
|
||||||
"additionalservices",
|
"additionalservices",
|
||||||
|
'customer'
|
||||||
],
|
],
|
||||||
other_perms=[
|
other_perms=[
|
||||||
"view_car",
|
"view_car",
|
||||||
|
|||||||
@ -135,55 +135,101 @@ 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:
|
||||||
entity_name = instance.user.dealer.name
|
try:
|
||||||
entity = EntityModel.create_entity(
|
# Use transaction to ensure atomicity
|
||||||
name=entity_name,
|
with transaction.atomic():
|
||||||
admin=instance.user,
|
entity_name = instance.user.dealer.name
|
||||||
use_accrual_method=True,
|
entity = EntityModel.create_entity(
|
||||||
fy_start_month=1,
|
name=entity_name,
|
||||||
)
|
admin=instance.user,
|
||||||
|
use_accrual_method=True,
|
||||||
|
fy_start_month=1,
|
||||||
|
)
|
||||||
|
|
||||||
if entity:
|
if entity:
|
||||||
instance.entity = entity
|
instance.entity = entity
|
||||||
instance.save()
|
instance.save(update_fields=['entity'])
|
||||||
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
|
# Create COA synchronously first
|
||||||
|
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=instance,
|
dealer_id=instance.id, # Pass ID instead of object
|
||||||
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)
|
||||||
|
|||||||
@ -62,14 +62,117 @@ def create_settings(pk):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_coa_accounts(**kwargs):
|
def create_coa_accounts(dealer_id, **kwargs):
|
||||||
logger.info("creating all accounts are created")
|
"""
|
||||||
instance = kwargs.get("dealer")
|
Create COA accounts with retry logic and proper error handling
|
||||||
entity = instance.entity
|
"""
|
||||||
coa = entity.get_default_coa()
|
from .models import Dealer
|
||||||
|
from .utils import create_account, get_accounts_data
|
||||||
|
|
||||||
for account_data in get_accounts_data():
|
max_retries = 3
|
||||||
create_account(entity, coa, account_data)
|
retry_delay = 2 # seconds
|
||||||
|
|
||||||
|
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):
|
||||||
|
|||||||
@ -494,8 +494,13 @@ 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 item in item_formset:
|
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:
|
||||||
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)
|
||||||
|
|||||||
@ -15,6 +15,7 @@ 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
|
||||||
@ -2386,7 +2387,19 @@ 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"],
|
||||||
@ -2395,11 +2408,37 @@ 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"]
|
|
||||||
account.save()
|
if account:
|
||||||
logger.info(f"Created default account: {account}")
|
account.role_default = account_data["default"]
|
||||||
|
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 default account: {account_data['code']}, {e}")
|
logger.error(f"Error creating 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):
|
||||||
@ -2464,9 +2503,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 = "http://10.10.1.111:8888"
|
BASE_URL = settings.TENHAL_IMAGE_GENERATOR_URL
|
||||||
USERNAME = "faheed"
|
USERNAME = settings.TENHAL_IMAGE_GENERATOR_USERNAME
|
||||||
PASSWORD = "Tenhal@123"
|
PASSWORD = settings.TENHAL_IMAGE_GENERATOR_PASSWORD
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.session = None
|
self.session = None
|
||||||
@ -2529,7 +2568,7 @@ class CarImageAPIClient:
|
|||||||
"make": payload["make"],
|
"make": payload["make"],
|
||||||
"model": payload["model"],
|
"model": payload["model"],
|
||||||
"exterior_color": payload["color"],
|
"exterior_color": payload["color"],
|
||||||
"angle": "3/4 rear",
|
"angle": "front three-quarter",
|
||||||
"reference_image": "",
|
"reference_image": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,132 +1,156 @@
|
|||||||
annotated-types
|
annotated-types==0.7.0
|
||||||
anyio
|
anyio==4.9.0
|
||||||
arrow
|
arrow==1.3.0
|
||||||
asgiref
|
asgiref==3.9.1
|
||||||
attrs
|
attrs==25.3.0
|
||||||
Babel
|
autobahn==24.4.2
|
||||||
beautifulsoup4
|
Automat==25.4.16
|
||||||
blessed
|
Babel==2.15.0
|
||||||
cattrs
|
beautifulsoup4==4.13.4
|
||||||
certifi
|
blessed==1.21.0
|
||||||
cffi
|
cattrs==25.1.1
|
||||||
charset-normalizer
|
certifi==2025.7.9
|
||||||
click
|
cffi==1.17.1
|
||||||
colorama
|
channels==4.2.2
|
||||||
crispy-bootstrap5
|
charset-normalizer==3.4.2
|
||||||
cryptography
|
click==8.2.1
|
||||||
cssbeautifier
|
colorama==0.4.6
|
||||||
defusedxml
|
constantly==23.10.4
|
||||||
diff-match-patch
|
crispy-bootstrap5==2025.6
|
||||||
distro
|
cryptography==45.0.5
|
||||||
Django
|
cssbeautifier==1.15.4
|
||||||
django-allauth
|
daphne==4.2.1
|
||||||
django-appconf
|
defusedxml==0.7.1
|
||||||
django-appointment
|
diff-match-patch==20241021
|
||||||
django-background-tasks
|
distro==1.9.0
|
||||||
django-bootstrap5
|
Django==5.2.4
|
||||||
django-ckeditor
|
django-allauth==65.10.0
|
||||||
django-cors-headers
|
django-appconf==1.1.0
|
||||||
django-countries
|
django-appointment==3.8.0
|
||||||
django-crispy-forms
|
django-background-tasks==1.2.8
|
||||||
django-debug-toolbar
|
django-bootstrap5==25.1
|
||||||
django-easy-audit
|
django-ckeditor==6.7.3
|
||||||
django-extensions
|
django-cors-headers==4.7.0
|
||||||
django-filter
|
django-countries==7.6.1
|
||||||
django-imagekit
|
django-crispy-forms==2.4
|
||||||
django-import-export
|
django-debug-toolbar==5.2.0
|
||||||
django-js-asset
|
django-easy-audit==1.3.7
|
||||||
django-ledger
|
django-extensions==4.1
|
||||||
django-manager-utils
|
django-filter==25.1
|
||||||
django-next-url-mixin
|
django-imagekit==5.0.0
|
||||||
django-ordered-model
|
django-import-export==4.3.8
|
||||||
django-phonenumber-field
|
django-js-asset==3.1.2
|
||||||
django-picklefield
|
django-ledger==0.7.6.1
|
||||||
django-plans
|
django-manager-utils==3.1.5
|
||||||
django-q2
|
django-next-url-mixin==0.4.0
|
||||||
django-query-builder
|
django-ordered-model==3.7.4
|
||||||
django-schema-graph
|
django-phonenumber-field==8.0.0
|
||||||
django-sequences
|
django-picklefield==3.3
|
||||||
django-tables2
|
django-plans==2.0.0
|
||||||
django-treebeard
|
django-prometheus==2.4.1
|
||||||
django-widget-tweaks
|
django-q2==1.8.0
|
||||||
djangorestframework
|
django-query-builder==3.2.0
|
||||||
djhtml
|
django-schema-graph==3.1.0
|
||||||
djlint
|
django-sequences==3.0
|
||||||
docopt
|
django-tables2==2.7.5
|
||||||
EditorConfig
|
django-treebeard==4.7.1
|
||||||
Faker
|
django-widget-tweaks==1.5.0
|
||||||
fleming
|
djangorestframework==3.16.0
|
||||||
fonttools
|
djhtml==3.0.8
|
||||||
fpdf
|
djlint==1.36.4
|
||||||
fpdf2
|
docopt==0.6.2
|
||||||
greenlet
|
EditorConfig==0.17.1
|
||||||
h11
|
Faker==37.4.0
|
||||||
httpcore
|
fleming==0.7.0
|
||||||
httpx
|
fonttools==4.58.5
|
||||||
icalendar
|
# fpdf==1.7.2
|
||||||
idna
|
fpdf2==2.8.3
|
||||||
jiter
|
greenlet==3.2.3
|
||||||
jsbeautifier
|
gunicorn==23.0.0
|
||||||
json5
|
h11==0.16.0
|
||||||
jsonpatch
|
h2==4.2.0
|
||||||
jsonpointer
|
hpack==4.1.0
|
||||||
jwt
|
httpcore==1.0.9
|
||||||
langchain
|
httpx==0.28.1
|
||||||
langchain-core
|
hyperframe==6.1.0
|
||||||
langchain-ollama
|
hyperlink==21.0.0
|
||||||
langchain-text-splitters
|
icalendar==6.3.1
|
||||||
langsmith
|
idna==3.10
|
||||||
luhnchecker
|
incremental==24.7.2
|
||||||
Markdown
|
jiter==0.10.0
|
||||||
markdown-it-py
|
jsbeautifier==1.15.4
|
||||||
mdurl
|
json5==0.12.0
|
||||||
num2words
|
jsonpatch==1.33
|
||||||
numpy
|
jsonpointer==3.0.0
|
||||||
ofxtools
|
jwt==1.4.0
|
||||||
ollama
|
langchain==0.3.26
|
||||||
openai
|
langchain-core==0.3.68
|
||||||
opencv-python
|
langchain-ollama==0.3.4
|
||||||
orjson
|
langchain-text-splitters==0.3.8
|
||||||
packaging
|
langsmith==0.4.4
|
||||||
pandas
|
luhnchecker==0.0.12
|
||||||
pathspec
|
Markdown==3.8.2
|
||||||
phonenumbers
|
markdown-it-py==3.0.0
|
||||||
pilkit
|
mdurl==0.1.2
|
||||||
pillow
|
num2words==0.5.14
|
||||||
psycopg2-binary
|
numpy==2.3.1
|
||||||
pycparser
|
ofxtools==0.9.5
|
||||||
pydantic
|
ollama==0.5.1
|
||||||
pydantic_core
|
openai==1.93.3
|
||||||
Pygments
|
opencv-python==4.11.0.86
|
||||||
python-dateutil
|
orjson==3.10.18
|
||||||
python-slugify
|
packaging==24.2
|
||||||
python-stdnum
|
pandas==2.3.1
|
||||||
pytz
|
pathspec==0.12.1
|
||||||
pyvin
|
phonenumbers==8.13.42
|
||||||
PyYAML
|
pilkit==3.0
|
||||||
pyzbar
|
pillow==10.4.0
|
||||||
redis
|
priority==1.3.0
|
||||||
regex
|
prometheus_client==0.22.1
|
||||||
requests
|
psycopg2-binary==2.9.10
|
||||||
requests-toolbelt
|
pyasn1==0.6.1
|
||||||
rich
|
pyasn1_modules==0.4.2
|
||||||
ruff
|
pycparser==2.22
|
||||||
setuptools
|
pydantic==2.11.7
|
||||||
six
|
pydantic_core==2.33.2
|
||||||
sniffio
|
Pygments==2.19.2
|
||||||
soupsieve
|
pyOpenSSL==25.1.0
|
||||||
SQLAlchemy
|
python-dateutil==2.9.0.post0
|
||||||
sqlparse
|
python-dotenv==1.1.1
|
||||||
suds
|
python-slugify==8.0.4
|
||||||
swapper
|
python-stdnum==2.1
|
||||||
tablib
|
pytz==2025.2
|
||||||
tenacity
|
pyvin==0.0.2
|
||||||
text-unidecode
|
PyYAML==6.0.2
|
||||||
tqdm
|
pyzbar==0.1.9
|
||||||
types-python-dateutil
|
redis==6.2.0
|
||||||
typing-inspection
|
regex==2024.11.6
|
||||||
typing_extensions
|
requests==2.32.4
|
||||||
tzdata
|
requests-toolbelt==1.0.0
|
||||||
urllib3
|
rich==14.0.0
|
||||||
wcwidth
|
ruff==0.12.2
|
||||||
zstandard
|
service-identity==24.2.0
|
||||||
|
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.
|
After Width: | Height: | Size: 493 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 470 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 418 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 450 KiB |
@ -2,19 +2,9 @@
|
|||||||
{% 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">
|
||||||
@ -125,13 +115,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">
|
||||||
{% if not item_formset.has_po %}
|
{% comment %} {% 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 %}
|
{% endif %} {% endcomment %}
|
||||||
<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' %}
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-select-oob="#toast-container"
|
hx-select-oob="#toast-container"
|
||||||
hx-indicator="#spinner">
|
hx-indicator="#spinner">
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% comment %} <p class="navbar-vertical-label text-primary fs-8 text-truncate">{{request.dealer|default:"Apps"}}</p>
|
{% comment %} <p class="navbar-vertical-label text-primary fs-8 text-truncate">{{request.dealer|default:"Apps"}}</p>
|
||||||
<hr class="navbar-vertical-line"> {% endcomment %}
|
<hr class="navbar-vertical-line"> {% endcomment %}
|
||||||
@ -440,7 +440,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div> {% endcomment %}
|
</div> {% endcomment %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -450,10 +450,10 @@
|
|||||||
<a class="nav-link ps-2" href="{% if request.is_dealer%}{% url 'ticket_list' request.dealer.slug %} {% else %}#{%endif%}">
|
<a class="nav-link ps-2" href="{% if request.is_dealer%}{% url 'ticket_list' request.dealer.slug %} {% else %}#{%endif%}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
{% if user.is_authenticated%}
|
{% if user.is_authenticated%}
|
||||||
|
|
||||||
<span class="nav-link-icon"><span class="fa-solid fa-gear me-1 fs-7"></span></span>
|
<span class="nav-link-icon"><span class="fa-solid fa-gear me-1 fs-7"></span></span>
|
||||||
<span class="nav-link-text">{{ request.dealer.user.username }}</span>
|
<span class="nav-link-text">{{ request.dealer.user.username }}</span>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -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-3">
|
<div class="col-md-4">
|
||||||
<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-3">
|
<div class="col-md-4">
|
||||||
<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-2">
|
<div class="col-md-4">
|
||||||
<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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user