estimate and bank accounts

This commit is contained in:
gitea 2024-12-29 14:49:28 +00:00
parent 855c8f6c80
commit 239ebb8a65
13 changed files with 1117 additions and 3 deletions

View File

@ -24,6 +24,7 @@ from .models import (
AdditionalServices
)
from django_ledger.models import ItemModel
from django.forms import ModelMultipleChoiceField, ValidationError
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
@ -403,3 +404,11 @@ class WizardForm3(forms.Form):
if password and confirm_password and password != confirm_password:
raise forms.ValidationError("Passwords do not match.")
class ItemForm(forms.Form):
item = forms.ModelChoiceField(queryset=ItemModel.objects.all(),label="Item", required=True)
quantity = forms.DecimalField(label="Quantity", required=True)
unit = forms.DecimalField(label="Unit", required=True)
unit_cost = forms.DecimalField(label="Unit Cost", required=True)
unit_sales_price = forms.DecimalField(label="Unit Sales Price", required=True)

View File

@ -105,6 +105,24 @@ urlpatterns = [
path('representatives/create/', views.RepresentativeCreateView.as_view(), name='representative_create'),
path('representatives/<int:pk>/update/', views.RepresentativeUpdateView.as_view(), name='representative_update'),
path('representatives/<int:pk>/delete/', views.RepresentativeDeleteView.as_view(), name='representative_delete'),
#Ledger URLS
#Bank Account
path('bank_accounts/', views.BankAccountListView.as_view(), name='bank_account_list'),
path('bank_accounts/<uuid:pk>/', views.BankAccountDetailView.as_view(), name='bank_account_detail'),
path('bank_accounts/create/', views.BankAccountCreateView.as_view(), name='bank_account_create'),
path('bank_accounts/<uuid:pk>/update/', views.BankAccountUpdateView.as_view(), name='bank_account_update'),
path('bank_accounts/<uuid:pk>/delete/', views.bank_account_delete, name='bank_account_delete'),
# Account
path('coa_accounts/', views.AccountListView.as_view(), name='account_list'),
path('coa_accounts/<uuid:pk>/', views.AccountDetailView.as_view(), name='account_detail'),
path('coa_accounts/create/', views.AccountCreateView.as_view(), name='account_create'),
path('coa_accounts/<uuid:pk>/update/', views.AccountUpdateView.as_view(), name='account_update'),
path('coa_accounts/<uuid:pk>/delete/', views.account_delete, name='account_delete'),
# Estimate
path('sales/estimates/', views.EstimateListView.as_view(), name='estimate_list'),
path('estimates/<uuid:pk>/', views.EstimateDetailView.as_view(), name='estimate_detail'),
path('sales/estimates/create/', views.create_estimate, name='estimate_create'),
]

View File

