From 7142975004ef1e6773a7b8457564add8da17a237 Mon Sep 17 00:00:00 2001 From: gitea Date: Mon, 27 Jan 2025 15:41:30 +0000 Subject: [PATCH] sale order --- car_inventory/urls.py | 2 +- inventory/forms.py | 7 +- ...08_saleorder_estimate_saleorder_invoice.py | 25 + .../0009_saleorder_formatted_order_id.py | 19 + ..._car_remove_saleorder_customer_and_more.py | 25 + .../0011_alter_saleorder_estimate.py | 21 + inventory/mixins.py | 2 +- inventory/models.py | 49 +- inventory/templatetags/custom_filters.py | 117 ++- inventory/templatetags/tenhal_tag.py | 908 ++++++++++++++++++ inventory/urls.py | 24 + inventory/utils.py | 229 +++-- inventory/views.py | 74 +- scripts/report.py | 9 + scripts/run.py | 70 ++ templates/base.html | 1 + templates/header.html | 7 +- templates/ledger/reports/balance_sheet.html | 54 ++ .../reports/components/period_navigator.html | 73 ++ .../ledger/reports/income_statement.html | 0 .../reports/tags/balance_sheet_statement.html | 140 +++ .../sales/estimates/estimate_detail.html | 3 + .../sales/estimates/sale_order_form.html | 157 +++ ...ale_order.html => sale_order_preview.html} | 65 +- templates/sales/orders/order_list.html | 48 + 25 files changed, 1984 insertions(+), 145 deletions(-) create mode 100644 inventory/migrations/0008_saleorder_estimate_saleorder_invoice.py create mode 100644 inventory/migrations/0009_saleorder_formatted_order_id.py create mode 100644 inventory/migrations/0010_remove_saleorder_car_remove_saleorder_customer_and_more.py create mode 100644 inventory/migrations/0011_alter_saleorder_estimate.py create mode 100644 inventory/templatetags/tenhal_tag.py create mode 100644 scripts/report.py create mode 100644 scripts/run.py create mode 100644 templates/ledger/reports/balance_sheet.html create mode 100644 templates/ledger/reports/components/period_navigator.html create mode 100644 templates/ledger/reports/income_statement.html create mode 100644 templates/ledger/reports/tags/balance_sheet_statement.html create mode 100644 templates/sales/estimates/sale_order_form.html rename templates/sales/estimates/{sale_order.html => sale_order_preview.html} (83%) create mode 100644 templates/sales/orders/order_list.html diff --git a/car_inventory/urls.py b/car_inventory/urls.py index dafe4c8a..e07626dc 100644 --- a/car_inventory/urls.py +++ b/car_inventory/urls.py @@ -24,7 +24,7 @@ urlpatterns += i18n_patterns( path('ledger/', include('django_ledger.urls', namespace='django_ledger')), path("haikalbot/", include("haikalbot.urls")), path('appointment/', include('appointment.urls')), - path('plans/', include('plans.urls')), + # path('plans/', include('plans.urls')), ) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/inventory/forms.py b/inventory/forms.py index d5dfa247..3664c702 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -230,7 +230,7 @@ class CarUpdateForm(forms.ModelForm, AddClassMixin): # ] -class CarFinanceForm(AddClassMixin, forms.ModelForm): +class CarFinanceForm(forms.ModelForm): additional_finances = forms.ModelMultipleChoiceField( queryset=AdditionalServices.objects.all(), widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}), @@ -689,8 +689,7 @@ class BillModelCreateForm(BillModelCreateFormBase): class SaleOrderForm(forms.ModelForm): class Meta: model = SaleOrder - fields = '__all__' - widgets = { - 'address': forms.Textarea(attrs={'rows': 3}), + fields = ['estimate','payment_method', 'comments'] + widgets = { 'comments': forms.Textarea(attrs={'rows': 3}), } \ No newline at end of file diff --git a/inventory/migrations/0008_saleorder_estimate_saleorder_invoice.py b/inventory/migrations/0008_saleorder_estimate_saleorder_invoice.py new file mode 100644 index 00000000..4fa2cf08 --- /dev/null +++ b/inventory/migrations/0008_saleorder_estimate_saleorder_invoice.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.17 on 2025-01-27 08:29 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), + ('inventory', '0007_alter_cartransfer_status'), + ] + + operations = [ + migrations.AddField( + model_name='saleorder', + name='estimate', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.estimatemodel', verbose_name='Estimate'), + ), + migrations.AddField( + model_name='saleorder', + name='invoice', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.invoicemodel', verbose_name='Invoice'), + ), + ] diff --git a/inventory/migrations/0009_saleorder_formatted_order_id.py b/inventory/migrations/0009_saleorder_formatted_order_id.py new file mode 100644 index 00000000..2287102e --- /dev/null +++ b/inventory/migrations/0009_saleorder_formatted_order_id.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.17 on 2025-01-27 08:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0008_saleorder_estimate_saleorder_invoice'), + ] + + operations = [ + migrations.AddField( + model_name='saleorder', + name='formatted_order_id', + field=models.CharField(default=1, editable=False, max_length=10, unique=True), + preserve_default=False, + ), + ] diff --git a/inventory/migrations/0010_remove_saleorder_car_remove_saleorder_customer_and_more.py b/inventory/migrations/0010_remove_saleorder_car_remove_saleorder_customer_and_more.py new file mode 100644 index 00000000..b52c89af --- /dev/null +++ b/inventory/migrations/0010_remove_saleorder_car_remove_saleorder_customer_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.17 on 2025-01-27 09:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0009_saleorder_formatted_order_id'), + ] + + operations = [ + migrations.RemoveField( + model_name='saleorder', + name='car', + ), + migrations.RemoveField( + model_name='saleorder', + name='customer', + ), + migrations.RemoveField( + model_name='saleorder', + name='trade_in', + ), + ] diff --git a/inventory/migrations/0011_alter_saleorder_estimate.py b/inventory/migrations/0011_alter_saleorder_estimate.py new file mode 100644 index 00000000..0fb4bd20 --- /dev/null +++ b/inventory/migrations/0011_alter_saleorder_estimate.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.17 on 2025-01-27 11:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_ledger', '0017_alter_accountmodel_unique_together_and_more'), + ('inventory', '0010_remove_saleorder_car_remove_saleorder_customer_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='saleorder', + name='estimate', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='sale_orders', to='django_ledger.estimatemodel', verbose_name='Estimate'), + preserve_default=False, + ), + ] diff --git a/inventory/mixins.py b/inventory/mixins.py index b7762382..db90e7d8 100644 --- a/inventory/mixins.py +++ b/inventory/mixins.py @@ -33,7 +33,7 @@ class AddClassMixin: """ field = super().__getitem__(name) wrapper_class = field.field.widget.attrs.pop('wrapper_class', None) - if wrapper_class: + if wrapper_class: field = forms.utils.safety.mark_safe(f'
{field}
') return field diff --git a/inventory/models.py b/inventory/models.py index a901e9b9..14d6a75c 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -28,7 +28,7 @@ from sqlalchemy.orm.base import object_state from .utilities.financials import get_financial_value, get_total, get_total_financials from django.db.models import FloatField from .mixins import LocalizedNameMixin -from django_ledger.models import EntityModel, ItemModel +from django_ledger.models import EntityModel, ItemModel,EstimateModel,InvoiceModel from django_countries.fields import CountryField from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType @@ -1540,34 +1540,55 @@ class UserActivityLog(models.Model): def __str__(self): return f"{self.user.email} - {self.action} - {self.timestamp}" -class SaleOrder(models.Model): - customer = models.ForeignKey( - Customer, +class SaleOrder(models.Model): + estimate = models.ForeignKey( + EstimateModel, on_delete=models.CASCADE, related_name="sale_orders", - verbose_name=_("Customer"), + verbose_name=_("Estimate") ) - car = models.ForeignKey( - Car, + invoice = models.ForeignKey( + InvoiceModel, on_delete=models.CASCADE, related_name="sale_orders", - verbose_name=_("Car"), + verbose_name=_("Invoice"), + null=True, + blank=True ) payment_method = models.CharField(max_length=20, choices=[ ('cash', 'Cash'), ('finance', 'Finance'), ('lease', 'Lease'), - ]) - trade_in = models.CharField(max_length=100, blank=True, null=True) + ]) comments = models.TextField(blank=True, null=True) - + formatted_order_id = models.CharField(max_length=10, unique=True, editable=False) + + def save(self, *args, **kwargs): + if not self.formatted_order_id: + last_order = SaleOrder.objects.order_by('-id').first() + if last_order: + next_id = last_order.id + 1 + else: + next_id = 1 + self.formatted_order_id = f"{next_id:05d}" + super().save(*args, **kwargs) def __str__(self): - return f"Sale Order for {self.full_name} - {self.make} {self.model}" + return f"Sale Order for {self.full_name}" @property def full_name(self): - return f"{self.customer.first_name} {self.customer.last_name}" + return f"{self.customer.customer_name}" @property def price(self): - return self.car.finances.selling_price \ No newline at end of file + return self.car.finances.selling_price + + @property + def items(self): + if self.estimate.get_itemtxs_data(): + return self.estimate.get_itemtxs_data()[0] + return [] + + @property + def customer(self): + return self.estimate.customer \ No newline at end of file diff --git a/inventory/templatetags/custom_filters.py b/inventory/templatetags/custom_filters.py index c1f06443..fec36bb5 100644 --- a/inventory/templatetags/custom_filters.py +++ b/inventory/templatetags/custom_filters.py @@ -1,8 +1,10 @@ from django import template +from calendar import month_abbr +from django.urls import reverse +from django_ledger.io.io_core import get_localdate register = template.Library() - @register.filter(name='percentage') def percentage(value): if value is not None: @@ -31,3 +33,116 @@ def attr(field, args): attrs[definition.strip()] = True return field.as_widget(attrs=attrs) + +@register.inclusion_tag('ledger/reports/components/period_navigator.html', takes_context=True) +def period_navigation(context, base_url: str): + kwargs = dict() + entity_slug = context['view'].kwargs['entity_slug'] + kwargs['entity_slug'] = entity_slug + + if context['view'].kwargs.get('ledger_pk'): + kwargs['ledger_pk'] = context['view'].kwargs.get('ledger_pk') + + if context['view'].kwargs.get('account_pk'): + kwargs['account_pk'] = context['view'].kwargs.get('account_pk') + + if context['view'].kwargs.get('unit_slug'): + kwargs['unit_slug'] = context['view'].kwargs.get('unit_slug') + + if context['view'].kwargs.get('coa_slug'): + kwargs['coa_slug'] = context['view'].kwargs.get('coa_slug') + + ctx = dict() + ctx['year'] = context['year'] + ctx['has_year'] = context.get('has_year') + ctx['has_quarter'] = context.get('has_quarter') + ctx['has_month'] = context.get('has_month') + ctx['has_date'] = context.get('has_date') + ctx['previous_year'] = context['previous_year'] + + kwargs['year'] = context['previous_year'] + ctx['previous_year_url'] = reverse(f'{base_url}-year', kwargs=kwargs) + ctx['next_year'] = context['next_year'] + + kwargs['year'] = context['next_year'] + ctx['next_year_url'] = reverse(f'{base_url}-year', kwargs=kwargs) + + kwargs['year'] = context['year'] + ctx['current_year_url'] = reverse(f'{base_url}-year', kwargs=kwargs) + + dt = get_localdate() + + KWARGS_CURRENT_MONTH = { + 'entity_slug': context['view'].kwargs['entity_slug'], + 'year': dt.year, + 'month': dt.month + } + + if 'unit_slug' in kwargs: + KWARGS_CURRENT_MONTH['unit_slug'] = kwargs['unit_slug'] + if 'account_pk' in kwargs: + KWARGS_CURRENT_MONTH['account_pk'] = kwargs['account_pk'] + if 'ledger_pk' in kwargs: + KWARGS_CURRENT_MONTH['ledger_pk'] = kwargs['ledger_pk'] + if 'coa_slug' in kwargs: + KWARGS_CURRENT_MONTH['coa_slug'] = kwargs['coa_slug'] + + ctx['current_month_url'] = reverse(f'{base_url}-month', + kwargs=KWARGS_CURRENT_MONTH) + + quarter_urls = list() + ctx['quarter'] = context.get('quarter') + for Q in range(1, 5): + kwargs['quarter'] = Q + quarter_urls.append({ + 'url': reverse(f'{base_url}-quarter', kwargs=kwargs), + 'quarter': Q, + 'quarter_name': f'Q{Q}' + }) + del kwargs['quarter'] + ctx['quarter_urls'] = quarter_urls + + month_urls = list() + ctx['month'] = context.get('month') + for M in range(1, 13): + kwargs['month'] = M + month_urls.append({ + 'url': reverse(f'{base_url}-month', kwargs=kwargs), + 'month': M, + 'month_abbr': month_abbr[M] + }) + ctx['month_urls'] = month_urls + ctx['from_date'] = context['from_date'] + ctx['to_date'] = context['to_date'] + ctx.update(kwargs) + + ctx['date_navigation_url'] = context.get('date_navigation_url') + + return ctx + + +@register.inclusion_tag('ledger/reports/tags/balance_sheet_statement.html', takes_context=True) +def balance_sheet_statement(context, io_model, to_date=None): + user_model = context['user'] + activity = context['request'].GET.get('activity') + entity_slug = context['view'].kwargs.get('entity_slug') + if not to_date: + to_date = context['to_date'] + + io_digest = io_model.digest( + activity=activity, + user_model=user_model, + equity_only=False, + entity_slug=entity_slug, + unit_slug=context['unit_slug'], + by_unit=context['by_unit'], + to_date=to_date, + signs=True, + process_groups=True, + balance_sheet_statement=True) + + return { + 'entity_slug': entity_slug, + 'user_model': user_model, + 'tx_digest': io_digest.get_io_data(), + } diff --git a/inventory/templatetags/tenhal_tag.py b/inventory/templatetags/tenhal_tag.py new file mode 100644 index 00000000..915875a3 --- /dev/null +++ b/inventory/templatetags/tenhal_tag.py @@ -0,0 +1,908 @@ +# """ +# Django Ledger created by Miguel Sanda . +# Copyright© EDMA Group Inc licensed under the GPLv3 Agreement. + +# Contributions to this module: +# Miguel Sanda +# """ + +from calendar import month_abbr +from random import randint + +from django import template +from django.db.models import Sum +from django.urls import reverse +from django.utils.formats import number_format + +from django_ledger import __version__ +from django_ledger.forms.app_filters import EntityFilterForm, ActivityFilterForm +from django_ledger.forms.feedback import BugReportForm, RequestNewFeatureForm +from django_ledger.io import CREDIT, DEBIT, ROLES_ORDER_ALL +from django_ledger.io.io_core import validate_activity, get_localdate +from django_ledger.models import TransactionModel, BillModel, InvoiceModel, EntityUnitModel +from django_ledger.settings import ( + DJANGO_LEDGER_FINANCIAL_ANALYSIS, DJANGO_LEDGER_CURRENCY_SYMBOL, + DJANGO_LEDGER_SPACED_CURRENCY_SYMBOL) +from django_ledger.utils import get_default_entity_session_key, get_end_date_from_session + +register = template.Library() + + +# @register.simple_tag(name='current_version') +# def current_version(): +# return __version__ + + +# @register.simple_tag(name='currency_symbol') +# def currency_symbol(spaced: bool = False): +# if spaced or DJANGO_LEDGER_SPACED_CURRENCY_SYMBOL: +# return f'{DJANGO_LEDGER_CURRENCY_SYMBOL} ' +# return DJANGO_LEDGER_CURRENCY_SYMBOL + + +# @register.filter(name='absolute') +# def absolute(value): +# if value: +# if isinstance(value, str): +# value = float(value) +# return abs(value) + + +# @register.filter(name='reverse_sign') +# def reverse_sign(value): +# if value: +# if isinstance(value, str): +# value = float(value) +# return -value + + +# @register.filter(name='currency_format') +# def currency_format(value): +# if not value: +# value = 0.00 +# return number_format(value, decimal_pos=2, use_l10n=True, force_grouping=True) + + +# @register.filter(name='percentage') +# def percentage(value): +# if value is not None: +# return '{0:,.2f}%'.format(value * 100) + + +# @register.filter(name='last_four') +# def last_four(value: str): +# if value: +# return '*' + value[-4:] +# return '' + + +# @register.inclusion_tag('django_ledger/components/icon.html') +# def icon(icon_name, size): +# return { +# 'icon': icon_name, +# 'size': size +# } + + +# @register.inclusion_tag('django_ledger/financial_statements/tags/balance_sheet_statement.html', takes_context=True) +# def balance_sheet_statement(context, io_model, to_date=None): +# user_model = context['user'] +# activity = context['request'].GET.get('activity') +# entity_slug = context['view'].kwargs.get('entity_slug') + +# if not to_date: +# to_date = context['to_date'] + +# io_digest = io_model.digest( +# activity=activity, +# user_model=user_model, +# equity_only=False, +# entity_slug=entity_slug, +# unit_slug=context['unit_slug'], +# by_unit=context['by_unit'], +# to_date=to_date, +# signs=True, +# process_groups=True, +# balance_sheet_statement=True) + +# return { +# 'entity_slug': entity_slug, +# 'user_model': user_model, +# 'tx_digest': io_digest.get_io_data(), +# } + + +# @register.inclusion_tag('django_ledger/financial_statements/tags/cash_flow_statement.html', takes_context=True) +# def cash_flow_statement(context, io_model): +# user_model = context['user'] +# entity_slug = context['view'].kwargs.get('entity_slug') +# from_date = context['from_date'] +# to_date = context['to_date'] + +# io_digest = io_model.digest( +# cash_flow_statement=True, +# by_activity=True, +# user_model=user_model, +# equity_only=False, +# signs=True, +# entity_slug=entity_slug, +# unit_slug=context['unit_slug'], +# by_unit=context['by_unit'], +# from_date=from_date, +# to_date=to_date, +# process_groups=True) + +# return { +# 'entity_slug': entity_slug, +# 'user_model': user_model, +# 'tx_digest': io_digest.get_io_data() +# } + + +# @register.inclusion_tag('django_ledger/financial_statements/tags/income_statement.html', takes_context=True) +# def income_statement_table(context, io_model, from_date=None, to_date=None): +# user_model = context['user'] +# activity = context['request'].GET.get('activity') +# activity = validate_activity(activity, raise_404=True) +# entity_slug = context['view'].kwargs.get('entity_slug') + +# if not from_date: +# from_date = context['from_date'] +# if not to_date: +# to_date = context['to_date'] + +# io_digest = io_model.digest( +# activity=activity, +# user_model=user_model, +# entity_slug=entity_slug, +# unit_slug=context['unit_slug'], +# by_unit=context['by_unit'], +# from_date=from_date, +# to_date=to_date, +# equity_only=True, +# process_groups=True, +# income_statement=True, +# signs=True +# ) + +# return { +# 'entity_slug': entity_slug, +# 'user_model': user_model, +# 'tx_digest': io_digest.get_io_data() +# } + + +# @register.inclusion_tag('django_ledger/bank_account/tags/bank_accounts_table.html', takes_context=True) +# def bank_account_table(context, bank_account_qs): +# entity_slug = context['view'].kwargs['entity_slug'] +# return { +# 'bank_account_qs': bank_account_qs, +# 'entity_slug': entity_slug +# } + + +# @register.inclusion_tag('django_ledger/data_import/tags/data_import_job_list_table.html', takes_context=True) +# def data_import_job_list_table(context): +# return context + + +# @register.inclusion_tag('django_ledger/data_import/tags/data_import_job_txs_table.html', takes_context=True) +# def data_import_job_txs_pending(context, staged_txs_formset): +# return { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'job_pk': context['view'].kwargs['job_pk'], +# 'staged_txs_formset': staged_txs_formset +# } + + +# @register.inclusion_tag('django_ledger/data_import/tags/data_import_job_txs_imported.html', takes_context=True) +# def data_import_job_txs_imported(context, staged_txs_qs): +# imported_txs = [stx for stx in staged_txs_qs if stx.is_imported()] +# return { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'job_pk': context['view'].kwargs['job_pk'], +# 'imported_txs': imported_txs +# } + + +# @register.inclusion_tag('django_ledger/journal_entry/tags/je_table.html', takes_context=True) +# def jes_table(context, journal_entry_qs, next_url=None): +# entity_slug = context['view'].kwargs['entity_slug'] +# ledger_pk = context['view'].kwargs['ledger_pk'] +# if not next_url: +# next_url = reverse('django_ledger:je-list', +# kwargs={ +# 'entity_slug': entity_slug, +# 'ledger_pk': ledger_pk +# }) +# return { +# 'jes': journal_entry_qs, +# 'entity_slug': entity_slug, +# 'ledger_pk': ledger_pk, +# 'next_url': next_url +# } + + +# @register.inclusion_tag('django_ledger/journal_entry/tags/je_txs_table.html') +# def journal_entry_txs_table(journal_entry_model, style='detail'): +# txs_queryset = journal_entry_model.transactionmodel_set.all().select_related('account').order_by('account__code') +# total_credits = sum(tx.amount for tx in txs_queryset if tx.tx_type == 'credit') +# total_debits = sum(tx.amount for tx in txs_queryset if tx.tx_type == 'debit') +# return { +# 'txs': txs_queryset, +# 'total_debits': total_debits, +# 'total_credits': total_credits, +# 'style': style +# } + + +# @register.inclusion_tag('django_ledger/journal_entry/tags/je_txs_table.html', takes_context=True) +# def bill_txs_table(context, bill_model: BillModel): +# # todo: move this to bill model... +# txs_queryset = TransactionModel.objects.for_bill( +# bill_model=bill_model.uuid, +# user_model=context['request'].user, +# entity_slug=context['view'].kwargs['entity_slug'] +# ).select_related('journal_entry', 'journal_entry__entity_unit', 'account').order_by('-journal_entry__timestamp') +# total_credits = sum(tx.amount for tx in txs_queryset if tx.tx_type == CREDIT) +# total_debits = sum(tx.amount for tx in txs_queryset if tx.tx_type == DEBIT) +# return { +# 'style': 'detail', +# 'txs': txs_queryset, +# 'total_debits': total_debits, +# 'total_credits': total_credits +# } + + +# @register.inclusion_tag('django_ledger/journal_entry/tags/je_txs_table.html', takes_context=True) +# def invoice_txs_table(context, invoice_model: InvoiceModel): +# txs_queryset = TransactionModel.objects.for_invoice( +# invoice_model=invoice_model, +# user_model=context['request'].user, +# entity_slug=context['view'].kwargs['entity_slug'] +# ).select_related('journal_entry', 'journal_entry__entity_unit', 'account').order_by('-journal_entry__timestamp') +# total_credits = sum(tx.amount for tx in txs_queryset if tx.tx_type == CREDIT) +# total_debits = sum(tx.amount for tx in txs_queryset if tx.tx_type == DEBIT) +# return { +# 'style': 'detail', +# 'txs': txs_queryset, +# 'total_debits': total_debits, +# 'total_credits': total_credits +# } + + +# @register.inclusion_tag('django_ledger/ledger/tags/ledgers_table.html', takes_context=True) +# def ledgers_table(context, ledger_model_qs): +# return { +# 'ledgers': ledger_model_qs, +# 'entity_slug': context['view'].kwargs['entity_slug'], +# } + + +# @register.inclusion_tag('django_ledger/invoice/tags/invoice_table.html', takes_context=True) +# def invoice_table(context, invoice_qs): +# return { +# 'invoices': invoice_qs, +# 'entity_slug': context['view'].kwargs['entity_slug'] +# } + + +# @register.inclusion_tag('django_ledger/bills/tags/bill_table.html', takes_context=True) +# def bill_table(context, bill_qs): +# return { +# 'bills': bill_qs, +# 'entity_slug': context['view'].kwargs['entity_slug'] +# } + + +# @register.inclusion_tag('django_ledger/closing_entry/tags/closing_entry_table.html', takes_context=True) +# def closing_entry_table(context, closing_entry_qs): +# return { +# 'closing_entry_list': closing_entry_qs, +# 'entity_slug': context['view'].kwargs['entity_slug'] +# } + + +# @register.inclusion_tag('django_ledger/closing_entry/tags/closing_entry_txs_table.html', takes_context=True) +# def closing_entry_txs_table(context, closing_entry_txs_qs): +# ce_txs_list = list(closing_entry_txs_qs) +# ce_txs_list.sort(key=lambda ce_txs: ROLES_ORDER_ALL.index(ce_txs.account_model.role)) +# return { +# 'ce_txs_list': ce_txs_list, +# 'entity_slug': context['view'].kwargs['entity_slug'] +# } + + +# @register.inclusion_tag('django_ledger/purchase_order/includes/po_table.html', takes_context=True) +# def po_table(context, purchase_order_qs): +# return { +# 'po_list': purchase_order_qs, +# 'entity_slug': context['view'].kwargs['entity_slug'] +# } + + +# @register.inclusion_tag('django_ledger/account/tags/accounts_table.html', takes_context=True) +# def accounts_table(context, accounts_qs, title=None): +# return { +# 'title': title, +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'accounts_gb': accounts_qs.gb_bs_role(), +# } + + +# @register.inclusion_tag('django_ledger/customer/tags/customer_table.html', takes_context=True) +# def customer_table(context): +# return context + + +# @register.inclusion_tag('django_ledger/vendor/tags/vendor_table.html', takes_context=True) +# def vendor_table(context): +# return context + + +# @register.inclusion_tag('django_ledger/account/tags/account_txs_table.html', takes_context=True) +# def account_txs_table(context, txs_qs): +# return { +# 'transactions': txs_qs, +# 'total_credits': sum(tx.amount for tx in txs_qs if tx.tx_type == 'credit'), +# 'total_debits': sum(tx.amount for tx in txs_qs if tx.tx_type == 'debit'), +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'account_pk': context['view'].kwargs['account_pk'] +# } + + +# @register.inclusion_tag('django_ledger/components/breadcrumbs.html', takes_context=True) +# def nav_breadcrumbs(context): +# entity_slug = context['view'].kwargs.get('entity_slug') +# coa_slug = context['view'].kwargs.get('coa_slug') +# ledger_pk = context['view'].kwargs.get('entity_slug') +# account_pk = context['view'].kwargs.get('account_pk') +# return { +# 'entity_slug': entity_slug, +# 'coa_slug': coa_slug, +# 'ledger_pk': ledger_pk, +# 'account_pk': account_pk +# } + + +# @register.inclusion_tag('django_ledger/components/default_entity.html', takes_context=True) +# def default_entity(context): +# user = context['user'] +# session_key = get_default_entity_session_key() +# session = context['request'].session +# session_entity_data = session.get(session_key) +# identity = randint(0, 1000000) +# try: +# entity_uuid = session_entity_data['entity_uuid'] +# default_entity_form = EntityFilterForm( +# user_model=user, +# form_id=identity, +# current_entity_uuid=entity_uuid +# ) +# except TypeError or KeyError: +# default_entity_form = EntityFilterForm( +# user_model=user, +# form_id=identity, +# ) + +# return { +# 'default_entity_form': default_entity_form, +# 'form_id': identity, +# } + + +# @register.simple_tag(takes_context=True) +# def session_entity_name(context, request=None): +# session_key = get_default_entity_session_key() +# if not request: +# request = context.get('request') +# try: +# session = request.session +# entity_name = session.get(session_key)['entity_name'] +# except AttributeError: +# entity_name = 'Django Ledger' +# except KeyError: +# entity_name = 'Django Ledger' +# except TypeError: +# entity_name = 'Django Ledger' +# return entity_name + + +# # todo: rename template to activity_form_filter. +# @register.inclusion_tag('django_ledger/components/activity_form.html', takes_context=True) +# def activity_filter(context): +# request = context['request'] +# activity = request.GET.get('activity') +# if activity: +# activity_form = ActivityFilterForm(initial={ +# 'activity': activity +# }) +# else: +# activity_form = ActivityFilterForm() + +# return { +# 'activity_form': activity_form, +# 'form_path': context['request'].path +# } + + +# @register.inclusion_tag('django_ledger/components/date_picker.html', takes_context=True) +# def date_picker(context, nav_url=None, date_picker_id=None): +# try: +# entity_slug = context['view'].kwargs.get('entity_slug') +# except KeyError: +# entity_slug = context['entity_slug'] + +# if not date_picker_id: +# date_picker_id = f'djl-datepicker-{randint(10000, 99999)}' + +# if 'date_picker_ids' not in context: +# context['date_picker_ids'] = list() +# context['date_picker_ids'].append(date_picker_id) + +# date_navigation_url = nav_url if nav_url else context.get('date_navigation_url') +# return { +# 'entity_slug': entity_slug, +# 'date_picker_id': date_picker_id, +# 'date_navigation_url': date_navigation_url +# } + + +# @register.simple_tag(takes_context=True) +# def get_current_end_date_filter(context): +# entity_slug = context['view'].kwargs.get('entity_slug') +# return get_end_date_from_session(entity_slug, context['request']) + + +# @register.inclusion_tag('django_ledger/components/chart_container.html') +# def chart_container(chart_id, endpoint=None): +# return { +# 'chart_id': chart_id, +# 'endpoint': endpoint +# } + + +# @register.inclusion_tag('django_ledger/components/modals.html', takes_context=True) +# def modal_action(context, model, http_method: str = 'post', entity_slug: str = None): +# if not entity_slug: +# entity_slug = context['view'].kwargs['entity_slug'] +# action_url = model.get_mark_as_paid_url(entity_slug=entity_slug) +# return { +# 'object': model, +# 'action_url': action_url, +# 'http_method': http_method, +# 'message': f'Do you want to mark {model.__class__._meta.verbose_name} {model.get_document_id()} as paid?' +# } + + +# @register.inclusion_tag('django_ledger/components/modals_v2.html', takes_context=True) +# def modal_action_v2(context, model, action_url: str, message: str, html_id: str, http_method: str = 'get'): +# return { +# 'object': model, +# 'action_url': action_url, +# 'http_method': http_method, +# 'message': message, +# 'html_id': html_id +# } + + +# @register.simple_tag +# def fin_ratio_max_value(ratio: str): +# params = DJANGO_LEDGER_FINANCIAL_ANALYSIS['ratios'][ratio]['ranges'] +# return params['healthy'] + + +# @register.filter +# def fin_ratio_threshold_class(value, ratio): +# if value: +# params = DJANGO_LEDGER_FINANCIAL_ANALYSIS['ratios'][ratio] +# ranges = params['ranges'] + +# if params['good_incremental']: +# if value <= ranges['critical']: +# return 'is-danger' +# elif value <= ranges['warning']: +# return 'is-warning' +# elif value <= ranges['watch']: +# return 'is-primary' +# return 'is-success' +# else: +# if value >= ranges['critical']: +# return 'is-danger' +# elif value >= ranges['warning']: +# return 'is-warning' +# elif value >= ranges['watch']: +# return 'is-primary' +# return 'is-success' + + +# @register.inclusion_tag('django_ledger/components/feedback_button.html', takes_context=True) +# def feedback_button(context, button_size_class: str = 'is-small', color_class: str = 'is-success', icon_id: str = None): +# bug_modal_html_id = f'djl-bug-button-{randint(10000, 99999)}' +# feature_modal_html_id = f'djl-feature-button-{randint(10000, 99999)}' +# bug_form = BugReportForm() +# feature_form = RequestNewFeatureForm() +# next_url = context['request'].path +# return { +# 'icon_id': icon_id, +# 'bug_modal_html_id': bug_modal_html_id, +# 'feature_modal_html_id': feature_modal_html_id, +# 'button_size_class': button_size_class, +# 'color_class': color_class, +# 'bug_form': bug_form, +# 'feature_form': feature_form, +# 'next_url': next_url +# } + + +@register.inclusion_tag('inventory/ledger/reports/components/period_navigator.html', takes_context=True) +def period_navigation(context, base_url: str): + kwargs = dict() + entity_slug = context['view'].kwargs['entity_slug'] + kwargs['entity_slug'] = entity_slug + + if context['view'].kwargs.get('ledger_pk'): + kwargs['ledger_pk'] = context['view'].kwargs.get('ledger_pk') + + if context['view'].kwargs.get('account_pk'): + kwargs['account_pk'] = context['view'].kwargs.get('account_pk') + + if context['view'].kwargs.get('unit_slug'): + kwargs['unit_slug'] = context['view'].kwargs.get('unit_slug') + + if context['view'].kwargs.get('coa_slug'): + kwargs['coa_slug'] = context['view'].kwargs.get('coa_slug') + + ctx = dict() + ctx['year'] = context['year'] + ctx['has_year'] = context.get('has_year') + ctx['has_quarter'] = context.get('has_quarter') + ctx['has_month'] = context.get('has_month') + ctx['has_date'] = context.get('has_date') + ctx['previous_year'] = context['previous_year'] + + kwargs['year'] = context['previous_year'] + ctx['previous_year_url'] = reverse(f'django_ledger:{base_url}-year', kwargs=kwargs) + ctx['next_year'] = context['next_year'] + + kwargs['year'] = context['next_year'] + ctx['next_year_url'] = reverse(f'django_ledger:{base_url}-year', kwargs=kwargs) + + kwargs['year'] = context['year'] + ctx['current_year_url'] = reverse(f'django_ledger:{base_url}-year', kwargs=kwargs) + + dt = get_localdate() + + KWARGS_CURRENT_MONTH = { + 'entity_slug': context['view'].kwargs['entity_slug'], + 'year': dt.year, + 'month': dt.month + } + + if 'unit_slug' in kwargs: + KWARGS_CURRENT_MONTH['unit_slug'] = kwargs['unit_slug'] + if 'account_pk' in kwargs: + KWARGS_CURRENT_MONTH['account_pk'] = kwargs['account_pk'] + if 'ledger_pk' in kwargs: + KWARGS_CURRENT_MONTH['ledger_pk'] = kwargs['ledger_pk'] + if 'coa_slug' in kwargs: + KWARGS_CURRENT_MONTH['coa_slug'] = kwargs['coa_slug'] + + ctx['current_month_url'] = reverse(f'django_ledger:{base_url}-month', + kwargs=KWARGS_CURRENT_MONTH) + + quarter_urls = list() + ctx['quarter'] = context.get('quarter') + for Q in range(1, 5): + kwargs['quarter'] = Q + quarter_urls.append({ + 'url': reverse(f'django_ledger:{base_url}-quarter', kwargs=kwargs), + 'quarter': Q, + 'quarter_name': f'Q{Q}' + }) + del kwargs['quarter'] + ctx['quarter_urls'] = quarter_urls + + month_urls = list() + ctx['month'] = context.get('month') + for M in range(1, 13): + kwargs['month'] = M + month_urls.append({ + 'url': reverse(f'django_ledger:{base_url}-month', kwargs=kwargs), + 'month': M, + 'month_abbr': month_abbr[M] + }) + ctx['month_urls'] = month_urls + ctx['from_date'] = context['from_date'] + ctx['to_date'] = context['to_date'] + ctx.update(kwargs) + + ctx['date_navigation_url'] = context.get('date_navigation_url') + + return ctx + + +# @register.inclusion_tag('django_ledger/components/menu.html', takes_context=True) +# def navigation_menu(context, style): +# ENTITY_SLUG = context['view'].kwargs.get('entity_slug') + +# ctx = dict() +# ctx['style'] = style +# if ENTITY_SLUG: +# ctx['entity_slug'] = ENTITY_SLUG +# nav_menu_links = [ +# { +# 'type': 'link', +# 'title': 'Entity Dashboard', +# 'url': reverse('django_ledger:entity-dashboard', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'links', +# 'title': 'Management', +# 'links': [ +# { +# 'type': 'link', +# 'title': 'Vendors', +# 'url': reverse('django_ledger:vendor-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Customers', +# 'url': reverse('django_ledger:customer-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Bank Accounts', +# 'url': reverse('django_ledger:bank-account-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Estimates & Contracts', +# 'url': reverse('django_ledger:customer-estimate-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Bills', +# 'url': reverse('django_ledger:bill-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Invoices', +# 'url': reverse('django_ledger:invoice-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Purchase Orders', +# 'url': reverse('django_ledger:po-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Inventory', +# 'url': reverse('django_ledger:inventory-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Closing Entries', +# 'url': reverse('django_ledger:closing-entry-list', kwargs={'entity_slug': ENTITY_SLUG}) +# } + +# ] +# }, +# { +# 'type': 'links', +# 'title': 'Your Lists', +# 'links': [ +# { +# 'type': 'link', +# 'title': 'Entity Units', +# 'url': reverse('django_ledger:unit-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Products', +# 'url': reverse('django_ledger:product-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Services', +# 'url': reverse('django_ledger:service-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Business Expenses', +# 'url': reverse('django_ledger:expense-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Inventory Items', +# 'url': reverse('django_ledger:inventory-item-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Unit of Measures', +# 'url': reverse('django_ledger:uom-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# ] +# }, +# { +# 'type': 'links', +# 'title': 'Reports', +# 'links': [ +# { +# 'type': 'link', +# 'title': 'Balance Sheet', +# 'url': reverse('django_ledger:entity-bs', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Income Statement', +# 'url': reverse('django_ledger:entity-ic', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Cash Flow Statement', +# 'url': reverse('django_ledger:entity-cf', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# ] +# }, +# { +# 'type': 'links', +# 'title': 'Accounting', +# 'links': [ +# { +# 'type': 'link', +# 'title': 'Chart of Accounts', +# 'url': reverse('django_ledger:coa-list', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Ledgers', +# 'url': reverse('django_ledger:ledger-list-visible', kwargs={'entity_slug': ENTITY_SLUG}) +# }, +# { +# 'type': 'link', +# 'title': 'Data Import', +# 'url': reverse('django_ledger:data-import-jobs-list', kwargs={'entity_slug': ENTITY_SLUG}) +# } +# ] +# }, +# { +# 'type': 'links', +# 'title': 'Administration', +# 'links': [ +# { +# 'type': 'link', +# 'title': 'My Entities', +# 'url': reverse('django_ledger:home') +# }, +# { +# 'type': 'link', +# 'title': 'Entity Settings', +# 'url': reverse('django_ledger:entity-update', kwargs={'entity_slug': ENTITY_SLUG}) +# } +# ] +# } +# ] +# ctx['links'] = nav_menu_links +# ctx['request'] = context['request'] +# return ctx + + +# @register.inclusion_tag('django_ledger/product/tags/product_table.html', takes_context=True) +# def product_table(context, queryset): +# entity_slug = context['view'].kwargs['entity_slug'] +# return { +# 'entity_slug': entity_slug, +# 'product_list': queryset +# } + + +# @register.inclusion_tag('django_ledger/service/tags/services_table.html', takes_context=True) +# def service_table(context, queryset): +# entity_slug = context['view'].kwargs['entity_slug'] +# return { +# 'entity_slug': entity_slug, +# 'service_list': queryset +# } + + +# @register.inclusion_tag('django_ledger/expense/tags/expense_item_table.html', takes_context=True) +# def expense_item_table(context, queryset): +# entity_slug = context['view'].kwargs['entity_slug'] +# return { +# 'entity_slug': entity_slug, +# 'expense_list': queryset +# } + + +# @register.inclusion_tag('django_ledger/inventory/tags/inventory_item_table.html', takes_context=True) +# def inventory_item_table(context, queryset): +# entity_slug = context['view'].kwargs['entity_slug'] +# return { +# 'entity_slug': entity_slug, +# 'inventory_item_list': queryset +# } + + +# @register.inclusion_tag('django_ledger/invoice/tags/invoice_item_formset.html', takes_context=True) +# def invoice_item_formset_table(context, itemtxs_formset): +# return { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'invoice_model': context['invoice'], +# 'total_amount__sum': context['total_amount__sum'], +# 'itemtxs_formset': itemtxs_formset, +# } + + +# @register.inclusion_tag('django_ledger/bills/tags/bill_item_formset.html', takes_context=True) +# def bill_item_formset_table(context, item_formset): +# return { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'bill_pk': context['view'].kwargs['bill_pk'], +# 'total_amount__sum': context['total_amount__sum'], +# 'item_formset': item_formset, +# } + + +# @register.inclusion_tag('django_ledger/purchase_order/includes/po_item_formset.html', takes_context=True) +# def po_item_formset_table(context, po_model, itemtxs_formset): +# return { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'po_model': po_model, +# 'itemtxs_formset': itemtxs_formset, +# } + + +# @register.inclusion_tag('django_ledger/uom/tags/uom_table.html', takes_context=True) +# def uom_table(context, uom_queryset): +# return { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'uom_list': uom_queryset +# } + + +# @register.inclusion_tag('django_ledger/inventory/tags/inventory_table.html', takes_context=True) +# def inventory_table(context, queryset): +# ctx = { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'inventory_list': queryset +# } +# ctx.update(queryset.aggregate(inventory_total_value=Sum('total_value'))) +# return ctx + + +# @register.inclusion_tag('django_ledger/estimate/includes/estimate_table.html', takes_context=True) +# def customer_estimate_table(context, queryset): +# return { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'ce_list': queryset +# } + + +# @register.inclusion_tag('django_ledger/estimate/includes/estimate_item_table.html', takes_context=True) +# def estimate_item_table(context, queryset): +# return { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'ce_model': context['estimate_model'], +# 'ce_item_list': queryset +# } + + +# @register.inclusion_tag('django_ledger/purchase_order/tags/po_item_table.html', takes_context=True) +# def po_item_table(context, queryset): +# return { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'po_model': context['po_model'], +# 'po_item_list': queryset +# } + + +# @register.inclusion_tag('django_ledger/estimate/tags/ce_item_formset.html', takes_context=True) +# def customer_estimate_item_formset(context, item_formset): +# return { +# 'entity_slug': context['view'].kwargs['entity_slug'], +# 'ce_pk': context['view'].kwargs['ce_pk'], +# 'ce_revenue_estimate__sum': context['ce_revenue_estimate__sum'], +# 'ce_cost_estimate__sum': context['ce_cost_estimate__sum'], +# 'item_formset': item_formset, +# } diff --git a/inventory/urls.py b/inventory/urls.py index 80ec7da9..7b323a58 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -279,6 +279,7 @@ urlpatterns = [ views.payment_create, name="payment_create", ), + # Users URLs path("user/create/", views.UserCreateView.as_view(), name="user_create"), path("user//update/", views.UserUpdateView.as_view(), name="user_update"), @@ -403,6 +404,7 @@ urlpatterns = [ "sales/estimates//send_email", views.send_email_view, name="send_email" ), path('sales/estimates//sale_order/', views.create_sale_order, name='create_sale_order'), + path('sales/estimates//sale_order/preview/', views.preview_sale_order, name='preview_sale_order'), # Invoice path("sales/invoices/", views.InvoiceListView.as_view(), name="invoice_list"), @@ -523,6 +525,28 @@ urlpatterns = [ views.bill_mark_as_paid, name="bill_mark_as_paid", ), + + # orders + path("orders/", views.OrderListView.as_view(), name="order_list"), + + # BALANCE SHEET Reports... + # Entities... + path('entity//balance-sheet/', + views.BaseBalanceSheetRedirectView.as_view(), + name='entity-bs'), + path('entity//balance-sheet/year//', + views.FiscalYearBalanceSheetViewBase.as_view(), + name='entity-bs-year'), + path('entity//balance-sheet/quarter///', + views.QuarterlyBalanceSheetView.as_view(), + name='entity-bs-quarter'), + path('entity//balance-sheet/month///', + views.MonthlyBalanceSheetView.as_view(), + name='entity-bs-month'), + path('entity//balance-sheet/date////', + views.DateBalanceSheetView.as_view(), + name='entity-bs-date'), + ] diff --git a/inventory/utils.py b/inventory/utils.py index 2abf4681..c73ed419 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -15,7 +15,13 @@ from django.core.mail import send_mail from django.utils.translation import gettext_lazy as _ from inventory.utilities.financials import get_financial_value from django_ledger.models.items import ItemModel -from django_ledger.models import InvoiceModel, EstimateModel,BillModel,VendorModel,CustomerModel +from django_ledger.models import ( + InvoiceModel, + EstimateModel, + BillModel, + VendorModel, + CustomerModel, +) from decimal import Decimal @@ -123,6 +129,73 @@ def calculate_vat_amount(amount): return amount +def get_car_finance_data(model): + vat = models.VatRate.objects.filter(is_active=True).first() + data = model.get_itemtxs_data()[0].all() + total = sum( + [ + Decimal(item.item_model.additional_info["car_finance"]["selling_price"]) + * Decimal(item.ce_quantity or item.quantity) + for item in data + ] + ) + + additional_services = [] + + for i in data: + if i.item_model.additional_info["additional_services"]: + additional_services.extend( + [ + {"name": x.name, "price": x.price} + for x in i.item_model.additional_info["additional_services"] + ] + ) + + return { + "cars": [ + { + "vin": x.item_model.additional_info["car_info"]["vin"], + "make": x.item_model.additional_info["car_info"]["make"], + "model": x.item_model.additional_info["car_info"]["model"], + "year": x.item_model.additional_info["car_info"]["year"], + "trim": x.item_model.additional_info["car_info"]["mileage"], + "cost_price": x.item_model.additional_info["car_finance"]["cost_price"], + "selling_price": x.item_model.additional_info["car_finance"][ + "selling_price" + ], + "discount": x.item_model.additional_info["car_finance"][ + "discount_amount" + ], + "quantity": x.ce_quantity or x.quantity, + "unit_price": Decimal(x.item_model.additional_info["car_finance"]["total"]), + "total": Decimal(x.item_model.additional_info["car_finance"]["total"]) * Decimal(x.quantity or x.ce_quantity), + "total_vat": x.item_model.additional_info["car_finance"]["total_vat"], + "additional_services": x.item_model.additional_info[ + "additional_services" + ], + } + for x in data + ], + "quantity": sum((x.quantity or x.ce_quantity) for x in data), + "total_price": total, + "total_vat": (total * vat.rate) + total, + "total_discount": sum( + Decimal(x.item_model.additional_info["car_finance"]["discount_amount"]) + for x in data + ), + "grand_total": Decimal(total * vat.rate) + + total + - Decimal( + sum( + Decimal(x.item_model.additional_info["car_finance"]["discount_amount"]) + for x in data + ) + ), + "additionals": additional_services, + "vat": vat.rate, + } + + def get_financial_values(model): vat = models.VatRate.objects.filter(is_active=True).first() @@ -136,7 +209,7 @@ def get_financial_values(model): "car_and_item_info": [], "additional_services": [], } - data = model.get_itemtxs_data()[0].all() + data = model.get_itemtxs_data()[0].all() for item in data: if not item.item_model.additional_info.get("car_finance"): @@ -149,44 +222,52 @@ def get_financial_values(model): "car_and_item_info": [], "additional_services": [], } - + if isinstance(model, InvoiceModel): if model.ce_model: data = model.ce_model.get_itemtxs_data()[0].all() else: data = model.get_itemtxs_data()[0].all() - total = sum([Decimal(item.item_model.additional_info["car_finance"]["selling_price"]) * Decimal(item.ce_quantity or item.quantity) for item in data]) - + total = sum( + [ + Decimal(item.item_model.additional_info["car_finance"]["selling_price"]) + * Decimal(item.ce_quantity or item.quantity) + for item in data + ] + ) + discount_amount = sum( - Decimal(i.item_model.additional_info['car_finance']["discount_amount"]) + Decimal(i.item_model.additional_info["car_finance"]["discount_amount"]) for i in data ) additional_services = [] - for i in data: - if i.item_model.additional_info['additional_services']: + for i in data: + if i.item_model.additional_info["additional_services"]: additional_services.extend( [ {"name": x.name, "price": x.price} - for x in i.item_model.additional_info['additional_services'] + for x in i.item_model.additional_info["additional_services"] ] ) grand_total = Decimal(total) - Decimal(discount_amount) vat_amount = round(Decimal(grand_total) * Decimal(vat.rate), 2) - + car_and_item_info = [ { - "info": x.item_model.additional_info['car_info'], - "finances": x.item_model.additional_info['car_finance'], + "info": x.item_model.additional_info["car_info"], + "finances": x.item_model.additional_info["car_finance"], "quantity": x.ce_quantity or x.quantity, - "total": Decimal(x.item_model.additional_info['car_finance']['selling_price']) + "total": Decimal( + x.item_model.additional_info["car_finance"]["selling_price"] + ) * Decimal(x.ce_quantity or x.quantity), } for x in data ] - + return { "total": total, "discount_amount": discount_amount, @@ -245,7 +326,7 @@ def set_invoice_payment(dealer, entity, invoice, amount, payment_method): tx_type="debit", description="Payment Received", ) - + # if total_amount + invoice. TransactionModel.objects.create( @@ -288,7 +369,6 @@ def set_bill_payment(dealer, entity, bill, amount, payment_method): name="Accounts Payable", active=True ) - TransactionModel.objects.create( journal_entry=journal, account=cash_account, # Debit Cash @@ -309,9 +389,9 @@ def set_bill_payment(dealer, entity, bill, amount, payment_method): bill.save() -def transfer_to_dealer(request,cars, to_dealer, remarks=None): +def transfer_to_dealer(request, cars, to_dealer, remarks=None): dealer = get_user_type(request) - + if not cars: raise ValueError("No cars selected for transfer.") @@ -337,33 +417,39 @@ def transfer_to_dealer(request,cars, to_dealer, remarks=None): for car in cars: car.dealer = to_dealer car.save() - -def transfer_car(car,transfer): + + +def transfer_car(car, transfer): from_dealer = transfer.from_dealer to_dealer = transfer.to_dealer # add transfer.to_dealer as customer in transfer.from_dealer entity - - customer = from_dealer.entity.get_customers().filter( - email=to_dealer.user.email).first() + + customer = ( + from_dealer.entity.get_customers().filter(email=to_dealer.user.email).first() + ) if not customer: customer = from_dealer.entity.create_customer( customer_model_kwargs={ "customer_name": to_dealer.name, "email": to_dealer.user.email, - "address_1": to_dealer.address + "address_1": to_dealer.address, } ) - customer.additional_info.update({"type":"organization"}) - customer.save() + customer.additional_info.update({"type": "organization"}) + customer.save() invoice = from_dealer.entity.create_invoice( customer_model=customer, terms=InvoiceModel.TERMS_NET_30, - cash_account=from_dealer.entity.get_default_coa_accounts().get(name="Cash", active=True), - prepaid_account=from_dealer.entity.get_default_coa_accounts().get(name="Accounts Receivable", active=True), + cash_account=from_dealer.entity.get_default_coa_accounts().get( + name="Cash", active=True + ), + prepaid_account=from_dealer.entity.get_default_coa_accounts().get( + name="Accounts Receivable", active=True + ), coa_model=from_dealer.entity.get_default_coa(), ) - + ledger = from_dealer.entity.create_ledger(name=str(invoice.pk)) invoice.ledgar = ledger ledger.invoicemodel = invoice @@ -372,7 +458,7 @@ def transfer_car(car,transfer): item = from_dealer.entity.get_items_products().filter(name=car.vin).first() if not item: return - + invoice_itemtxs = { item.item_number: { "unit_cost": transfer.total_price, @@ -386,68 +472,76 @@ def transfer_car(car,transfer): commit=True, operation=InvoiceModel.ITEMIZE_APPEND, ) - + invoice.save() invoice.mark_as_review() invoice.mark_as_approved(from_dealer.entity.slug, from_dealer.entity.admin) # invoice.mark_as_paid(from_dealer.entity.slug, from_dealer.entity.admin) invoice.save() - #create car item product in to_dealer entity + # create car item product in to_dealer entity uom = to_dealer.entity.get_uom_all().filter(name=item.uom.name).first() - - #create item product in the reciever ledger + + # create item product in the reciever ledger product = to_dealer.entity.create_item_product( - name=item.name, - uom_model=uom, - item_type=item.item_type, - coa_model=to_dealer.entity.get_default_coa(), + name=item.name, + uom_model=uom, + item_type=item.item_type, + coa_model=to_dealer.entity.get_default_coa(), ) - - product.additional_info.update({'car_info': car.to_dict()}) + + product.additional_info.update({"car_info": car.to_dict()}) product.save() - - #add the sender as vendor and create a bill for it + + # add the sender as vendor and create a bill for it vendor = None vendor = to_dealer.entity.get_vendors().filter(vendor_name=from_dealer.name).first() if not vendor: - vendor = VendorModel.objects.create(entity_model=to_dealer.entity, vendor_name=from_dealer.name,additional_info={"info":to_dict(from_dealer)}) - - #transfer the car to to_dealer and create items record - + vendor = VendorModel.objects.create( + entity_model=to_dealer.entity, + vendor_name=from_dealer.name, + additional_info={"info": to_dict(from_dealer)}, + ) + + # transfer the car to to_dealer and create items record + bill = to_dealer.entity.create_bill( vendor_model=vendor, terms=BillModel.TERMS_NET_30, - cash_account=to_dealer.entity.get_default_coa_accounts().get(name="Cash", active=True), - prepaid_account=to_dealer.entity.get_default_coa_accounts().get(name="Prepaid Expenses", active=True), + cash_account=to_dealer.entity.get_default_coa_accounts().get( + name="Cash", active=True + ), + prepaid_account=to_dealer.entity.get_default_coa_accounts().get( + name="Prepaid Expenses", active=True + ), coa_model=to_dealer.entity.get_default_coa(), ) bill.additional_info = {} bill_itemtxs = { product.item_number: { - "unit_cost": transfer.total_price, + "unit_cost": transfer.total_price, "quantity": transfer.quantity, "total_amount": transfer.total_price, } - } - - bill_itemtxs = bill.migrate_itemtxs(itemtxs=bill_itemtxs, - commit=True, - operation=BillModel.ITEMIZE_APPEND) - - bill.additional_info.update({'car_info': car.to_dict()}) - bill.additional_info.update({'car_finance': car.finances.to_dict()}) - + } + + bill_itemtxs = bill.migrate_itemtxs( + itemtxs=bill_itemtxs, commit=True, operation=BillModel.ITEMIZE_APPEND + ) + + bill.additional_info.update({"car_info": car.to_dict()}) + bill.additional_info.update({"car_finance": car.finances.to_dict()}) + bill.mark_as_review() bill.mark_as_approved(to_dealer.entity.slug, to_dealer.entity.admin) bill.save() - + car.dealer = to_dealer car.vendor = vendor car.receiving_date = datetime.datetime.now() car.finances.additional_services.clear() if hasattr(car, "custom_cards"): car.custom_cards.delete() - + car.finances.cost_price = transfer.total_price car.finances.selling_price = 0 car.finances.discount_amount = 0 @@ -461,24 +555,25 @@ def transfer_car(car,transfer): transfer.active = False transfer.save() car.save() - + return True - #pay the pill + # pay the pill # set_bill_payment(to_dealer,to_dealer.entity,bill,transfer.total_price,"credit") + def to_dict(obj): obj_dict = vars(obj).copy() - if '_state' in vars(obj): + if "_state" in vars(obj): del obj_dict["_state"] - + for key, value in obj_dict.items(): if isinstance(value, datetime.datetime): - obj_dict[key] = value.strftime('%Y-%m-%d %H:%M:%S') - elif hasattr(value,'pk') or hasattr(value,'id'): + obj_dict[key] = value.strftime("%Y-%m-%d %H:%M:%S") + elif hasattr(value, "pk") or hasattr(value, "id"): try: obj_dict[key] = value.name except AttributeError: obj_dict[key] = str(value) else: obj_dict[key] = str(value) - return obj_dict \ No newline at end of file + return obj_dict diff --git a/inventory/views.py b/inventory/views.py index 8b9bd00d..70dc11c1 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -84,6 +84,7 @@ from django.contrib.auth.models import Group from .utils import ( calculate_vat_amount, get_calculations, + get_car_finance_data, get_financial_values, reserve_car, send_email, @@ -2399,15 +2400,26 @@ def create_sale_order(request, pk): form = forms.SaleOrderForm(request.POST) if form.is_valid(): form.save() - return redirect("success") - else: - form = forms.SaleOrderForm() + if not estimate.is_completed(): + estimate.mark_as_completed() + estimate.save() + messages.success(request, "Sale Order created successfully") + return redirect("estimate_detail", pk=pk) + + form = forms.SaleOrderForm() + form.fields["estimate"].queryset = EstimateModel.objects.filter(pk=pk) + form.initial['estimate'] = estimate + data = get_car_finance_data(estimate) return render( request, - "sales/estimates/sale_order.html", - {"form": form, "estimate": estimate, "items": items}, + "sales/estimates/sale_order_form.html", + {"form": form, "estimate": estimate, "items": items,"data": data}, ) +def preview_sale_order(request,pk): + estimate = get_object_or_404(EstimateModel,pk=pk) + data = get_car_finance_data(estimate) + return render(request,'sales/estimates/sale_order_preview.html',{'order':estimate.sale_orders.first(),"data":data,"estimate":estimate}) class PaymentRequest(LoginRequiredMixin, DetailView): model = EstimateModel @@ -3406,6 +3418,13 @@ class SubscriptionPlans(ListView): context_object_name = "plans" +# orders + +class OrderListView(ListView): + model = models.SaleOrder + template_name = "sales/orders/order_list.html" + context_object_name = "orders" + # email def send_email_view(request, pk): estimate = get_object_or_404(EstimateModel, pk=pk) @@ -3474,3 +3493,48 @@ def custom_permission_denied_view(request, exception=None): def custom_bad_request_view(request, exception=None): return render(request, "errors/400.html", {}) + + + +# from django_ledger.io.io_core import get_localdate +# from django_ledger.views.mixins import (DjangoLedgerSecurityMixIn) +# from django.views.generic import RedirectView +from django_ledger.views.financial_statement import FiscalYearBalanceSheetView +from django.views.generic import DetailView, RedirectView + +from django_ledger.io.io_core import get_localdate +from django_ledger.models import EntityModel, EntityUnitModel +from django_ledger.views.mixins import ( + QuarterlyReportMixIn, YearlyReportMixIn, + MonthlyReportMixIn, DateReportMixIn, DjangoLedgerSecurityMixIn, EntityUnitMixIn, + BaseDateNavigationUrlMixIn, PDFReportMixIn +) +# BALANCE SHEET ----------- + +class BaseBalanceSheetRedirectView(DjangoLedgerSecurityMixIn, RedirectView): + + def get_redirect_url(self, *args, **kwargs): + year = get_localdate().year + return reverse('entity-bs-year', + kwargs={ + 'entity_slug': self.kwargs['entity_slug'], + 'year': year + }) +class FiscalYearBalanceSheetViewBase(FiscalYearBalanceSheetView): + template_name = "ledger/reports/balance_sheet.html" + +class QuarterlyBalanceSheetView(FiscalYearBalanceSheetViewBase, QuarterlyReportMixIn): + """ + Quarter Balance Sheet View. + """ + +class MonthlyBalanceSheetView(FiscalYearBalanceSheetViewBase, MonthlyReportMixIn): + """ + Monthly Balance Sheet View. + """ + + +class DateBalanceSheetView(FiscalYearBalanceSheetViewBase, DateReportMixIn): + """ + Date Balance Sheet View. + """ diff --git a/scripts/report.py b/scripts/report.py new file mode 100644 index 00000000..b8ccb1e3 --- /dev/null +++ b/scripts/report.py @@ -0,0 +1,9 @@ +from django_ledger.report.balance_sheet import BalanceSheetReport +from django_ledger.models import EntityModel + +def run(): + + entity = EntityModel.objects.first() + report = BalanceSheetReport(entity=entity) + + print(report) \ No newline at end of file diff --git a/scripts/run.py b/scripts/run.py new file mode 100644 index 00000000..5350fcd8 --- /dev/null +++ b/scripts/run.py @@ -0,0 +1,70 @@ +from decimal import Decimal +from django_ledger.models import EstimateModel +from rich import print + +from inventory.models import VatRate + + +def run(): + estimate = EstimateModel.objects.first() + vat = VatRate.objects.filter(is_active=True).first() + data = estimate.get_itemtxs_data()[0].all() + total = sum( + [ + Decimal(item.item_model.additional_info["car_finance"]["selling_price"]) + * Decimal(item.ce_quantity or item.quantity) + for item in data + ] + ) + + additional_services = [] + + for i in data: + if i.item_model.additional_info["additional_services"]: + additional_services.extend( + [ + {"name": x.name, "price": x.price} + for x in i.item_model.additional_info["additional_services"] + ] + ) + cars_info = { + "cars": [ + { + "vin": x.item_model.additional_info["car_info"]["vin"], + "make": x.item_model.additional_info["car_info"]["make"], + "model": x.item_model.additional_info["car_info"]["model"], + "year": x.item_model.additional_info["car_info"]["year"], + "trim": x.item_model.additional_info["car_info"]["mileage"], + "cost_price": x.item_model.additional_info["car_finance"]["cost_price"], + "selling_price": x.item_model.additional_info["car_finance"][ + "selling_price" + ], + "discount": x.item_model.additional_info["car_finance"][ + "discount_amount" + ], + "total": x.item_model.additional_info["car_finance"]["total"], + "additional_services": x.item_model.additional_info[ + "additional_services" + ], + } + for x in data + ], + "quantity": data.count(), + "total_price": total, + "total__vat": (total * vat.rate) + total, + "total_discount": sum( + Decimal(x.item_model.additional_info["car_finance"]["discount_amount"]) + for x in data + ), + "grand_total": Decimal(total * vat.rate) + + total + - Decimal( + sum( + Decimal(x.item_model.additional_info["car_finance"]["discount_amount"]) + for x in data + ) + ), + "additionals": additional_services, + } + + print(cars_info) diff --git a/templates/base.html b/templates/base.html index 7599a954..1ff03af0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -56,6 +56,7 @@
{% include 'header.html' %}
+ {% block period_navigation %}{% endblock period_navigation %}
{% block content %} {% endblock content%} diff --git a/templates/header.html b/templates/header.html index fd7571f7..ad9c6581 100644 --- a/templates/header.html +++ b/templates/header.html @@ -105,7 +105,7 @@
+ +
+ {% trans 'bills'|capfirst %} +
+
diff --git a/templates/ledger/reports/balance_sheet.html b/templates/ledger/reports/balance_sheet.html new file mode 100644 index 00000000..53d30094 --- /dev/null +++ b/templates/ledger/reports/balance_sheet.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} +{% load i18n %} +{% load static %} +{% load custom_filters %} + + +{% block period_navigation %} + {% if entity %} +
+
{% period_navigation 'entity-bs' %}
+
+ {% elif ledger %} +
+
{% period_navigation 'ledger-bs' %}
+
+ {% elif unit_model %} +
+
{% period_navigation 'unit-bs' %}
+
+ {% endif %} +{% endblock %} + + +{% block content %} +
+
+
+ {% if entity %} +

