estimate and bank accounts
This commit is contained in:
parent
855c8f6c80
commit
239ebb8a65
@ -24,6 +24,7 @@ from .models import (
|
|||||||
AdditionalServices
|
AdditionalServices
|
||||||
|
|
||||||
)
|
)
|
||||||
|
from django_ledger.models import ItemModel
|
||||||
from django.forms import ModelMultipleChoiceField, ValidationError
|
from django.forms import ModelMultipleChoiceField, ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
@ -403,3 +404,11 @@ class WizardForm3(forms.Form):
|
|||||||
|
|
||||||
if password and confirm_password and password != confirm_password:
|
if password and confirm_password and password != confirm_password:
|
||||||
raise forms.ValidationError("Passwords do not match.")
|
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)
|
||||||
@ -105,6 +105,24 @@ urlpatterns = [
|
|||||||
path('representatives/create/', views.RepresentativeCreateView.as_view(), name='representative_create'),
|
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>/update/', views.RepresentativeUpdateView.as_view(), name='representative_update'),
|
||||||
path('representatives/<int:pk>/delete/', views.RepresentativeDeleteView.as_view(), name='representative_delete'),
|
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'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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 logging
|
||||||
import json
|
import json
|
||||||
import datetime
|
import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from django_ledger.models import TransactionModel, AccountModel,JournalEntryModel
|
|
||||||
from django.shortcuts import HttpResponse
|
from django.shortcuts import HttpResponse
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
# from weasyprint import HTML
|
# from weasyprint import HTML
|
||||||
@ -1492,3 +1496,222 @@ def payment_create(request, pk):
|
|||||||
else:
|
else:
|
||||||
form = forms.PaymentForm()
|
form = forms.PaymentForm()
|
||||||
return render(request, "sales/payments/payment_create.html", {"quotation": quotation,"form": form})
|
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)
|
||||||
@ -803,6 +803,132 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</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-->
|
<!-- JavaScripts-->
|
||||||
@ -822,7 +948,6 @@
|
|||||||
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
|
<script src="{% static 'vendors/mapbox-gl/mapbox-gl.js' %}"></script>
|
||||||
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
|
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script>
|
||||||
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
|
<script src="{% static 'vendors/swiper/swiper-bundle.min.js' %}"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
73
templates/ledger/bank_accounts/bank_account_detail.html
Normal file
73
templates/ledger/bank_accounts/bank_account_detail.html
Normal 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 %}
|
||||||
39
templates/ledger/bank_accounts/bank_account_form.html
Normal file
39
templates/ledger/bank_accounts/bank_account_form.html
Normal 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 %}
|
||||||
126
templates/ledger/bank_accounts/bank_account_list.html
Normal file
126
templates/ledger/bank_accounts/bank_account_list.html
Normal 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">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link" aria-hidden="true">«</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">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link" aria-hidden="true">»</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
77
templates/ledger/coa_accounts/account_detail.html
Normal file
77
templates/ledger/coa_accounts/account_detail.html
Normal 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 %}
|
||||||
39
templates/ledger/coa_accounts/account_form.html
Normal file
39
templates/ledger/coa_accounts/account_form.html
Normal 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 %}
|
||||||
130
templates/ledger/coa_accounts/account_list.html
Normal file
130
templates/ledger/coa_accounts/account_list.html
Normal 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">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link" aria-hidden="true">«</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">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link" aria-hidden="true">»</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
147
templates/sales/estimates/estimate_detail.html
Normal file
147
templates/sales/estimates/estimate_detail.html
Normal 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 %}
|
||||||
58
templates/sales/estimates/estimate_form.html
Normal file
58
templates/sales/estimates/estimate_form.html
Normal 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 %}
|
||||||
50
templates/sales/estimates/estimate_list.html
Normal file
50
templates/sales/estimates/estimate_list.html
Normal 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 %}
|
||||||
Loading…
x
Reference in New Issue
Block a user