@ -1,9 +1,13 @@
from django_ledger.models import EntityModel, InvoiceModel
from django_ledger.models import EntityModel, InvoiceModel,BankAccountModel,AccountModel,JournalEntryModel,TransactionModel,EstimateModel,CustomerModel
from django_ledger.forms.bank_account import BankAccountCreateForm,BankAccountUpdateForm
from django_ledger.forms.account import AccountModelCreateForm,AccountModelUpdateForm
from django_ledger.forms.estimate import EstimateModelCreateForm
import logging
import json
import datetime
from decimal import Decimal
from django_ledger.models import TransactionModel, AccountModel,JournalEntryModel
from django.shortcuts import HttpResponse
from django.template.loader import render_to_string
# from weasyprint import HTML
@ -1492,3 +1496,222 @@ def payment_create(request, pk):
else:
form = forms.PaymentForm()
return render(request, "sales/payments/payment_create.html", {"quotation": quotation,"form": form})
#Ledger
#BANK ACCOUNT
class BankAccountListView(LoginRequiredMixin, ListView):
model = BankAccountModel
template_name = "ledger/bank_accounts/bank_account_list.html"
context_object_name = "bank_accounts"
def get_queryset(self):
return BankAccountModel.objects.filter(entity_model=self.request.user.dealer.entity)
class BankAccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = BankAccountModel
form_class = BankAccountCreateForm
template_name = "ledger/bank_accounts/bank_account_form.html"
success_url = reverse_lazy("bank_account_list")
success_message = "Bank account created successfully."
def form_valid(self, form):
form.instance.entity_model = self.request.user.dealer.entity
return super().form_valid(form)
def get_form_kwargs(self):
"""
Override this method to pass additional keyword arguments to the form.
"""
entity = self.request.user.dealer.entity
kwargs = super().get_form_kwargs()
kwargs['entity_slug'] = entity.slug # Get entity_slug from URL
kwargs['user_model'] = entity.admin # Get user_model from the request
return kwargs
class BankAccountDetailView(LoginRequiredMixin, DetailView):
model = BankAccountModel
template_name = "ledger/bank_accounts/bank_account_detail.html"
context_object_name = "bank_account"
class BankAccountUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = BankAccountModel
form_class = BankAccountUpdateForm
template_name = "ledger/bank_accounts/bank_account_form.html"
success_url = reverse_lazy("bank_account_list")
success_message = "Bank account updated successfully."
def get_form_kwargs(self):
"""
Override this method to pass additional keyword arguments to the form.
"""
entity = self.request.user.dealer.entity
kwargs = super().get_form_kwargs()
kwargs['entity_slug'] = entity.slug # Get entity_slug from URL
kwargs['user_model'] = entity.admin # Get user_model from the request
return kwargs
@login_required
def bank_account_delete(request, pk):
bank_account = get_object_or_404(BankAccountModel, pk=pk)
if request.method == "POST":
bank_account.delete()
messages.success(request, "Bank account deleted successfully.")
return redirect("bank_account_list")
return render(request, "ledger/bank_accounts/bank_account_delete.html", {"bank_account": bank_account})
# Accounts
class AccountListView(LoginRequiredMixin, ListView):
model = AccountModel
template_name = "ledger/coa_accounts/account_list.html"
context_object_name = "accounts"
def get_queryset(self):
entity = self.request.user.dealer.entity
qs = entity.get_all_accounts()
return qs
class AccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = AccountModel
form_class = AccountModelCreateForm
template_name = "ledger/coa_accounts/account_form.html"
success_url = reverse_lazy("account_list")
success_message = "Account created successfully."
def form_valid(self, form):
form.instance.entity_model = self.request.user.dealer.entity
form.instance.depth = 0
return super().form_valid(form)
def get_form_kwargs(self):
"""
Override this method to pass additional keyword arguments to the form.
"""
entity = self.request.user.dealer.entity
kwargs = super().get_form_kwargs()
kwargs['coa_model'] = entity.get_default_coa()
return kwargs
class AccountDetailView(LoginRequiredMixin, DetailView):
model = AccountModel
template_name = "ledger/coa_accounts/account_detail.html"
context_object_name = "account"
class AccountUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
model = AccountModel
form_class = AccountModelUpdateForm
template_name = "ledger/coa_accounts/account_form.html"
success_url = reverse_lazy("account_list")
success_message = "Account updated successfully."
@login_required
def account_delete(request, pk):
account = get_object_or_404(AccountModel, pk=pk)
if request.method == "POST":
account.delete()
messages.success(request, "Account deleted successfully.")
return redirect("account_list")
return render(request, "ledger/coa_accounts/account_delete.html", {"account": account})
#Estimates
class EstimateListView(LoginRequiredMixin, ListView):
model = EstimateModel
template_name = "sales/estimates/estimate_list.html"
context_object_name = "estimates"
class EstimateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = EstimateModel
form_class = EstimateModelCreateForm
template_name = "sales/estimates/estimate_form.html"
success_url = reverse_lazy("estimate_list")
success_message = "Estimate created successfully."
def get_form_kwargs(self):
"""
Override this method to pass additional keyword arguments to the form.
"""
entity = self.request.user.dealer.entity
kwargs = super().get_form_kwargs()
kwargs['entity_slug'] = entity.slug
kwargs['user_model'] = entity.admin
return kwargs
def get_context_data(self, **kwargs):
entity = self.request.user.dealer.entity
kwargs['items'] = entity.get_items_all()
return super().get_context_data(**kwargs)
def get_customer_queryset(self):
entity = self.request.user.dealer.entity
return entity.get_customer_queryset()
def form_valid(self, form):
form.instance.entity = self.request.user.dealer.entity
return super().form_valid(form)
@csrf_exempt
def create_estimate(request):
entity = request.user.dealer.entity
if request.method == 'POST':
try:
data = json.loads(request.body)
title = data['title']
customer_id = data['customer']
terms = data['terms']
customer = entity.get_customers().filter(pk=customer_id).first()
estimate = entity.create_estimate(
estimate_title=title,
customer_model=customer,
contract_terms=terms)
items = data.get('item[]', [])
items = [entity.get_items_all().filter(pk=item).first() for item in items]
quantities = data.get('quantity[]', [])
unit_costs = data.get('unitCost[]', [])
unit_sales_prices = data.get('unitSalesPrice[]', [])
total_costs = data.get('totalCost[]', [])
total_revenues = data.get('totalRevenue[]', [])
estimate_itemtxs = {
item.item_number: {
'unit_cost': float(unit_costs[i]),
'unit_revenue': float(unit_sales_prices[i]),
'quantity': float(quantities[i]),
'total_amount': float(total_costs[i])
}
for i, item in enumerate(items)
}
estimate.migrate_itemtxs(
itemtxs=estimate_itemtxs,
commit=True,
operation=EstimateModel.ITEMIZE_APPEND
)
url = reverse_lazy('estimate_detail', kwargs={'pk': estimate.pk})
return JsonResponse({'status': 'success', 'message': 'Estimate created successfully!','url':url})
except Exception as e:
return JsonResponse({'status': 'error', 'message': f'An error occurred while processing the request.{e}'}, status=400)
form = EstimateModelCreateForm(entity_slug=entity.slug,user_model=entity.admin)
context = {'form': form,"items":entity.get_items_all()}
return render(request, 'sales/estimates/estimate_form.html', context)
class EstimateDetailView(LoginRequiredMixin, DetailView):
model = EstimateModel
template_name = "sales/estimates/estimate_detail.html"
context_object_name = "estimate"
def get_context_data(self, **kwargs):
kwargs["total_unit_price"] = self.object.get_itemtxs_data()[0].first().ce_unit_cost_estimate * self.object.get_itemtxs_data()[0].first().ce_quantity
return super().get_context_data(**kwargs)