{{ entity.name }}

+ {% elif ledger %} +

{{ ledger.name }}

+ {% elif unit_model %} +

{{ ledger.name }}

+ {% endif %} +

{% trans 'Balance Sheet' %}

+ {% if unit_model %} +

{{ unit_model.name }} {% trans 'Unit' %}

+ {% endif %} +

+ {% if quarter %}{{ year }} | Q{{ quarter }} + {% elif month %}{{ from_date | date:'F, Y' }} + {% else %}{% trans 'Fiscal Year' %} {{ year }} + {% endif %} +

+

As of {{ to_date | date:'m/d/Y' }}

+
+ + +
+ {% balance_sheet_statement io_model=object %} +
+
+
+{% endblock %} diff --git a/templates/ledger/reports/components/period_navigator.html b/templates/ledger/reports/components/period_navigator.html new file mode 100644 index 00000000..674b3c7f --- /dev/null +++ b/templates/ledger/reports/components/period_navigator.html @@ -0,0 +1,73 @@ +{% load django_ledger %} +{% load trans from i18n %} + +
+
+ +
+

+ {% if has_year %}Fiscal Year {{ to_date.year }}{% endif %} + {% if has_month %}{{ to_date | date:"F Y" }}{% endif %} + {% if has_quarter %}Q{{ quarter }} {{ year }}{% endif %} + {% if has_date %}{{ to_date | date }}{% endif %} +

