estimate and bank accounts
This commit is contained in:
parent
855c8f6c80
commit
239ebb8a65
@ -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)
|
||||
@ -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'),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
@ -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>
|
||||
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