View File

@ -803,6 +803,132 @@
}
</script>
{% block extra_js %}{% endblock extra_js %}
<script>
// Function to calculate Total Cost and Total Revenue
function calculateTotals(container) {
const quantity = parseFloat(container.querySelector('.quantity').value) || 0;
const unitCost = parseFloat(container.querySelector('.unitCost').value) || 0;
const unitSalesPrice = parseFloat(container.querySelector('.unitSalesPrice').value) || 0;
const totalCost = quantity * unitCost;
const totalRevenue = quantity * unitSalesPrice;
container.querySelector('.totalCost').value = totalCost.toFixed(2);
container.querySelector('.totalRevenue').value = totalRevenue.toFixed(2);
}
// Add event listeners to inputs for dynamic calculation
function addInputListeners(container) {
container.querySelectorAll('.quantity, .unitCost, .unitSalesPrice').forEach(input => {
input.addEventListener('input', () => calculateTotals(container));
});
}
// Add new form fields
document.getElementById('addMoreBtn').addEventListener('click', function(e) {
e.preventDefault();
const formContainer = document.getElementById('formContainer');
const newForm = document.createElement('div');
newForm.className = 'form-container row g-3 mb-3 mt-5';
newForm.innerHTML = `
<div class="mb-2 col-sm-2">
<select class="form-control item" name="item[]" required>
{% for item in items %}
<option value="{{ item.pk }}">{{ item.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control unitCost" type="number" placeholder="Unit Cost" name="unitCost[]" step="0.01" required>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control unitSalesPrice" type="number" placeholder="Unit Sales Price" name="unitSalesPrice[]" step="0.01" required>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control totalCost" type="number" placeholder="Total Cost" name="totalCost[]" readonly>
</div>
<div class="mb-2 col-sm-1">
<input class="form-control totalRevenue" type="number" placeholder="Total Revenue" name="totalRevenue[]" readonly>
</div>
<div class="mb-2 col-sm-1">
<button class="btn btn-danger removeBtn">Remove</button>
</div>
`;
formContainer.appendChild(newForm);
addInputListeners(newForm); // Add listeners to the new form
// Add remove button functionality
newForm.querySelector('.removeBtn').addEventListener('click', function() {
newForm.remove();
});
});
// Add listeners to the initial form
document.querySelectorAll('.form-container').forEach(container => {
addInputListeners(container);
// Add remove button functionality to the initial form
container.querySelector('.removeBtn').addEventListener('click', function() {
container.remove();
});
});
document.getElementById('mainForm').addEventListener('submit', function(e) {
e.preventDefault();
// Collect all form data
const formData = new FormData(this);
const csrfToken = getCookie('csrftoken');
const data = {};
formData.forEach((value, key) => {
// Handle multi-value fields (e.g., item[], quantity[])
if (data[key]) {
if (!Array.isArray(data[key])) {
data[key] = [data[key]]; // Convert to array
}
data[key].push(value);
} else {
data[key] = value;
}
});
// Send data to the server using fetch
fetch('http://10.10.1.120:8888/en/sales/estimates/create/', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
if(data.status == "error"){
notify("error",data.message);
}
else{
notify("success","Estimate created successfully");
setTimeout(() => {
window.location.href = data.url;
}, 1000);
}
})
.catch(error => {
console.error('Error:', error);
notify("error",error);
alert('An error occurred while submitting the form.');
});
});
</script>
<!-- ===============================================-->
<!-- JavaScripts-->
@ -822,7 +948,6 @@
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
</body>
</html>