+
+ + + + + +
+

+ Quarter: + {% for q_url in quarter_urls %} + {{ q_url.quarter_name }} + {% if not forloop.last %}|{% endif %} + {% endfor %} +

+
+ + +
+

+ {% trans 'Month' %}: + {% for m_url in month_urls %} + {{ m_url.month_abbr }} + {% if not forloop.last %}|{% endif %} + {% endfor %} +

+
+ + +
+ {% if has_date %} +

{{ from_date | date:"m/d/Y" }}

+ {% else %} +

+ {{ from_date | date:"m/d/Y" }} + {% trans 'thru' %} + {{ to_date | date:"m/d/Y" }} +

+ {% endif %} +
+ + + + + +
+ {% date_picker date_navigation_url %} +
+
+
\ No newline at end of file diff --git a/templates/ledger/reports/income_statement.html b/templates/ledger/reports/income_statement.html new file mode 100644 index 00000000..e69de29b diff --git a/templates/ledger/reports/tags/balance_sheet_statement.html b/templates/ledger/reports/tags/balance_sheet_statement.html new file mode 100644 index 00000000..581c24e2 --- /dev/null +++ b/templates/ledger/reports/tags/balance_sheet_statement.html @@ -0,0 +1,140 @@ +{% load django_ledger %} +{% load i18n %} + +
+ + + {% for bs_role, bs_role_data in tx_digest.balance_sheet.items %} + {% if bs_role_data.is_block %} + + + + {% if tx_digest.by_unit %} + + {% endif %} + + + + + + + + {% if tx_digest.by_unit %} + + {% endif %} + + + + + + {% for acc_role, acc_data in bs_role_data.roles.items %} + + + + {% if tx_digest.by_unit %} + + {% endif %} + + + + + + {% for acc in acc_data.accounts %} + + + + {% if tx_digest.by_unit %} + + {% endif %} + + + + + {% endfor %} + + + + + + {% if tx_digest.by_unit %} + + {% endif %} + + + + + {% endfor %} + + + {% if bs_role != 'equity' %} + + + + {% if tx_digest.by_unit %} + + {% endif %} + + + + {% endif %} + + + {% endif %} + {% endfor %} + + + + + + {% if tx_digest.by_unit %} + + {% endif %} + + + + + + + + + {% if tx_digest.by_unit %} + + {% endif %} + + + + + + + + {% if tx_digest.by_unit %} + + {% endif %} + + + + + + +

