adding arabic translation

This commit is contained in:
Flatlogic Bot 2026-02-03 03:44:40 +00:00
parent bdafaca493
commit b0192498f4
16 changed files with 3638 additions and 62 deletions

Binary file not shown.

26
accounting/forms.py Normal file
View File

@ -0,0 +1,26 @@
from django import forms
from .models import Account, JournalEntry, JournalItem
from django.utils.translation import gettext_lazy as _
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = ['code', 'name_en', 'name_ar', 'account_type', 'description', 'is_active']
widgets = {
'code': forms.TextInput(attrs={'class': 'form-control'}),
'name_en': forms.TextInput(attrs={'class': 'form-control'}),
'name_ar': forms.TextInput(attrs={'class': 'form-control'}),
'account_type': forms.Select(attrs={'class': 'form-select'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
class JournalEntryForm(forms.ModelForm):
class Meta:
model = JournalEntry
fields = ['date', 'description', 'reference']
widgets = {
'date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
'reference': forms.TextInput(attrs={'class': 'form-control'}),
}

View File

@ -0,0 +1,67 @@
{% extends 'base.html' %}
{% load i18n %}
{% block content %}
<div class="container-fluid py-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-1">
<li class="breadcrumb-item"><a href="{% url 'accounting_dashboard' %}">{% trans "Accounting" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'chart_of_accounts' %}">{% trans "Chart of Accounts" %}</a></li>
<li class="breadcrumb-item active">{% if account %}{% trans "Edit Account" %}{% else %}{% trans "Add Account" %}{% endif %}</li>
</ol>
</nav>
<h2 class="mb-0">{% if account %}{% trans "Edit Account" %}{% else %}{% trans "Add Account" %}{% endif %}</h2>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-4">
<form method="post">
{% csrf_token %}
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">{% trans "Account Code" %}</label>
{{ form.code }}
{% if form.code.errors %}<div class="text-danger small">{{ form.code.errors }}</div>{% endif %}
</div>
<div class="col-md-8">
<label class="form-label">{% trans "Account Type" %}</label>
{{ form.account_type }}
{% if form.account_type.errors %}<div class="text-danger small">{{ form.account_type.errors }}</div>{% endif %}
</div>
<div class="col-md-6">
<label class="form-label">{% trans "Name (English)" %}</label>
{{ form.name_en }}
{% if form.name_en.errors %}<div class="text-danger small">{{ form.name_en.errors }}</div>{% endif %}
</div>
<div class="col-md-6">
<label class="form-label">{% trans "Name (Arabic)" %}</label>
{{ form.name_ar }}
{% if form.name_ar.errors %}<div class="text-danger small">{{ form.name_ar.errors }}</div>{% endif %}
</div>
<div class="col-12">
<label class="form-label">{% trans "Description" %}</label>
{{ form.description }}
</div>
<div class="col-12">
<div class="form-check">
{{ form.is_active }}
<label class="form-check-label">{% trans "Is Active" %}</label>
</div>
</div>
</div>
<div class="mt-4 pt-3 border-top d-flex justify-content-end gap-2">
<a href="{% url 'chart_of_accounts' %}" class="btn btn-light">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-primary px-4">{% trans "Save Account" %}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -13,6 +13,9 @@
</nav> </nav>
<h2 class="mb-0">{% trans "Chart of Accounts" %}</h2> <h2 class="mb-0">{% trans "Chart of Accounts" %}</h2>
</div> </div>
<a href="{% url 'account_create' %}" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> {% trans "Add Account" %}
</a>
</div> </div>
<div class="card border-0 shadow-sm"> <div class="card border-0 shadow-sm">
@ -43,9 +46,14 @@
{{ account.balance|floatformat:global_settings.decimal_places }} {{ global_settings.currency_symbol }} {{ account.balance|floatformat:global_settings.decimal_places }} {{ global_settings.currency_symbol }}
</td> </td>
<td class="text-center"> <td class="text-center">
<a href="{% url 'account_ledger' account.id %}" class="btn btn-sm btn-outline-primary"> <div class="btn-group">
<i class="bi bi-list-columns"></i> {% trans "Ledger" %} <a href="{% url 'account_ledger' account.id %}" class="btn btn-sm btn-outline-primary" title="{% trans 'Ledger' %}">
</a> <i class="bi bi-list-columns"></i>
</a>
<a href="{% url 'account_edit' account.id %}" class="btn btn-sm btn-outline-secondary" title="{% trans 'Edit' %}">
<i class="bi bi-pencil"></i>
</a>
</div>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -54,4 +62,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -13,6 +13,9 @@
</nav> </nav>
<h2 class="mb-0">{% trans "Journal Entries" %}</h2> <h2 class="mb-0">{% trans "Journal Entries" %}</h2>
</div> </div>
<a href="{% url 'manual_journal_entry' %}" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> {% trans "New Manual Entry" %}
</a>
</div> </div>
{% for entry in entries %} {% for entry in entries %}
@ -63,4 +66,4 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,202 @@
{% extends 'base.html' %}
{% load i18n %}
{% block content %}
<div class="container-fluid py-4">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-1">
<li class="breadcrumb-item"><a href="{% url 'accounting_dashboard' %}">{% trans "Accounting" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'journal_entries' %}">{% trans "Journal Entries" %}</a></li>
<li class="breadcrumb-item active">{% trans "New Manual Entry" %}</li>
</ol>
</nav>
<h2 class="mb-0">{% trans "New Manual Journal Entry" %}</h2>
</div>
</div>
<form method="post" id="journal-form">
{% csrf_token %}
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">{% trans "Date" %}</label>
{{ form.date }}
</div>
<div class="col-md-3">
<label class="form-label">{% trans "Reference" %}</label>
{{ form.reference }}
</div>
<div class="col-md-6">
<label class="form-label">{% trans "Description" %}</label>
{{ form.description }}
</div>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0" id="items-table">
<thead class="bg-light">
<tr>
<th style="width: 40%;">{% trans "Account" %}</th>
<th style="width: 20%;">{% trans "Type" %}</th>
<th style="width: 30%;">{% trans "Amount" %}</th>
<th style="width: 10%;"></th>
</tr>
</thead>
<tbody>
<tr class="item-row">
<td>
<select name="account[]" class="form-select select2-account" required>
<option value="">{% trans "Select Account" %}</option>
{% for acc in accounts %}
<option value="{{ acc.id }}">{{ acc.code }} - {% if LANGUAGE_CODE == 'ar' %}{{ acc.name_ar }}{% else %}{{ acc.name_en }}{% endif %}</option>
{% endfor %}
</select>
</td>
<td>
<select name="type[]" class="form-select item-type" required>
<option value="debit">{% trans "Debit" %}</option>
<option value="credit">{% trans "Credit" %}</option>
</select>
</td>
<td>
<div class="input-group">
<input type="number" name="amount[]" class="form-control item-amount" step="0.001" min="0" required>
<span class="input-group-text">{{ global_settings.currency_symbol }}</span>
</div>
</td>
<td class="text-center">
<button type="button" class="btn btn-outline-danger btn-sm remove-row"><i class="bi bi-trash"></i></button>
</td>
</tr>
<tr class="item-row">
<td>
<select name="account[]" class="form-select select2-account" required>
<option value="">{% trans "Select Account" %}</option>
{% for acc in accounts %}
<option value="{{ acc.id }}">{{ acc.code }} - {% if LANGUAGE_CODE == 'ar' %}{{ acc.name_ar }}{% else %}{{ acc.name_en }}{% endif %}</option>
{% endfor %}
</select>
</td>
<td>
<select name="type[]" class="form-select item-type" required>
<option value="debit">{% trans "Debit" %}</option>
<option value="credit" selected>{% trans "Credit" %}</option>
</select>
</td>
<td>
<div class="input-group">
<input type="number" name="amount[]" class="form-control item-amount" step="0.001" min="0" required>
<span class="input-group-text">{{ global_settings.currency_symbol }}</span>
</div>
</td>
<td class="text-center">
<button type="button" class="btn btn-outline-danger btn-sm remove-row"><i class="bi bi-trash"></i></button>
</td>
</tr>
</tbody>
<tfoot class="bg-light">
<tr>
<td colspan="4">
<button type="button" class="btn btn-outline-primary btn-sm" id="add-row">
<i class="bi bi-plus-lg"></i> {% trans "Add Line" %}
</button>
</td>
</tr>
<tr class="fw-bold">
<td class="text-end">{% trans "Totals" %}:</td>
<td class="text-end">{% trans "Debit" %}: <span id="total-debit">0.000</span></td>
<td class="text-end">{% trans "Credit" %}: <span id="total-credit">0.000</span></td>
<td></td>
</tr>
<tr>
<td colspan="4" class="text-center py-2" id="balance-message">
<span class="badge bg-danger">{% trans "Out of Balance" %}</span>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="card-footer bg-white p-4 border-top d-flex justify-content-end gap-2">
<a href="{% url 'journal_entries' %}" class="btn btn-light">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-primary px-4" id="submit-btn" disabled>{% trans "Create Entry" %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const table = document.getElementById('items-table').getElementsByTagName('tbody')[0];
const addBtn = document.getElementById('add-row');
const totalDebitSpan = document.getElementById('total-debit');
const totalCreditSpan = document.getElementById('total-credit');
const balanceMessage = document.getElementById('balance-message');
const submitBtn = document.getElementById('submit-btn');
function updateTotals() {
let totalDebit = 0;
let totalCredit = 0;
document.querySelectorAll('.item-row').forEach(row => {
const type = row.querySelector('.item-type').value;
const amount = parseFloat(row.querySelector('.item-amount').value) || 0;
if (type === 'debit') totalDebit += amount;
else totalCredit += amount;
});
totalDebitSpan.textContent = totalDebit.toFixed(3);
totalCreditSpan.textContent = totalCredit.toFixed(3);
const balanced = totalDebit > 0 && Math.abs(totalDebit - totalCredit) < 0.001;
if (balanced) {
balanceMessage.innerHTML = '<span class="badge bg-success">{% trans "Balanced" %}</span>';
submitBtn.disabled = false;
} else {
balanceMessage.innerHTML = '<span class="badge bg-danger">{% trans "Out of Balance" %}</span>';
submitBtn.disabled = true;
}
}
addBtn.addEventListener('click', function() {
const firstRow = document.querySelector('.item-row');
const newRow = firstRow.cloneNode(true);
newRow.querySelector('.item-amount').value = '';
table.appendChild(newRow);
newRow.querySelector('.remove-row').addEventListener('click', function() {
if (document.querySelectorAll('.item-row').length > 2) {
newRow.remove();
updateTotals();
}
});
newRow.querySelector('.item-type').addEventListener('change', updateTotals);
newRow.querySelector('.item-amount').addEventListener('input', updateTotals);
});
document.querySelectorAll('.remove-row').forEach(btn => {
btn.addEventListener('click', function() {
if (document.querySelectorAll('.item-row').length > 2) {
btn.closest('.item-row').remove();
updateTotals();
}
});
});
document.querySelectorAll('.item-type').forEach(el => el.addEventListener('change', updateTotals));
document.querySelectorAll('.item-amount').forEach(el => el.addEventListener('input', updateTotals));
});
</script>
{% endblock %}

View File

@ -4,9 +4,12 @@ from . import views
urlpatterns = [ urlpatterns = [
path('', views.accounting_dashboard, name='accounting_dashboard'), path('', views.accounting_dashboard, name='accounting_dashboard'),
path('chart-of-accounts/', views.chart_of_accounts, name='chart_of_accounts'), path('chart-of-accounts/', views.chart_of_accounts, name='chart_of_accounts'),
path('chart-of-accounts/add/', views.account_create_update, name='account_create'),
path('chart-of-accounts/edit/<int:pk>/', views.account_create_update, name='account_edit'),
path('journal-entries/', views.journal_entries, name='journal_entries'), path('journal-entries/', views.journal_entries, name='journal_entries'),
path('journal-entries/manual/', views.manual_journal_entry, name='manual_journal_entry'),
path('ledger/<int:account_id>/', views.account_ledger, name='account_ledger'), path('ledger/<int:account_id>/', views.account_ledger, name='account_ledger'),
path('trial-balance/', views.trial_balance, name='trial_balance'), path('trial-balance/', views.trial_balance, name='trial_balance'),
path('balance-sheet/', views.balance_sheet, name='balance_sheet'), path('balance-sheet/', views.balance_sheet, name='balance_sheet'),
path('profit-loss/', views.profit_loss, name='profit_loss'), path('profit-loss/', views.profit_loss, name='profit_loss'),
] ]

View File

@ -1,9 +1,14 @@
from django.shortcuts import render, get_object_or_404 from django.utils.translation import gettext as _
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .models import Account, JournalEntry, JournalItem from .models import Account, JournalEntry, JournalItem
from .forms import AccountForm, JournalEntryForm
from django.db.models import Sum, Q from django.db.models import Sum, Q
from django.utils import timezone from django.utils import timezone
from datetime import datetime from datetime import datetime
from django.db import transaction
import json
@login_required @login_required
def accounting_dashboard(request): def accounting_dashboard(request):
@ -44,11 +49,84 @@ def chart_of_accounts(request):
accounts = Account.objects.all().order_by('code') accounts = Account.objects.all().order_by('code')
return render(request, 'accounting/chart_of_accounts.html', {'accounts': accounts}) return render(request, 'accounting/chart_of_accounts.html', {'accounts': accounts})
@login_required
def account_create_update(request, pk=None):
if pk:
account = get_object_or_404(Account, pk=pk)
else:
account = None
if request.method == 'POST':
form = AccountForm(request.POST, instance=account)
if form.is_valid():
form.save()
messages.success(request, _("Account saved successfully."))
return redirect('chart_of_accounts')
else:
form = AccountForm(instance=account)
return render(request, 'accounting/account_form.html', {'form': form, 'account': account})
@login_required @login_required
def journal_entries(request): def journal_entries(request):
entries = JournalEntry.objects.all().order_by('-date', '-id') entries = JournalEntry.objects.all().order_by('-date', '-id')
return render(request, 'accounting/journal_entries.html', {'entries': entries}) return render(request, 'accounting/journal_entries.html', {'entries': entries})
@login_required
def manual_journal_entry(request):
accounts = Account.objects.filter(is_active=True).order_by('code')
if request.method == 'POST':
form = JournalEntryForm(request.POST)
# Manual journal entry requires at least two items and they must balance
account_ids = request.POST.getlist('account[]')
types = request.POST.getlist('type[]')
amounts = request.POST.getlist('amount[]')
if form.is_valid():
try:
with transaction.atomic():
entry = form.save()
total_debit = 0
total_credit = 0
for i in range(len(account_ids)):
acc_id = account_ids[i]
item_type = types[i]
amount = float(amounts[i])
if amount <= 0: continue
JournalItem.objects.create(
entry=entry,
account_id=acc_id,
type=item_type,
amount=amount
)
if item_type == 'debit':
total_debit += amount
else:
total_credit += amount
if round(total_debit, 3) != round(total_credit, 3):
raise Exception(f"Journal entry does not balance. Total Debit: {total_debit}, Total Credit: {total_credit}")
if total_debit == 0:
raise Exception("Journal entry must have at least one debit and one credit.")
messages.success(request, _("Manual journal entry created successfully."))
return redirect('journal_entries')
except Exception as e:
messages.error(request, str(e))
else:
form = JournalEntryForm()
return render(request, 'accounting/journal_entry_form.html', {
'form': form,
'accounts': accounts
})
@login_required @login_required
def account_ledger(request, account_id): def account_ledger(request, account_id):
account = get_object_or_404(Account, id=account_id) account = get_object_or_404(Account, id=account_id)

View File

@ -24,7 +24,8 @@ def global_settings(request):
settings = SystemSetting.objects.create() settings = SystemSetting.objects.create()
return { return {
'site_settings': settings, 'site_settings': settings,
'global_settings': settings,
'decimal_places': settings.decimal_places if settings else 3 'decimal_places': settings.decimal_places if settings else 3
} }
except: except:
return {'decimal_places': 3} return {'decimal_places': 3}

View File

@ -1,3 +1,4 @@
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .utils import number_to_words_en from .utils import number_to_words_en
from django.core.paginator import Paginator from django.core.paginator import Paginator
@ -277,7 +278,7 @@ def add_purchase_payment(request, pk):
created_by=request.user created_by=request.user
) )
purchase.update_balance() purchase.update_balance()
messages.success(request, "Payment added successfully!") messages.success(request, _("Payment added successfully!"))
return redirect('purchases') return redirect('purchases')
@login_required @login_required
@ -288,7 +289,7 @@ def delete_purchase(request, pk):
item.product.save() item.product.save()
purchase.delete() purchase.delete()
messages.success(request, "Purchase deleted successfully!") messages.success(request, _("Purchase deleted successfully!"))
return redirect('purchases') return redirect('purchases')
# --- Sale Views --- # --- Sale Views ---
@ -552,7 +553,7 @@ def add_sale_payment(request, pk):
created_by=request.user created_by=request.user
) )
sale.update_balance() sale.update_balance()
messages.success(request, "Payment added successfully!") messages.success(request, _("Payment added successfully!"))
return redirect('invoices') return redirect('invoices')
@login_required @login_required
@ -562,7 +563,7 @@ def delete_sale(request, pk):
item.product.stock_quantity += item.quantity item.product.stock_quantity += item.quantity
item.product.save() item.product.save()
sale.delete() sale.delete()
messages.success(request, "Sale deleted successfully!") messages.success(request, _("Sale deleted successfully!"))
return redirect('invoices') return redirect('invoices')
# --- Quotation Views --- # --- Quotation Views ---
@ -641,7 +642,7 @@ def create_quotation_api(request):
def convert_quotation_to_invoice(request, pk): def convert_quotation_to_invoice(request, pk):
quotation = get_object_or_404(Quotation, pk=pk) quotation = get_object_or_404(Quotation, pk=pk)
if quotation.status == 'converted': if quotation.status == 'converted':
messages.warning(request, "This quotation has already been converted to an invoice.") messages.warning(request, _("This quotation has already been converted to an invoice."))
return redirect('invoices') return redirect('invoices')
# Create Sale from Quotation # Create Sale from Quotation
@ -674,14 +675,14 @@ def convert_quotation_to_invoice(request, pk):
quotation.status = 'converted' quotation.status = 'converted'
quotation.save() quotation.save()
messages.success(request, "Quotation converted to Invoice successfully!") messages.success(request, _("Quotation converted to Invoice successfully!"))
return redirect('invoice_detail', pk=sale.pk) return redirect('invoice_detail', pk=sale.pk)
@login_required @login_required
def delete_quotation(request, pk): def delete_quotation(request, pk):
quotation = get_object_or_404(Quotation, pk=pk) quotation = get_object_or_404(Quotation, pk=pk)
quotation.delete() quotation.delete()
messages.success(request, "Quotation deleted successfully!") messages.success(request, _("Quotation deleted successfully!"))
return redirect('quotations') return redirect('quotations')
# --- Sale Return Views --- # --- Sale Return Views ---
@ -770,7 +771,7 @@ def delete_sale_return(request, pk):
item.product.stock_quantity -= item.quantity item.product.stock_quantity -= item.quantity
item.product.save() item.product.save()
sale_return.delete() sale_return.delete()
messages.success(request, "Sale return deleted successfully!") messages.success(request, _("Sale return deleted successfully!"))
return redirect('sales_returns') return redirect('sales_returns')
@ -860,7 +861,7 @@ def delete_purchase_return(request, pk):
item.product.stock_quantity += item.quantity item.product.stock_quantity += item.quantity
item.product.save() item.product.save()
purchase_return.delete() purchase_return.delete()
messages.success(request, "Purchase return deleted successfully!") messages.success(request, _("Purchase return deleted successfully!"))
return redirect('purchase_returns') return redirect('purchase_returns')
# --- Other Management Views --- # --- Other Management Views ---
@ -917,7 +918,7 @@ def settings_view(request):
settings.logo = request.FILES['logo'] settings.logo = request.FILES['logo']
settings.save() settings.save()
messages.success(request, "Settings updated successfully!") messages.success(request, _("Settings updated successfully!"))
return redirect(reverse('settings') + '#profile') return redirect(reverse('settings') + '#profile')
payment_methods = PaymentMethod.objects.all() payment_methods = PaymentMethod.objects.all()
@ -936,7 +937,7 @@ def add_payment_method(request):
name_ar = request.POST.get('name_ar') name_ar = request.POST.get('name_ar')
is_active = request.POST.get('is_active') == 'on' is_active = request.POST.get('is_active') == 'on'
PaymentMethod.objects.create(name_en=name_en, name_ar=name_ar, is_active=is_active) PaymentMethod.objects.create(name_en=name_en, name_ar=name_ar, is_active=is_active)
messages.success(request, "Payment method added successfully!") messages.success(request, _("Payment method added successfully!"))
return redirect(reverse('settings') + '#payments') return redirect(reverse('settings') + '#payments')
@login_required @login_required
@ -947,14 +948,14 @@ def edit_payment_method(request, pk):
pm.name_ar = request.POST.get('name_ar') pm.name_ar = request.POST.get('name_ar')
pm.is_active = request.POST.get('is_active') == 'on' pm.is_active = request.POST.get('is_active') == 'on'
pm.save() pm.save()
messages.success(request, "Payment method updated successfully!") messages.success(request, _("Payment method updated successfully!"))
return redirect(reverse('settings') + '#payments') return redirect(reverse('settings') + '#payments')
@login_required @login_required
def delete_payment_method(request, pk): def delete_payment_method(request, pk):
pm = get_object_or_404(PaymentMethod, pk=pk) pm = get_object_or_404(PaymentMethod, pk=pk)
pm.delete() pm.delete()
messages.success(request, "Payment method deleted successfully!") messages.success(request, _("Payment method deleted successfully!"))
return redirect(reverse('settings') + '#payments') return redirect(reverse('settings') + '#payments')
@login_required @login_required
@ -965,7 +966,7 @@ def add_customer(request):
email = request.POST.get('email') email = request.POST.get('email')
address = request.POST.get('address') address = request.POST.get('address')
Customer.objects.create(name=name, phone=phone, email=email, address=address) Customer.objects.create(name=name, phone=phone, email=email, address=address)
messages.success(request, "Customer added successfully!") messages.success(request, _("Customer added successfully!"))
return redirect('customers') return redirect('customers')
@login_required @login_required
@ -977,14 +978,14 @@ def edit_customer(request, pk):
customer.email = request.POST.get('email') customer.email = request.POST.get('email')
customer.address = request.POST.get('address') customer.address = request.POST.get('address')
customer.save() customer.save()
messages.success(request, "Customer updated successfully!") messages.success(request, _("Customer updated successfully!"))
return redirect('customers') return redirect('customers')
@login_required @login_required
def delete_customer(request, pk): def delete_customer(request, pk):
customer = get_object_or_404(Customer, pk=pk) customer = get_object_or_404(Customer, pk=pk)
customer.delete() customer.delete()
messages.success(request, "Customer deleted successfully!") messages.success(request, _("Customer deleted successfully!"))
return redirect('customers') return redirect('customers')
@login_required @login_required
@ -994,7 +995,7 @@ def add_supplier(request):
contact_person = request.POST.get('contact_person') contact_person = request.POST.get('contact_person')
phone = request.POST.get('phone') phone = request.POST.get('phone')
Supplier.objects.create(name=name, contact_person=contact_person, phone=phone) Supplier.objects.create(name=name, contact_person=contact_person, phone=phone)
messages.success(request, "Supplier added successfully!") messages.success(request, _("Supplier added successfully!"))
return redirect('suppliers') return redirect('suppliers')
@login_required @login_required
@ -1005,14 +1006,14 @@ def edit_supplier(request, pk):
supplier.contact_person = request.POST.get('contact_person') supplier.contact_person = request.POST.get('contact_person')
supplier.phone = request.POST.get('phone') supplier.phone = request.POST.get('phone')
supplier.save() supplier.save()
messages.success(request, "Supplier updated successfully!") messages.success(request, _("Supplier updated successfully!"))
return redirect('suppliers') return redirect('suppliers')
@login_required @login_required
def delete_supplier(request, pk): def delete_supplier(request, pk):
supplier = get_object_or_404(Supplier, pk=pk) supplier = get_object_or_404(Supplier, pk=pk)
supplier.delete() supplier.delete()
messages.success(request, "Supplier deleted successfully!") messages.success(request, _("Supplier deleted successfully!"))
return redirect('suppliers') return redirect('suppliers')
@ -1071,7 +1072,7 @@ def add_product(request):
product.image = request.FILES['image'] product.image = request.FILES['image']
product.save() product.save()
messages.success(request, "Product added successfully!") messages.success(request, _("Product added successfully!"))
return redirect(reverse('inventory') + '#items') return redirect(reverse('inventory') + '#items')
@login_required @login_required
@ -1100,7 +1101,7 @@ def edit_product(request, pk):
product.image = request.FILES['image'] product.image = request.FILES['image']
product.save() product.save()
messages.success(request, "Product updated successfully!") messages.success(request, _("Product updated successfully!"))
return redirect(reverse('inventory') + '#items') return redirect(reverse('inventory') + '#items')
return redirect(reverse('inventory') + '#items') return redirect(reverse('inventory') + '#items')
@ -1108,7 +1109,7 @@ def edit_product(request, pk):
def delete_product(request, pk): def delete_product(request, pk):
product = get_object_or_404(Product, pk=pk) product = get_object_or_404(Product, pk=pk)
product.delete() product.delete()
messages.success(request, "Product deleted successfully!") messages.success(request, _("Product deleted successfully!"))
return redirect(reverse('inventory') + '#items') return redirect(reverse('inventory') + '#items')
@login_required @login_required
@ -1118,7 +1119,7 @@ def add_category(request):
name_ar = request.POST.get('name_ar') name_ar = request.POST.get('name_ar')
slug = slugify(name_en) slug = slugify(name_en)
Category.objects.create(name_en=name_en, name_ar=name_ar, slug=slug) Category.objects.create(name_en=name_en, name_ar=name_ar, slug=slug)
messages.success(request, "Category added successfully!") messages.success(request, _("Category added successfully!"))
return redirect(reverse('inventory') + '#categories-list') return redirect(reverse('inventory') + '#categories-list')
@login_required @login_required
@ -1129,14 +1130,14 @@ def edit_category(request, pk):
category.name_ar = request.POST.get('name_ar') category.name_ar = request.POST.get('name_ar')
category.slug = slugify(category.name_en) category.slug = slugify(category.name_en)
category.save() category.save()
messages.success(request, "Category updated successfully!") messages.success(request, _("Category updated successfully!"))
return redirect(reverse('inventory') + '#categories-list') return redirect(reverse('inventory') + '#categories-list')
@login_required @login_required
def delete_category(request, pk): def delete_category(request, pk):
category = get_object_or_404(Category, pk=pk) category = get_object_or_404(Category, pk=pk)
category.delete() category.delete()
messages.success(request, "Category deleted successfully!") messages.success(request, _("Category deleted successfully!"))
return redirect(reverse('inventory') + '#categories-list') return redirect(reverse('inventory') + '#categories-list')
@login_required @login_required
@ -1146,7 +1147,7 @@ def add_unit(request):
name_ar = request.POST.get('name_ar') name_ar = request.POST.get('name_ar')
short_name = request.POST.get('short_name') short_name = request.POST.get('short_name')
Unit.objects.create(name_en=name_en, name_ar=name_ar, short_name=short_name) Unit.objects.create(name_en=name_en, name_ar=name_ar, short_name=short_name)
messages.success(request, "Unit added successfully!") messages.success(request, _("Unit added successfully!"))
return redirect(reverse('inventory') + '#units-list') return redirect(reverse('inventory') + '#units-list')
@login_required @login_required
@ -1157,14 +1158,14 @@ def edit_unit(request, pk):
unit.name_ar = request.POST.get('name_ar') unit.name_ar = request.POST.get('name_ar')
unit.short_name = request.POST.get('short_name') unit.short_name = request.POST.get('short_name')
unit.save() unit.save()
messages.success(request, "Unit updated successfully!") messages.success(request, _("Unit updated successfully!"))
return redirect(reverse('inventory') + '#units-list') return redirect(reverse('inventory') + '#units-list')
@login_required @login_required
def delete_unit(request, pk): def delete_unit(request, pk):
unit = get_object_or_404(Unit, pk=pk) unit = get_object_or_404(Unit, pk=pk)
unit.delete() unit.delete()
messages.success(request, "Unit deleted successfully!") messages.success(request, _("Unit deleted successfully!"))
return redirect(reverse('inventory') + '#units-list') return redirect(reverse('inventory') + '#units-list')
@login_required @login_required
@ -1183,7 +1184,7 @@ def import_products(request):
excel_file = request.FILES['excel_file'] excel_file = request.FILES['excel_file']
if not excel_file.name.endswith('.xlsx'): if not excel_file.name.endswith('.xlsx'):
messages.error(request, "Please upload a valid .xlsx file.") messages.error(request, _("Please upload a valid .xlsx file."))
return redirect(reverse('inventory') + '#items') return redirect(reverse('inventory') + '#items')
try: try:
@ -1326,7 +1327,7 @@ def add_supplier_ajax(request):
@login_required @login_required
def user_management(request): def user_management(request):
if not (request.user.is_superuser or request.user.groups.filter(name='admin').exists()): if not (request.user.is_superuser or request.user.groups.filter(name='admin').exists()):
messages.error(request, "Access denied.") messages.error(request, _("Access denied."))
return redirect('index') return redirect('index')
users_qs = User.objects.all().prefetch_related('groups').order_by('username') users_qs = User.objects.all().prefetch_related('groups').order_by('username')
@ -1346,7 +1347,7 @@ def user_management(request):
group_ids = request.POST.getlist('groups') group_ids = request.POST.getlist('groups')
if User.objects.filter(username=username).exists(): if User.objects.filter(username=username).exists():
messages.error(request, "Username already exists.") messages.error(request, _("Username already exists."))
else: else:
user = User.objects.create_user(username=username, email=email, password=password) user = User.objects.create_user(username=username, email=email, password=password)
if group_ids: if group_ids:
@ -1375,7 +1376,7 @@ def user_management(request):
name = request.POST.get('name') name = request.POST.get('name')
permission_ids = request.POST.getlist('permissions') permission_ids = request.POST.getlist('permissions')
if Group.objects.filter(name=name).exists(): if Group.objects.filter(name=name).exists():
messages.error(request, "Group name already exists.") messages.error(request, _("Group name already exists."))
else: else:
group = Group.objects.create(name=name) group = Group.objects.create(name=name)
if permission_ids: if permission_ids:
@ -1397,13 +1398,13 @@ def user_management(request):
group_id = request.POST.get('group_id') group_id = request.POST.get('group_id')
group = get_object_or_404(Group, id=group_id) group = get_object_or_404(Group, id=group_id)
group.delete() group.delete()
messages.success(request, "Group deleted.") messages.success(request, _("Group deleted."))
elif action == 'toggle_status': elif action == 'toggle_status':
user_id = request.POST.get('user_id') user_id = request.POST.get('user_id')
user = get_object_or_404(User, id=user_id) user = get_object_or_404(User, id=user_id)
if user == request.user: if user == request.user:
messages.error(request, "You cannot deactivate yourself.") messages.error(request, _("You cannot deactivate yourself."))
else: else:
user.is_active = not user.is_active user.is_active = not user.is_active
user.save() user.save()
@ -1553,7 +1554,7 @@ def add_loyalty_tier(request):
min_points=min_points, point_multiplier=multiplier, min_points=min_points, point_multiplier=multiplier,
discount_percentage=discount, color_code=color discount_percentage=discount, color_code=color
) )
messages.success(request, "Loyalty tier added successfully!") messages.success(request, _("Loyalty tier added successfully!"))
return redirect(reverse('settings') + '#loyalty') return redirect(reverse('settings') + '#loyalty')
@login_required @login_required
@ -1567,14 +1568,14 @@ def edit_loyalty_tier(request, pk):
tier.discount_percentage = request.POST.get('discount_percentage') tier.discount_percentage = request.POST.get('discount_percentage')
tier.color_code = request.POST.get('color_code') tier.color_code = request.POST.get('color_code')
tier.save() tier.save()
messages.success(request, "Loyalty tier updated successfully!") messages.success(request, _("Loyalty tier updated successfully!"))
return redirect(reverse('settings') + '#loyalty') return redirect(reverse('settings') + '#loyalty')
@login_required @login_required
def delete_loyalty_tier(request, pk): def delete_loyalty_tier(request, pk):
tier = get_object_or_404(LoyaltyTier, pk=pk) tier = get_object_or_404(LoyaltyTier, pk=pk)
tier.delete() tier.delete()
messages.success(request, "Loyalty tier deleted successfully!") messages.success(request, _("Loyalty tier deleted successfully!"))
return redirect(reverse('settings') + '#loyalty') return redirect(reverse('settings') + '#loyalty')
@login_required @login_required
@ -1631,11 +1632,11 @@ def profile_view(request):
user.save() user.save()
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
update_session_auth_hash(request, user) update_session_auth_hash(request, user)
messages.success(request, "Profile and password updated successfully!") messages.success(request, _("Profile and password updated successfully!"))
else: else:
messages.error(request, "Passwords do not match.") messages.error(request, _("Passwords do not match."))
else: else:
messages.success(request, "Profile updated successfully!") messages.success(request, _("Profile updated successfully!"))
return redirect('profile') return redirect('profile')
@ -1705,7 +1706,7 @@ def expense_create_view(request):
attachment=attachment, attachment=attachment,
created_by=request.user created_by=request.user
) )
messages.success(request, "Expense recorded successfully!") messages.success(request, _("Expense recorded successfully!"))
return redirect('expenses') return redirect('expenses')
@ -1716,7 +1717,7 @@ def expense_delete_view(request, pk):
""" """
expense = get_object_or_404(Expense, pk=pk) expense = get_object_or_404(Expense, pk=pk)
expense.delete() expense.delete()
messages.success(request, "Expense deleted successfully!") messages.success(request, _("Expense deleted successfully!"))
return redirect('expenses') return redirect('expenses')
@login_required @login_required
@ -1725,22 +1726,28 @@ def expense_categories_view(request):
Manage expense categories Manage expense categories
""" """
if request.method == 'POST': if request.method == 'POST':
category_id = request.POST.get('category_id')
name_en = request.POST.get('name_en') name_en = request.POST.get('name_en')
name_ar = request.POST.get('name_ar') name_ar = request.POST.get('name_ar')
description = request.POST.get('description', '') description = request.POST.get('description', '')
ExpenseCategory.objects.create( if category_id:
name_en=name_en, category = get_object_or_404(ExpenseCategory, id=category_id)
name_ar=name_ar, category.name_en = name_en
description=description category.name_ar = name_ar
) category.description = description
messages.success(request, "Expense category created successfully!") category.save()
messages.success(request, _("Expense category updated successfully!"))
else:
ExpenseCategory.objects.create(
name_en=name_en,
name_ar=name_ar,
description=description
)
messages.success(request, _("Expense category created successfully!"))
return redirect('expense_categories') return redirect('expense_categories')
paginator = Paginator(expenses, 25) categories = ExpenseCategory.objects.all().order_by('name_en')
page_number = request.GET.get('page')
expenses = paginator.get_page(page_number)
categories = ExpenseCategory.objects.all()
return render(request, 'core/expense_categories.html', {'categories': categories}) return render(request, 'core/expense_categories.html', {'categories': categories})
@login_required @login_required
@ -1750,11 +1757,10 @@ def expense_category_delete_view(request, pk):
""" """
category = get_object_or_404(ExpenseCategory, pk=pk) category = get_object_or_404(ExpenseCategory, pk=pk)
category.delete() category.delete()
messages.success(request, "Expense category deleted successfully!") messages.success(request, _("Expense category deleted successfully!"))
return redirect('expense_categories') return redirect('expense_categories')
@csrf_exempt @csrf_exempt
@login_required @login_required
@csrf_exempt
def update_sale_api(request, pk): def update_sale_api(request, pk):
if request.method == 'POST': if request.method == 'POST':
try: try:

Binary file not shown.

File diff suppressed because it is too large Load Diff