View File

@ -0,0 +1,73 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{{ _("View Bank Account") }}{% endblock title %}
{% block content %}
<!-- Delete Modal -->
<div class="modal fade" id="deleteModal"
data-bs-backdrop="static"
data-bs-keyboard="false"
tabindex="-1"
aria-labelledby="deleteModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-sm ">
<div class="modal-content rounded">
<div class="modal-body d-flex justify-content-center">
<h1 class="text-danger me-2"><i class="bi bi-exclamation-diamond-fill"></i></h1>
<span class="text-danger">
{% trans "Are you sure you want to delete this bank account?" %}
</span>
</div>
<div class="btn-group">
<button type="button"
class="btn btn-sm btn-secondary"
data-bs-dismiss="modal">
{% trans 'No' %}
</button>
<a type="button"
class="btn btn-sm btn-danger"
href="{% url 'bank_account_delete' bank_account.pk %}">
{% trans 'Yes' %}
</a>
</div>
</div>
</div>
</div>
<div class="container my-5">
<div class="card rounded ">
<div class="card-header bg-primary text-white ">
<p class="mb-0">{{ _("Bank Account Details") }}</p>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p><strong>{{ _("Bank Account Name") }}:</strong> {{ bank_account.name }}</p>
<p><strong>{{ _("Cash Account") }}:</strong> {{ bank_account.cash_account }}</p>
</div>
<div class="col-md-6">
<p><strong>{{ _("Amount") }}:</strong> {{ bank_account.amount }}</p>
</div>
</div>
</div>
<div class="card-footer d-flex ">
<a class="btn btn-sm btn-primary me-1" href="{% url 'bank_account_update' bank_account.pk %}">
<!--<i class="bi bi-pencil-square"></i> -->
{{ _("Edit") }}
</a>
<a class="btn btn-sm btn-danger me-1"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
<!--<i class="bi bi-trash-fill"></i>-->
{{ _("Delete") }}
</a>
<a class="btn btn-sm btn-secondary"
href="{% url 'bank_account_list' %}">
<!--<i class="bi bi-arrow-left-square-fill"></i>-->
{% trans "Back to List" %}
</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% load i18n %}
{% load crispy_forms_filters %}
{% block title %}{% trans "bank account" %}{% endblock title %}
{% block content %}
<div class="container my-5">
<!-- Display Form Errors -->
<div class="card shadow rounded">
<div class="card-header bg-primary text-white">
<p class="mb-0">
{% if customer.created %}
<!--<i class="bi bi-pencil-square"></i>-->
{{ _("Edit Bank Account") }}
{% else %}
<!--<i class="bi bi-person-plus"></i> -->
{{ _("Add Bank Account") }}
{% endif %}
</p>
</div>
<div class="card-body">
<form method="post" class="form" novalidate>
{% csrf_token %}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<div class="d-flex justify-content-end">
<button class="btn btn-sm btn-success me-1" type="submit">
<!--<i class="bi bi-save"></i> -->
{{ _("Save") }}
</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger">{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,126 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "Bank Accounts" %}{% endblock title %}
{% block customers %}
<a class="nav-link active fw-bold">
{% trans "Customers"|capfirst %}
<span class="visually-hidden">(current)</span>
</a>
{% endblock %}
{% block content %}
<div class="d-flex flex-column min-vh-100">
<div class="d-flex flex-column flex-sm-grow-1 ms-sm-14 p-4">
<main class="d-grid gap-4 p-1">
<!-- Search Bar -->
<div class="row g-4">
<div class="col-12">
<div class="container-fluid p-2">
<form method="get">
<div class="input-group input-group-sm">
<button class="btn btn-sm btn-secondary rounded-start" type="submit">
{% trans "search" %}
</button>
<input type="text"
name="q"
class="form-control form-control-sm rounded-end"
value="{{ request.GET.q }}"
placeholder="{% trans 'Search accounts...' %}" />
{% if request.GET.q %}
<a href="{% url request.resolver_match.view_name %}"
class="btn btn-sm btn-outline-danger ms-1 rounded">
<i class="bi bi-x-lg"></i>
</a>
{% endif %}
</div>
</form>
</div>
</div>
</div>
<!-- Customer Table -->
<div class="row g-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">{% trans "Customers List" %}</h5>
</div>
<div class="card-body p-0">
<table class="table table-hover table-sm mb-0">
<thead class="table-light">
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Type" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for account in bank_accounts %}
<tr>
<td>{{ account.name }}</td>
<td>{{ account.cash_account }}</td>
<td class="text-center">
<a href="{% url 'bank_account_detail' account.pk %}"
class="btn btn-sm btn-success">
{% trans "view" %}
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center text-muted">
{% trans "No customers found." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="card-footer bg-light">
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="{% trans 'Previous' %}">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&laquo;</span>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="{% trans 'Next' %}">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&raquo;</span>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
</div>
</div>
</div>
</main>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,77 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{{ _("View Account") }}{% endblock title %}
{% block content %}
<!-- Delete Modal -->
<div class="modal fade" id="deleteModal"
data-bs-backdrop="static"
data-bs-keyboard="false"
tabindex="-1"
aria-labelledby="deleteModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-sm ">
<div class="modal-content rounded">
<div class="modal-body d-flex justify-content-center">
<h1 class="text-danger me-2"><i class="bi bi-exclamation-diamond-fill"></i></h1>
<span class="text-danger">
{% trans "Are you sure you want to delete this account?" %}
</span>
</div>
<div class="btn-group">
<button type="button"
class="btn btn-sm btn-secondary"
data-bs-dismiss="modal">
{% trans 'No' %}
</button>
<div class="btn btn-sm btn-danger">
<form action="{% url 'account_delete' account.pk %}" method="post">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-danger">
{% trans 'Yes' %}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="container my-5">
<div class="card rounded ">
<div class="card-header bg-primary text-white ">
<p class="mb-0">{{ _("Bank Account Details") }}</p>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p><strong>{{ _("Account Name") }}:</strong> {{ account.name }}</p>
<p><strong>{{ _("Account Code") }}:</strong> {{ account.code }}</p>
</div>
<div class="col-md-6">
<p><strong>{{ _("Balance Type") }}:</strong> {{ account.balance_type }}</p>
<p><strong>{{ _("Active") }}:</strong> {{ account.active }}</p>
</div>
</div>
</div>
<div class="card-footer d-flex ">
<a class="btn btn-sm btn-primary me-1" href="{% url 'account_update' account.pk %}">
<!--<i class="bi bi-pencil-square"></i> -->
{{ _("Edit") }}
</a>
<a class="btn btn-sm btn-danger me-1"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
<!--<i class="bi bi-trash-fill"></i>-->
{{ _("Delete") }}
</a>
<a class="btn btn-sm btn-secondary"
href="{% url 'account_list' %}">
<!--<i class="bi bi-arrow-left-square-fill"></i>-->
{% trans "Back to List" %}
</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% load i18n %}
{% load crispy_forms_filters %}
{% block title %}{% trans "account" %}{% endblock title %}
{% block content %}
<div class="container my-5">
<!-- Display Form Errors -->
<div class="card shadow rounded">
<div class="card-header bg-primary text-white">
<p class="mb-0">
{% if account.created %}
<!--<i class="bi bi-pencil-square"></i>-->
{{ _("Edit Account") }}
{% else %}
<!--<i class="bi bi-person-plus"></i> -->
{{ _("Add Account") }}
{% endif %}
</p>
</div>
<div class="card-body">
<form method="post" class="form" novalidate>
{% csrf_token %}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<div class="d-flex justify-content-end">
<button class="btn btn-sm btn-success me-1" type="submit">
<!--<i class="bi bi-save"></i> -->
{{ _("Save") }}
</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger">{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,130 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "Accounts" %}{% endblock title %}
{% block customers %}
<a class="nav-link active fw-bold">
{% trans "Accounts"|capfirst %}
<span class="visually-hidden">(current)</span>
</a>
{% endblock %}
{% block content %}
<div class="d-flex flex-column min-vh-100">
<div class="d-flex flex-column flex-sm-grow-1 ms-sm-14 p-4">
<main class="d-grid gap-4 p-1">
<!-- Search Bar -->
<div class="row g-4">
<div class="col-12">
<div class="container-fluid p-2">
<form method="get">
<div class="input-group input-group-sm">
<button class="btn btn-sm btn-secondary rounded-start" type="submit">
{% trans "search" %}
</button>
<input type="text"
name="q"
class="form-control form-control-sm rounded-end"
value="{{ request.GET.q }}"
placeholder="{% trans 'Search accounts...' %}" />
{% if request.GET.q %}
<a href="{% url request.resolver_match.view_name %}"
class="btn btn-sm btn-outline-danger ms-1 rounded">
<i class="bi bi-x-lg"></i>
</a>
{% endif %}
</div>
</form>
</div>
</div>
</div>
<!-- Customer Table -->
<div class="row g-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">{% trans "Account List" %}</h5>
</div>
<div class="card-body p-0">
<table class="table table-hover table-sm mb-0">
<thead class="table-light">
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Code" %}</th>
<th>{% trans "Balance Type" %}</th>
<th>{% trans "Active" %}</th>
<th class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for account in accounts %}
<tr>
<td>{{ account.name }}</td>
<td>{{ account.code }}</td>
<td>{{ account.balance_type }}</td>
<td>{{ account.active }}</td>
<td class="text-center">
<a href="{% url 'account_detail' account.pk %}"
class="btn btn-sm btn-success">
{% trans "view" %}
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center text-muted">
{% trans "No account found." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="card-footer bg-light">
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="{% trans 'Previous' %}">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&laquo;</span>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="{% trans 'Next' %}">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link" aria-hidden="true">&raquo;</span>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
</div>
</div>
</div>
</main>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,147 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{{ _("View Estimate") }}{% endblock title %}
{% block content %}
<!-- ============================================-->
<!-- <section> begin ============================-->
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
<div class="container-small mt-3">
<div class="d-flex justify-content-between align-items-end mb-4">
<h2 class="mb-0">Estimate</h2>
<div>
<button class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">Send Estimate</span></button>
<button class="btn btn-phoenix-secondary"><span class="d-none d-sm-inline-block">Mark As Sent</span></button>
</div>
</div>
<div class="bg-body dark__bg-gray-1100 p-4 mb-4 rounded-2">
<div class="row g-4">
<div class="col-12 col-lg-3">
<div class="row g-4 g-lg-2">
<div class="col-12 col-sm-6 col-lg-12">
<div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5">
<h6 class="mb-0 me-3">Estimate No :</h6>
</div>
<div class="col-auto col-lg-6 col-xl-7">
<p class="fs-9 text-body-secondary fw-semibold mb-0">#{{estimate.estimate_number}}</p>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-12">
<div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5">
<h6 class="me-3">Estimate Date :</h6>
</div>
<div class="col-auto col-lg-6 col-xl-7">
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{estimate.created}}</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-5">
<div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5">
<h6 class="mb-2 me-3">Customer :</h6>
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{estimate.customer.customer_name}}</p>
</div>
<div class="col-12 col-lg-4">
<h6 class="mb-2"> Email :</h6>
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{estimate.customer.email}}</p>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<div class="row g-4">
<div class="col-12 col-lg-6">
<h6 class="mb-2"> Estimate Status :</h6>
<div class="fs-9 text-body-secondary fw-semibold mb-0">
{% if estimate.status == 'draft' %}
<span class="badge text-bg-warning">Draft</span>
{% elif estimate.status == 'sent' %}
<span class="badge text-bg-info">Sent</span>
{% elif estimate.status == 'approved' %}
<span class="badge text-bg-success">Approved</span>
{% elif estimate.status == 'declined' %}
<span class="badge text-bg-danger">Declined</span>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="px-0">
<div class="table-responsive scrollbar">
<table id="estimate-table" class="table fs-9 text-body mb-0">
<thead class="bg-body-secondary">
<tr>
<th scope="col" style="width: 24px;">#</th>
<th scope="col" style="min-width: 360px;">Item</th>
<th class="text-start" scope="col" style="width: 80px;">Quantity</th>
<th scope="col" style="min-width: 60px;">Unit Price</th>
<th class="ps-5" scope="col" style="min-width: 150px;">Total</th>
</tr>
</thead>
<tbody>
{% for item in estimate.get_itemtxs_data.0 %}
<tr>
<td class="">{{forloop.counter}}</td>
<td class="">{{item.item_model.name}}</td>
<td class="align-middle">{{item.ce_quantity}}</td>
<td class="align-middle ps-5">{{item.ce_unit_cost_estimate}}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{item.ce_total_amount}}</td>
</tr>
{% endfor %}
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-bold text-body-highlight" colspan="4">Grand Total</td>
<td class="align-middle text-start fw-bold">
<span id="grand-total">0.00</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- end of .container-->
</section>
<!-- <section> close ============================-->
<!-- ============================================-->
{% endblock %}
{% block extra_js %}
<script>
function calculateTotals() {
const table = document.getElementById('estimate-table');
const rows = table.getElementsByTagName('tbody')[0].rows;
let grandTotal = 0;
for (let row of rows) {
// Ensure the row has the expected number of cells
if (row.cells.length >= 5) {
const quantity = parseFloat(row.cells[2].textContent); // Quantity column
const unitPrice = parseFloat(row.cells[3].textContent); // Unit Price column
if (!isNaN(quantity) && !isNaN(unitPrice)) {
const total = quantity * unitPrice;
row.cells[4].textContent = total.toFixed(2); // Populate Total column
grandTotal += total; // Add to grand total
}
}
}
// Display the grand total
document.getElementById('grand-total').textContent = grandTotal.toFixed(2);
}
// Run the function on page load
window.onload = calculateTotals;
</script>
{% endblock %}