{{ bs_role | upper }}

{% trans 'Account Code' %}{% trans 'Account Name' %}{% trans 'Unit' %}{% trans 'Balance Type' %}{% trans 'Balance Through' %} {{ tx_digest.to_date | date }}{% trans 'Actions' %}
{{ acc_data.role_name | upper }}
{{ acc.code }}{{ acc.name }}{% if acc.unit_name %}{{ acc.unit_name }}{% endif %}{{ acc.balance_type.0 | upper }}{% currency_symbol %}{{ acc.balance | currency_format }} + +
{{ acc_data.role_name | upper }} {% trans 'Total:' %} + {% currency_symbol %}{{ acc_data.total_balance | currency_format }}
{% trans 'Total' %} {{ bs_role | upper }}{% currency_symbol %}{{ bs_role_data.total_balance | currency_format }}
{% trans 'Retained Earnings' %}{% currency_symbol %}{{ tx_digest.group_balance.GROUP_EARNINGS | currency_format }}
{% trans 'Total EQUITY' %}{% currency_symbol %}{{ tx_digest.group_balance.GROUP_EQUITY | currency_format }}
{% trans 'Total Equity + Liabilities' %}{% currency_symbol %}{{ tx_digest.group_balance.GROUP_LIABILITIES_EQUITY | currency_format }}
+
diff --git a/templates/sales/estimates/estimate_detail.html b/templates/sales/estimates/estimate_detail.html index 1a5382ab..2b5a6fbe 100644 --- a/templates/sales/estimates/estimate_detail.html +++ b/templates/sales/estimates/estimate_detail.html @@ -45,7 +45,10 @@ {% elif estimate.status == 'in_review' %} {% elif estimate.status == 'approved' %} + {% trans 'Create Sale Order' %} + {% elif estimate.status == 'completed' %} {% trans 'Create Invoice' %} + {% trans 'Preview Sale Order' %} {% elif estimate.status == 'in_review' %} {% trans 'Preview' %} {% endif %} diff --git a/templates/sales/estimates/sale_order_form.html b/templates/sales/estimates/sale_order_form.html new file mode 100644 index 00000000..42ccf2ce --- /dev/null +++ b/templates/sales/estimates/sale_order_form.html @@ -0,0 +1,157 @@ +{% extends 'base.html' %} +{% load i18n static %} +{% load crispy_forms_filters %} +{% block title %} + {% trans 'Sale Order' %} +{% endblock %} + +{% block content %} + +
+
+
+
+

+ {% if customer.created %} + {{ _('Edit Sale Order') }} + {% else %} + {{ _('Add Sale Order') }} + {% endif %} +

+
+
+
+ +
+
+
+ {% csrf_token %} + {{ form|crispy }} +
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+

Customer Name

+
+
: +

{{ estimate.customer.customer_name }}

+
+
+
+ +
+

Email

+
+
: +

{{ estimate.customer.email }}

+
+
+
+ +
+

Address

+
+
: +

{{ estimate.customer.address_1 }}

+
+
+
+ +
+

Total Amount

+
+
: +

${{ data.grand_total }}

+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + {% for car in data.cars %} + + + + + + + + + {% endfor %} + +
+
+ +
+
VinMakeModelYearUnit Price
+
+ +
+
+ + {% comment %}
+ +
{% endcomment %} +
{{car.vin}}
+
+
{{car.make}}{{car.model}}{{car.year}} +
+ ${{car.total_vat}} +
+
+
+
+
+
+{% endblock %} diff --git a/templates/sales/estimates/sale_order.html b/templates/sales/estimates/sale_order_preview.html similarity index 83% rename from templates/sales/estimates/sale_order.html rename to templates/sales/estimates/sale_order_preview.html index d38f5631..eecde802 100644 --- a/templates/sales/estimates/sale_order.html +++ b/templates/sales/estimates/sale_order_preview.html @@ -123,7 +123,7 @@ -{% if estimate.status != "in_review" %} +{% if not estimate.is_completed %}
@@ -208,44 +208,6 @@
{% endcomment %} - - - - - -
@@ -265,30 +227,31 @@