View File

@ -0,0 +1,58 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n static %}
{% block title %}{{ _("Create Estimate") }}{% endblock title %}
{% block content %}
<div class="container mt-4">
<h3 class="text-center">{% trans "Create Estimate" %}</h3>
<form id="mainForm" method="post" class="needs-validation" novalidate>
{% csrf_token %}
<div class="row g-3">
{{ form|crispy }}
<div class="container mt-5">
<div id="formContainer">
<h3 class="text-start">Unit Items</h3>
<div class="form-container row g-3 mb-3 mt-5">
<div class="mb-2 col-sm-2">
<select class="form-control item" name="item[]" required>
{% for item in items %}
<option value="{{ item.pk }}">{{ item.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control unitCost" type="number" placeholder="Unit Cost" name="unitCost[]" step="0.01" required>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control unitSalesPrice" type="number" placeholder="Unit Sales Price" name="unitSalesPrice[]" step="0.01" required>
</div>
<div class="mb-2 col-sm-2">
<input class="form-control totalCost" type="number" placeholder="Total Cost" name="totalCost[]" readonly>
</div>
<div class="mb-2 col-sm-1">
<input class="form-control totalRevenue" type="number" placeholder="Total Revenue" name="totalRevenue[]" readonly>
</div>
<div class="mb-2 col-sm-1">
<button class="btn btn-danger removeBtn">Remove</button>
</div>
</div>
</div>
<div class="col-12">
<button id="addMoreBtn" class="btn btn-primary">Add More</button>
</div>
</div>
</div>
<!-- Buttons -->
<div class="mt-5 text-center">
<button type="submit" class="btn btn-success me-2">{% trans "Save" %}</button>
<a href="{% url 'estimate_list' %}" class="btn btn-secondary">{% trans "Cancel" %}</a>
</div>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,50 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{{ _("Quotations") }}{% endblock title %}
{% block content %}
<div class="container mt-4">
<h3 class="text-center">{% trans "Quotations" %}</h3>
<div class="mx-n4 px-4 mx-lg-n6 px-lg-6 bg-body-emphasis pt-7 border-y">
<div class="table-responsive mx-n1 px-1 scrollbar">
<table class="table fs-9 mb-0 border-top border-translucent">
<thead>
<tr>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Estimate Number" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Customer" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Status" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Status Date" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Created" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody class="list">
{% for estimate in estimates %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap py-0">{{ estimate.estimate_number }}</td>
<td class="align-middle product white-space-nowrap">{{ estimate.customer }}</td>
<td class="align-middle product white-space-nowrap">{{ estimate.status }}</td>
<td class="align-middle product white-space-nowrap">{{ estimate.get_status_action_date }}</td>
<td class="align-middle product white-space-nowrap">{{ estimate.created }}</td>
<td class="text-center">
<a href="{% url 'estimate_detail' estimate.pk %}"
class="btn btn-sm btn-success">
{% trans "view" %}
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">{% trans "No Quotations Found" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="d-flex justify-content-center">
</div>
</div>
</div>
{% endblock %}