{% trans "Terms" %} : {{estimate.terms|title}}

+
+ - - {% for item in items %} + {% for car in data.cars %} - - - - - - - + + + + + + + {% endfor %} @@ -297,10 +260,10 @@
-

{% trans "VAT" %} ({{vat}}%): ${{vat_amount}}

+

{% trans "VAT" %} ({{vat}}%): ${{data.vat}}

{% trans "Additional Services" %}:
- {% for service in additional_services %} + {% for service in data.additional_services %} {{service.name}} - ${{service.price}}
{% endfor %}

@@ -308,7 +271,7 @@
-

{%trans "Total Amount" %}: ${{total}}

+

{%trans "Total Amount" %}: ${{data.grand_total}}

diff --git a/templates/sales/orders/order_list.html b/templates/sales/orders/order_list.html new file mode 100644 index 00000000..98bf9d63 --- /dev/null +++ b/templates/sales/orders/order_list.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{{ _("Orders") }}{% endblock title %} + +{% block content %} +
+

{% trans "Orders" %}

+ +
+
{% trans "VIN" %} {% trans "Make" %} {% trans "Model" %} {% trans "Year" %}{% trans "VIN" %} {% trans "Quantity" %} {% trans "Unit Price" %} {% trans "Total" %}
{{ item.item_model.additional_info.car_info.make }}{{ item.item_model.additional_info.car_info.model }}{{ item.item_model.additional_info.car_info.year }}{{ item.item_model.additional_info.car_info.vin }}{{ item.ce_quantity }}{{ item.item_model.additional_info.car_finance.selling_price }}{{ item.total }}{{ car.vin }}{{ car.make }}{{ car.model }}{{ car.year }}{{ car.quantity }}{{ car.unit_price }}{{ car.total }}
+ + + + + + + + + {% for order in orders %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Order Number" %}{% trans "Customer" %}{% trans "For Estimate" %}
{{ order.formatted_order_id }}{{ order.estimate.customer.customer_name }}{{ order.estimate }} + {% comment %} + {% trans "view"|capfirst %} + {% endcomment %} +
{% trans "No Quotations Found" %}
+
+
+ {% if is_paginated %} + {% include 'partials/pagination.html' %} + {% endif %} +
+ +
+{% endblock %} \ No newline at end of file