Autosave: 20260202-132735

This commit is contained in:
Flatlogic Bot 2026-02-02 13:27:35 +00:00
parent a2c308c26c
commit ddd4aa0397
36 changed files with 1204 additions and 297 deletions

View File

@ -186,7 +186,13 @@ CONTACT_EMAIL_TO = [
# When both TLS and SSL flags are enabled, prefer SSL explicitly
if EMAIL_USE_SSL:
EMAIL_USE_TLS = False
# Authentication
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/accounts/login/'
LOGIN_URL = '/accounts/login/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View File

@ -5,6 +5,7 @@ from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")),
path("i18n/", include("django.conf.urls.i18n")),
path("", include("core.urls")),
]
@ -12,4 +13,4 @@ urlpatterns = [
if settings.DEBUG:
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -1,5 +1,13 @@
from django.contrib import admin
from .models import Category, Unit, Product, Customer, Supplier, Sale, SaleItem, Purchase, SystemSetting
from .models import (
Category, Unit, Product, Customer, Supplier,
Sale, SaleItem, SalePayment,
Purchase, PurchaseItem, PurchasePayment,
Quotation, QuotationItem,
SaleReturn, SaleReturnItem,
PurchaseReturn, PurchaseReturnItem,
SystemSetting, PaymentMethod
)
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
@ -13,7 +21,7 @@ class UnitAdmin(admin.ModelAdmin):
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ('name_en', 'name_ar', 'sku', 'price', 'stock_quantity', 'category', 'unit')
list_filter = ('category', 'unit')
list_filter = ('category', 'unit', 'is_active')
search_fields = ('name_en', 'name_ar', 'sku')
@admin.register(Customer)
@ -25,19 +33,41 @@ class CustomerAdmin(admin.ModelAdmin):
class SupplierAdmin(admin.ModelAdmin):
list_display = ('name', 'contact_person', 'phone')
@admin.register(PaymentMethod)
class PaymentMethodAdmin(admin.ModelAdmin):
list_display = ('name_en', 'name_ar', 'is_active')
class SaleItemInline(admin.TabularInline):
model = SaleItem
extra = 1
class SalePaymentInline(admin.TabularInline):
model = SalePayment
extra = 1
@admin.register(Sale)
class SaleAdmin(admin.ModelAdmin):
list_display = ('id', 'customer', 'total_amount', 'created_at')
inlines = [SaleItemInline]
list_display = ('id', 'invoice_number', 'customer', 'total_amount', 'paid_amount', 'status', 'created_at')
list_filter = ('status', 'created_at')
inlines = [SaleItemInline, SalePaymentInline]
@admin.register(Purchase)
class PurchaseAdmin(admin.ModelAdmin):
list_display = ('id', 'supplier', 'total_amount', 'created_at')
list_filter = ('supplier', 'created_at')
list_display = ('id', 'invoice_number', 'supplier', 'total_amount', 'paid_amount', 'status', 'created_at')
list_filter = ('supplier', 'status', 'created_at')
@admin.register(Quotation)
class QuotationAdmin(admin.ModelAdmin):
list_display = ('quotation_number', 'customer', 'total_amount', 'status', 'created_at')
list_filter = ('status', 'created_at')
@admin.register(SaleReturn)
class SaleReturnAdmin(admin.ModelAdmin):
list_display = ('return_number', 'customer', 'total_amount', 'created_at')
@admin.register(PurchaseReturn)
class PurchaseReturnAdmin(admin.ModelAdmin):
list_display = ('return_number', 'supplier', 'total_amount', 'created_at')
@admin.register(SystemSetting)
class SystemSettingAdmin(admin.ModelAdmin):

View File

@ -1,6 +1,10 @@
from .models import SystemSetting
import os
from django.utils import timezone
import time
# Stabilize the timestamp to avoid cache-busting on every single request
# This will only change when the server restarts
STARTUP_TIMESTAMP = int(time.time())
def project_context(request):
"""
@ -10,7 +14,7 @@ def project_context(request):
return {
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
"deployment_timestamp": int(timezone.now().timestamp()),
"deployment_timestamp": STARTUP_TIMESTAMP,
}
def global_settings(request):
@ -20,4 +24,4 @@ def global_settings(request):
settings = SystemSetting.objects.create()
return {'site_settings': settings}
except:
return {}
return {}

View File

@ -0,0 +1,51 @@
# Generated by Django 5.2.7 on 2026-02-02 10:42
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0009_purchasereturn_purchasereturnitem_salereturn_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='purchase',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchases', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='purchasepayment',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchase_payments', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='purchasereturn',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchase_returns', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='quotation',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='quotations', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='sale',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sales', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='salepayment',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sale_payments', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='salereturn',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sale_returns', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -0,0 +1,43 @@
# Generated by Django 5.2.7 on 2026-02-02 13:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0010_purchase_created_by_purchasepayment_created_by_and_more'),
]
operations = [
migrations.CreateModel(
name='PaymentMethod',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name_en', models.CharField(max_length=50, verbose_name='Name (English)')),
('name_ar', models.CharField(max_length=50, verbose_name='Name (Arabic)')),
('is_active', models.BooleanField(default=True, verbose_name='Active')),
],
),
migrations.AddField(
model_name='purchasepayment',
name='payment_method_name',
field=models.CharField(default='Cash', max_length=50, verbose_name='Payment Method Name'),
),
migrations.AddField(
model_name='salepayment',
name='payment_method_name',
field=models.CharField(default='Cash', max_length=50, verbose_name='Payment Method Name'),
),
migrations.AlterField(
model_name='purchasepayment',
name='payment_method',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchase_payments', to='core.paymentmethod'),
),
migrations.AlterField(
model_name='salepayment',
name='payment_method',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sale_payments', to='core.paymentmethod'),
),
]

View File

@ -1,6 +1,7 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.contrib.auth.models import User
class Category(models.Model):
name_en = models.CharField(_("Name (English)"), max_length=100)
@ -58,6 +59,14 @@ class Supplier(models.Model):
def __str__(self):
return self.name
class PaymentMethod(models.Model):
name_en = models.CharField(_("Name (English)"), max_length=50)
name_ar = models.CharField(_("Name (Arabic)"), max_length=50)
is_active = models.BooleanField(_("Active"), default=True)
def __str__(self):
return f"{self.name_en} / {self.name_ar}"
class Sale(models.Model):
PAYMENT_TYPE_CHOICES = [
('cash', _('Cash')),
@ -81,6 +90,7 @@ class Sale(models.Model):
status = models.CharField(_("Status"), max_length=20, choices=STATUS_CHOICES, default='paid')
due_date = models.DateField(_("Due Date"), null=True, blank=True)
notes = models.TextField(_("Notes"), blank=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="sales")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
@ -112,8 +122,10 @@ class SalePayment(models.Model):
sale = models.ForeignKey(Sale, on_delete=models.CASCADE, related_name="payments")
amount = models.DecimalField(_("Amount"), max_digits=15, decimal_places=3)
payment_date = models.DateField(_("Payment Date"), default=timezone.now)
payment_method = models.CharField(_("Payment Method"), max_length=50, default="Cash")
payment_method = models.ForeignKey(PaymentMethod, on_delete=models.SET_NULL, null=True, blank=True, related_name="sale_payments")
payment_method_name = models.CharField(_("Payment Method Name"), max_length=50, default="Cash") # Fallback
notes = models.TextField(_("Notes"), blank=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="sale_payments")
def __str__(self):
return f"Payment of {self.amount} for Sale #{self.sale.id}"
@ -135,6 +147,7 @@ class Quotation(models.Model):
valid_until = models.DateField(_("Valid Until"), null=True, blank=True)
terms_and_conditions = models.TextField(_("Terms and Conditions"), blank=True)
notes = models.TextField(_("Notes"), blank=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="quotations")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
@ -171,6 +184,7 @@ class Purchase(models.Model):
status = models.CharField(_("Status"), max_length=20, choices=STATUS_CHOICES, default='paid')
due_date = models.DateField(_("Due Date"), null=True, blank=True)
notes = models.TextField(_("Notes"), blank=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchases")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
@ -202,8 +216,10 @@ class PurchasePayment(models.Model):
purchase = models.ForeignKey(Purchase, on_delete=models.CASCADE, related_name="payments")
amount = models.DecimalField(_("Amount"), max_digits=15, decimal_places=3)
payment_date = models.DateField(_("Payment Date"), default=timezone.now)
payment_method = models.CharField(_("Payment Method"), max_length=50, default="Cash")
payment_method = models.ForeignKey(PaymentMethod, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase_payments")
payment_method_name = models.CharField(_("Payment Method Name"), max_length=50, default="Cash") # Fallback
notes = models.TextField(_("Notes"), blank=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase_payments")
def __str__(self):
return f"Payment of {self.amount} for Purchase #{self.purchase.id}"
@ -214,6 +230,7 @@ class SaleReturn(models.Model):
return_number = models.CharField(_("Return Number"), max_length=50, blank=True)
total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3)
notes = models.TextField(_("Notes"), blank=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="sale_returns")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
@ -235,6 +252,7 @@ class PurchaseReturn(models.Model):
return_number = models.CharField(_("Return Number"), max_length=50, blank=True)
total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3)
notes = models.TextField(_("Notes"), blank=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase_returns")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):

View File

@ -29,6 +29,7 @@
<div id="wrapper">
<!-- Sidebar -->
{% if user.is_authenticated %}
<nav id="sidebar">
<div class="sidebar-header d-flex align-items-center">
<a class="navbar-brand fw-bold text-primary fs-4" href="{% url 'index' %}">
@ -48,83 +49,126 @@
</a>
</li>
<li class="mt-3 px-4 small text-muted text-uppercase fw-bold">{% trans "Sales" %}</li>
<li>
<a href="{% url 'pos' %}" class="{% if request.resolver_match.url_name == 'pos' %}active{% endif %}">
<i class="bi bi-shop"></i> {% trans "POS System" %}
{% with url_name=request.resolver_match.url_name path=request.path %}
<!-- Sales Group -->
<li class="sidebar-group-header mt-2">
<a href="#salesSubmenu" data-bs-toggle="collapse" aria-expanded="{% if url_name == 'pos' or url_name == 'invoice_create' or url_name == 'invoices' or url_name == 'invoice_detail' or url_name == 'quotations' or url_name == 'quotation_create' or url_name == 'quotation_detail' or 'sales/returns' in path %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
<span>{% trans "Sales" %}</span>
<i class="bi bi-chevron-down chevron"></i>
</a>
<ul class="collapse list-unstyled sub-menu {% if url_name == 'pos' or url_name == 'invoice_create' or url_name == 'invoices' or url_name == 'invoice_detail' or url_name == 'quotations' or url_name == 'quotation_create' or url_name == 'quotation_detail' or 'sales/returns' in path %}show{% endif %}" id="salesSubmenu">
<li>
<a href="{% url 'pos' %}" class="{% if url_name == 'pos' %}active{% endif %}">
<i class="bi bi-shop"></i> {% trans "POS System" %}
</a>
</li>
<li>
<a href="{% url 'invoice_create' %}" class="{% if url_name == 'invoice_create' %}active{% endif %}">
<i class="bi bi-plus-circle"></i> {% trans "New Sales" %}
</a>
</li>
<li>
<a href="{% url 'invoices' %}" class="{% if url_name == 'invoices' or url_name == 'invoice_detail' %}active{% endif %}">
<i class="bi bi-file-earmark-text"></i> {% trans "Sales Invoices" %}
</a>
</li>
<li>
<a href="{% url 'quotations' %}" class="{% if url_name == 'quotations' or url_name == 'quotation_create' or url_name == 'quotation_detail' %}active{% endif %}">
<i class="bi bi-file-earmark-spreadsheet"></i> {% trans "Quotation" %}
</a>
</li>
<li>
<a href="{% url 'sales_returns' %}" class="{% if 'sales/returns' in path %}active{% endif %}">
<i class="bi bi-arrow-return-left"></i> {% trans "Sales Return" %}
</a>
</li>
</ul>
</li>
<li>
<a href="{% url 'invoice_create' %}" class="{% if request.resolver_match.url_name == 'invoice_create' %}active{% endif %}">
<i class="bi bi-plus-circle"></i> {% trans "New Sales" %}
<!-- Inventory Group -->
<li class="sidebar-group-header mt-2">
<a href="#inventorySubmenu" data-bs-toggle="collapse" aria-expanded="{% if url_name == 'inventory' or url_name == 'purchases' or url_name == 'purchase_create' or url_name == 'purchase_detail' or 'purchases/returns' in path or url_name == 'barcode_labels' or url_name == 'reports' %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
<span>{% trans "Inventory" %}</span>
<i class="bi bi-chevron-down chevron"></i>
</a>
<ul class="collapse list-unstyled sub-menu {% if url_name == 'inventory' or url_name == 'purchases' or url_name == 'purchase_create' or url_name == 'purchase_detail' or 'purchases/returns' in path or url_name == 'barcode_labels' or url_name == 'reports' %}show{% endif %}" id="inventorySubmenu">
<li>
<a href="{% url 'inventory' %}" class="{% if url_name == 'inventory' %}active{% endif %}">
<i class="bi bi-box-seam"></i> {% trans "Products" %}
</a>
</li>
<li>
<a href="{% url 'purchases' %}" class="{% if url_name == 'purchases' or url_name == 'purchase_create' or url_name == 'purchase_detail' %}active{% endif %}">
<i class="bi bi-cart-check"></i> {% trans "Purchases" %}
</a>
</li>
<li>
<a href="{% url 'purchase_returns' %}" class="{% if 'purchases/returns' in path %}active{% endif %}">
<i class="bi bi-arrow-return-right"></i> {% trans "Purchase Return" %}
</a>
</li>
<li>
<a href="{% url 'barcode_labels' %}" class="{% if url_name == 'barcode_labels' %}active{% endif %}">
<i class="bi bi-upc-scan"></i> {% trans "Barcode Printing" %}
</a>
</li>
<li>
<a href="{% url 'reports' %}" class="{% if url_name == 'reports' %}active{% endif %}">
<i class="bi bi-graph-up-arrow"></i> {% trans "Reports" %}
</a>
</li>
</ul>
</li>
<li>
<a href="{% url 'invoices' %}" class="{% if request.resolver_match.url_name == 'invoices' or request.resolver_match.url_name == 'invoice_detail' %}active{% endif %}">
<i class="bi bi-file-earmark-text"></i> {% trans "Sales Invoices" %}
</a>
</li>
<li>
<a href="{% url 'quotations' %}" class="{% if request.resolver_match.url_name == 'quotations' or request.resolver_match.url_name == 'quotation_create' or request.resolver_match.url_name == 'quotation_detail' %}active{% endif %}">
<i class="bi bi-file-earmark-spreadsheet"></i> {% trans "Quotation" %}
</a>
</li>
<li>
<a href="{% url 'sales_returns' %}" class="{% if 'sales/returns' in request.path %}active{% endif %}">
<i class="bi bi-arrow-return-left"></i> {% trans "Sales Return" %}
<!-- Contacts Group -->
<li class="sidebar-group-header mt-2">
<a href="#contactsSubmenu" data-bs-toggle="collapse" aria-expanded="{% if url_name == 'customers' or url_name == 'suppliers' %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
<span>{% trans "Contacts" %}</span>
<i class="bi bi-chevron-down chevron"></i>
</a>
<ul class="collapse list-unstyled sub-menu {% if url_name == 'customers' or url_name == 'suppliers' %}show{% endif %}" id="contactsSubmenu">
<li>
<a href="{% url 'customers' %}" class="{% if url_name == 'customers' %}active{% endif %}">
<i class="bi bi-people"></i> {% trans "Customers" %}
</a>
</li>
<li>
<a href="{% url 'suppliers' %}" class="{% if url_name == 'suppliers' %}active{% endif %}">
<i class="bi bi-truck"></i> {% trans "Suppliers" %}
</a>
</li>
</ul>
</li>
<li class="mt-3 px-4 small text-muted text-uppercase fw-bold">{% trans "Inventory" %}</li>
<li>
<a href="{% url 'inventory' %}" class="{% if request.resolver_match.url_name == 'inventory' %}active{% endif %}">
<i class="bi bi-box-seam"></i> {% trans "Products" %}
</a>
</li>
<li>
<a href="{% url 'purchases' %}" class="{% if request.resolver_match.url_name == 'purchases' or request.resolver_match.url_name == 'purchase_create' or request.resolver_match.url_name == 'purchase_detail' %}active{% endif %}">
<i class="bi bi-cart-check"></i> {% trans "Purchases" %}
</a>
</li>
<li>
<a href="{% url 'purchase_returns' %}" class="{% if 'purchases/returns' in request.path %}active{% endif %}">
<li>
<a href="{% url 'barcode_labels' %}" class="{% if request.resolver_match.url_name == 'barcode_labels' %}active{% endif %}">
<i class="bi bi-upc-scan"></i> {% trans "Barcode Printing" %}
</a>
</li>
<i class="bi bi-arrow-return-right"></i> {% trans "Purchase Return" %}
</a>
</li>
<li>
<a href="{% url 'reports' %}" class="{% if request.resolver_match.url_name == 'reports' %}active{% endif %}">
<i class="bi bi-graph-up-arrow"></i> {% trans "Reports" %}
</a>
</li>
<li class="mt-3 px-4 small text-muted text-uppercase fw-bold">{% trans "Contacts" %}</li>
<li>
<a href="{% url 'customers' %}" class="{% if request.resolver_match.url_name == 'customers' %}active{% endif %}">
<i class="bi bi-people"></i> {% trans "Customers" %}
</a>
</li>
<li>
<a href="{% url 'suppliers' %}" class="{% if request.resolver_match.url_name == 'suppliers' %}active{% endif %}">
<i class="bi bi-truck"></i> {% trans "Suppliers" %}
</a>
</li>
<li class="mt-3 px-4 small text-muted text-uppercase fw-bold">{% trans "System" %}</li>
<li>
<a href="{% url 'settings' %}" class="{% if request.resolver_match.url_name == 'settings' %}active{% endif %}">
<i class="bi bi-gear"></i> {% trans "Settings" %}
</a>
</li>
<li>
<a href="/admin/">
<i class="bi bi-shield-lock"></i> {% trans "Django Admin" %}
{% if user.is_superuser or user.is_staff %}
<!-- System Group -->
<li class="sidebar-group-header mt-2">
<a href="#systemSubmenu" data-bs-toggle="collapse" aria-expanded="{% if url_name == 'settings' or url_name == 'user_management' or '/admin/' in path %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
<span>{% trans "System" %}</span>
<i class="bi bi-chevron-down chevron"></i>
</a>
<ul class="collapse list-unstyled sub-menu {% if url_name == 'settings' or url_name == 'user_management' or '/admin/' in path %}show{% endif %}" id="systemSubmenu">
<li>
<a href="{% url 'settings' %}" class="{% if url_name == 'settings' %}active{% endif %}">
<i class="bi bi-gear"></i> {% trans "Settings" %}
</a>
</li>
<li>
<a href="{% url 'user_management' %}" class="{% if url_name == 'user_management' %}active{% endif %}">
<i class="bi bi-person-lock"></i> {% trans "User Management" %}
</a>
</li>
<li>
<a href="/admin/">
<i class="bi bi-shield-lock"></i> {% trans "Django Admin" %}
</a>
</li>
</ul>
</li>
{% endif %}
{% endwith %}
</ul>
<div class="mt-auto p-4 border-top">
@ -143,25 +187,39 @@
</form>
</div>
</nav>
{% endif %}
<!-- Page Content -->
<div id="content">
<div id="content" {% if not user.is_authenticated %}style="margin-left: 0; width: 100%;"{% endif %}>
<nav class="navbar navbar-expand-lg top-navbar sticky-top">
<div class="container-fluid">
{% if user.is_authenticated %}
<button type="button" id="sidebarCollapse" class="btn btn-light me-3">
<i class="bi bi-list"></i>
</button>
{% endif %}
<div class="ms-auto d-flex align-items-center">
<div class="dropdown">
<button class="btn btn-light dropdown-toggle d-flex align-items-center" type="button" id="userDropdown" data-bs-toggle="dropdown">
<i class="bi bi-person-circle fs-5 me-2"></i>
<span>{% trans "Admin" %}</span>
<span>{% if user.is_authenticated %}{{ user.username }}{% else %}{% trans "Guest" %}{% endif %}</span>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow border-0 mt-2">
<li><a class="dropdown-item" href="{% url 'settings' %}"><i class="bi bi-person-gear me-2"></i> {% trans "Profile & Settings" %}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="/admin/logout/"><i class="bi bi-box-arrow-right me-2"></i> {% trans "Logout" %}</a></li>
{% if user.is_authenticated %}
<li><a class="dropdown-item" href="{% url 'settings' %}"><i class="bi bi-person-gear me-2"></i> {% trans "Profile & Settings" %}</a></li>
<li><hr class="dropdown-divider"></li>
<li>
<form action="{% url 'logout' %}" method="post">
{% csrf_token %}
<button type="submit" class="dropdown-item text-danger border-0 bg-transparent w-100 text-start">
<i class="bi bi-box-arrow-right me-2"></i> {% trans "Logout" %}
</button>
</form>
</li>
{% else %}
<li><a class="dropdown-item" href="{% url 'login' %}"><i class="bi bi-box-arrow-in-right me-2"></i> {% trans "Login" %}</a></li>
{% endif %}
</ul>
</div>
</div>
@ -176,9 +234,11 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
{% if user.is_authenticated %}
document.getElementById('sidebarCollapse').addEventListener('click', function () {
document.getElementById('sidebar').classList.toggle('active');
});
{% endif %}
</script>
{% block scripts %}{% endblock %}
</body>

View File

@ -137,6 +137,7 @@
<th>{% trans "Customer" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Total Amount" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Action" %}</th>
</tr>
@ -148,16 +149,21 @@
<td>{{ sale.customer.name|default:"Guest" }}</td>
<td>{{ sale.created_at|date:"M d, Y H:i" }}</td>
<td class="fw-bold">{{ site_settings.currency_symbol }}{{ sale.total_amount|floatformat:3 }}</td>
<td>
<span class="text-muted small">
<i class="bi bi-person me-1"></i>{{ sale.created_by.username|default:"System" }}
</span>
</td>
<td><span class="badge bg-success bg-opacity-10 text-success">{% trans "Completed" %}</span></td>
<td>
<button class="btn btn-sm btn-light">
<a href="{% url 'invoice_detail' sale.id %}" class="btn btn-sm btn-light">
<i class="bi bi-eye"></i>
</button>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-4 text-muted">{% trans "No recent sales found." %}</td>
<td colspan="7" class="text-center py-4 text-muted">{% trans "No recent sales found." %}</td>
</tr>
{% endfor %}
</tbody>
@ -217,4 +223,4 @@
}
});
</script>
{% endblock %}
{% endblock %}

View File

@ -813,6 +813,7 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
// Persist active tab on reload var hash = window.location.hash; if (hash) { var triggerEl = document.querySelector('button[data-bs-target="' + hash + '"]'); if (triggerEl) { var tab = new bootstrap.Tab(triggerEl); tab.show(); } } var tabEls = document.querySelectorAll('button[data-bs-toggle="tab"]'); tabEls.forEach(function(el) { el.addEventListener('shown.bs.tab', function(event) { window.location.hash = event.target.getAttribute('data-bs-target'); }); });
// Fix for multiple modals: Ensure body class is preserved
document.addEventListener('hidden.bs.modal', function () {
if (document.querySelectorAll('.modal.show').length > 0) {

View File

@ -121,7 +121,7 @@
<!-- Payment Details -->
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
<label class="form-label small fw-bold">{% trans "Payment Type" %}</label>
<select class="form-select rounded-3 shadow-none border-secondary-subtle" v-model="paymentType">
<option value="cash">{% trans "Cash (Full)" %}</option>
<option value="credit">{% trans "Credit (Unpaid)" %}</option>
@ -129,6 +129,15 @@
</select>
</div>
<div class="mb-3" v-if="paymentType !== 'credit'">
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
<select class="form-select rounded-3 shadow-none border-secondary-subtle" v-model="paymentMethodId">
{% for method in payment_methods %}
<option value="{{ method.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ method.name_ar }}{% else %}{{ method.name_en }}{% endif %}</option>
{% endfor %}
</select>
</div>
<div class="mb-3" v-if="paymentType === 'partial'">
<label class="form-label small fw-bold">{% trans "Paid Amount" %}</label>
<input type="number" step="0.001" class="form-control rounded-3" v-model="paidAmount">
@ -184,6 +193,7 @@
customerId: '',
invoiceNumber: '',
paymentType: 'cash',
paymentMethodId: '{% if payment_methods.first %}{{ payment_methods.first.id }}{% endif %}',
paidAmount: 0,
discount: 0,
dueDate: '',
@ -255,6 +265,7 @@
discount: this.discount,
paid_amount: actualPaidAmount,
payment_type: this.paymentType,
payment_method_id: this.paymentMethodId,
due_date: this.dueDate,
notes: this.notes
};
@ -284,4 +295,4 @@
}
}).mount('#saleApp');
</script>
{% endblock %}
{% endblock %}

View File

@ -53,8 +53,8 @@
<div>{{ sale.created_at|date:"Y-m-d" }}</div>
</div>
<div class="col-6">
<div class="small text-muted fw-bold text-uppercase">{% trans "Due Date" %} / تاريخ الاستحقاق</div>
<div>{{ sale.due_date|date:"Y-m-d"|default:"-" }}</div>
<div class="small text-muted fw-bold text-uppercase">{% trans "Issued By" %} / صادرة عن</div>
<div>{{ sale.created_by.username|default:"System" }}</div>
</div>
</div>
</div>
@ -170,7 +170,7 @@
<!-- Payment History -->
{% if sale.payments.exists %}
<div class="mb-5">
<div class="mb-5 px-5">
<h5 class="fw-bold mb-3"><i class="bi bi-credit-card me-2"></i>{% trans "Payment Records" %} / سجلات الدفع</h5>
<div class="table-responsive">
<table class="table table-sm table-bordered">
@ -179,6 +179,7 @@
<th>{% trans "Date" %} / التاريخ</th>
<th>{% trans "Method" %} / الطريقة</th>
<th>{% trans "Amount" %} / المبلغ</th>
<th>{% trans "User" %} / المستخدم</th>
<th>{% trans "Notes" %} / ملاحظات</th>
</tr>
</thead>
@ -186,8 +187,15 @@
{% for payment in sale.payments.all %}
<tr>
<td>{{ payment.payment_date|date:"Y-m-d" }}</td>
<td>{{ payment.payment_method }}</td>
<td>
{% if payment.payment_method %}
{% if LANGUAGE_CODE == 'ar' %}{{ payment.payment_method.name_ar }}{% else %}{{ payment.payment_method.name_en }}{% endif %}
{% else %}
{{ payment.payment_method_name }}
{% endif %}
</td>
<td class="fw-bold">{{ settings.currency_symbol }}{{ payment.amount|floatformat:3 }}</td>
<td>{{ payment.created_by.username|default:"System" }}</td>
<td class="text-muted">{{ payment.notes }}</td>
</tr>
{% endfor %}
@ -199,13 +207,13 @@
<!-- Notes -->
{% if sale.notes %}
<div class="bg-light p-4 rounded-3 mb-5">
<div class="bg-light p-4 rounded-3 mb-5 mx-5">
<h6 class="fw-bold small text-uppercase mb-2 text-muted">{% trans "Internal Notes" %} / ملاحظات داخلية</h6>
<p class="mb-0 small">{{ sale.notes }}</p>
</div>
{% endif %}
<div class="text-center text-muted small mt-5 border-top pt-4">
<div class="text-center text-muted small mt-5 border-top pt-4 pb-5">
<p class="mb-1">{% trans "Thank you for your business!" %} / شكراً لتعاملكم معنا!</p>
<p class="mb-0">{% trans "Software by Meezan" %} / برمجة ميزان</p>
</div>

View File

@ -36,8 +36,7 @@
<th>{% trans "Date" %}</th>
<th>{% trans "Customer" %}</th>
<th>{% trans "Total" %}</th>
<th>{% trans "Paid" %}</th>
<th>{% trans "Balance" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Status" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
@ -51,13 +50,10 @@
<td>{{ sale.created_at|date:"Y-m-d" }}</td>
<td>{{ sale.customer.name|default:_("Guest") }}</td>
<td class="fw-bold text-dark">{{ site_settings.currency_symbol }}{{ sale.total_amount|floatformat:3 }}</td>
<td class="text-success">{{ site_settings.currency_symbol }}{{ sale.paid_amount|floatformat:3 }}</td>
<td class="text-danger fw-bold">
{% if sale.balance_due > 0 %}
{{ site_settings.currency_symbol }}{{ sale.balance_due|floatformat:3 }}
{% else %}
<span class="text-success">0.000</span>
{% endif %}
<td>
<span class="text-muted small">
<i class="bi bi-person me-1"></i>{{ sale.created_by.username|default:"System" }}
</span>
</td>
<td>
{% if sale.status == 'paid' %}
@ -103,10 +99,10 @@
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
<select name="payment_method" class="form-select rounded-3">
<option value="Cash">{% trans "Cash" %}</option>
<option value="Bank Transfer">{% trans "Bank Transfer" %}</option>
<option value="Cheque">{% trans "Cheque" %}</option>
<select name="payment_method_id" class="form-select rounded-3 shadow-none">
{% for method in payment_methods %}
<option value="{{ method.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ method.name_ar }}{% else %}{{ method.name_en }}{% endif %}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
@ -145,7 +141,7 @@
</tr>
{% empty %}
<tr>
<td colspan="8" class="text-center py-5">
<td colspan="7" class="text-center py-5">
<img src="https://illustrations.popsy.co/gray/success.svg" alt="Empty" style="width: 200px;" class="mb-3">
<p class="text-muted">{% trans "No sales invoices found." %}</p>
</td>

View File

@ -175,6 +175,16 @@
<span class="fw-bold fs-5">{% trans "Total" %}</span>
<span class="fw-bold fs-5 text-primary" id="totalAmount">{{ site_settings.currency_symbol }}0.000</span>
</div>
<div class="mb-3">
<label class="small text-muted mb-1">{% trans "Payment Method" %}</label>
<select id="paymentMethodSelect" class="form-select shadow-none">
{% for method in payment_methods %}
<option value="{{ method.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ method.name_ar }}{% else %}{{ method.name_en }}{% endif %}</option>
{% endfor %}
</select>
</div>
<button id="payNowBtn" class="btn btn-primary w-100 py-3 fw-bold rounded-3" onclick="checkout()" disabled>
{% trans "PAY NOW" %}
</button>
@ -352,11 +362,15 @@
payBtn.disabled = true;
payBtn.innerText = '{% trans "Processing..." %}';
const totalAmount = cart.reduce((acc, item) => acc + item.line_total, 0);
const data = {
customer_id: document.getElementById('customerSelect').value,
payment_method_id: document.getElementById('paymentMethodSelect').value,
items: cart,
total_amount: cart.reduce((acc, item) => acc + item.line_total, 0),
discount: 0
total_amount: totalAmount,
paid_amount: totalAmount,
discount: 0,
payment_type: 'cash'
};
fetch('{% url "create_sale_api" %}', {
@ -470,4 +484,4 @@
});
}
</script>
{% endblock %}
{% endblock %}

View File

@ -122,6 +122,15 @@
</select>
</div>
<div class="mb-3" v-if="paymentType !== 'credit'">
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
<select class="form-select rounded-3 shadow-none border-secondary-subtle" v-model="paymentMethodId">
{% for method in payment_methods %}
<option value="{{ method.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ method.name_ar }}{% else %}{{ method.name_en }}{% endif %}</option>
{% endfor %}
</select>
</div>
<div class="mb-3" v-if="paymentType === 'partial'">
<label class="form-label small fw-bold">{% trans "Amount Paid" %}</label>
<input type="number" step="0.001" class="form-control rounded-3" v-model="paidAmount">
@ -177,6 +186,7 @@
supplierId: '',
invoiceNumber: '',
paymentType: 'cash',
paymentMethodId: '{% if payment_methods.first %}{{ payment_methods.first.id }}{% endif %}',
paidAmount: 0,
dueDate: '',
notes: '',
@ -243,6 +253,7 @@
total_amount: this.subtotal,
paid_amount: actualPaidAmount,
payment_type: this.paymentType,
payment_method_id: this.paymentMethodId,
due_date: this.dueDate,
notes: this.notes
};
@ -272,4 +283,4 @@
}
}).mount('#purchaseApp');
</script>
{% endblock %}
{% endblock %}

View File

@ -48,11 +48,15 @@
<div class="h5">{{ purchase.invoice_number|default:purchase.id }}</div>
</div>
<div class="row g-3">
<div class="col-6">
<div class="col-4">
<div class="small text-muted fw-bold text-uppercase">{% trans "Issue Date" %} / تاريخ الإصدار</div>
<div>{{ purchase.created_at|date:"Y-m-d" }}</div>
</div>
<div class="col-6">
<div class="col-4">
<div class="small text-muted fw-bold text-uppercase">{% trans "Issued By" %} / صادرة عن</div>
<div>{{ purchase.created_by.username|default:"System" }}</div>
</div>
<div class="col-4">
<div class="small text-muted fw-bold text-uppercase">{% trans "Due Date" %} / تاريخ الاستحقاق</div>
<div>{{ purchase.due_date|date:"Y-m-d"|default:"-" }}</div>
</div>
@ -152,7 +156,7 @@
<!-- Payment History -->
{% if purchase.payments.exists %}
<div class="mb-5">
<div class="mb-5 px-5">
<h5 class="fw-bold mb-3"><i class="bi bi-credit-card me-2"></i>{% trans "Payment History" %} / سجل الدفعات</h5>
<div class="table-responsive">
<table class="table table-sm table-bordered">
@ -161,6 +165,7 @@
<th>{% trans "Date" %} / التاريخ</th>
<th>{% trans "Method" %} / الطريقة</th>
<th>{% trans "Amount" %} / المبلغ</th>
<th>{% trans "User" %} / المستخدم</th>
<th>{% trans "Notes" %} / ملاحظات</th>
</tr>
</thead>
@ -168,8 +173,15 @@
{% for payment in purchase.payments.all %}
<tr>
<td>{{ payment.payment_date|date:"Y-m-d" }}</td>
<td>{{ payment.payment_method }}</td>
<td>
{% if payment.payment_method %}
{% if LANGUAGE_CODE == 'ar' %}{{ payment.payment_method.name_ar }}{% else %}{{ payment.payment_method.name_en }}{% endif %}
{% else %}
{{ payment.payment_method_name }}
{% endif %}
</td>
<td class="fw-bold">{{ settings.currency_symbol }}{{ payment.amount|floatformat:3 }}</td>
<td>{{ payment.created_by.username|default:"System" }}</td>
<td class="text-muted">{{ payment.notes }}</td>
</tr>
{% endfor %}
@ -181,13 +193,13 @@
<!-- Notes -->
{% if purchase.notes %}
<div class="bg-light p-4 rounded-3 mb-5">
<div class="bg-light p-4 rounded-3 mb-5 mx-5">
<h6 class="fw-bold small text-uppercase mb-2 text-muted">{% trans "Notes" %} / ملاحظات</h6>
<p class="mb-0 small">{{ purchase.notes }}</p>
</div>
{% endif %}
<div class="text-center text-muted small mt-5 border-top pt-4">
<div class="text-center text-muted small mt-5 border-top pt-4 pb-5">
<p class="mb-0">{% trans "Thank you for your business!" %} / شكراً لتعاملكم معنا!</p>
</div>
</div>

View File

@ -35,8 +35,8 @@
<th class="ps-4">{% trans "Return #" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Supplier" %}</th>
<th>{% trans "Original Purchase" %}</th>
<th>{% trans "Total Amount" %}</th>
<th>{% trans "User" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
@ -48,16 +48,12 @@
</td>
<td>{{ return.created_at|date:"Y-m-d" }}</td>
<td>{{ return.supplier.name|default:"N/A" }}</td>
<td>
{% if return.purchase %}
<a href="{% url 'purchase_detail' return.purchase.id %}" class="text-decoration-none">
#{{ return.purchase.invoice_number|default:return.purchase.id }}
</a>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
<td class="fw-bold text-dark">{{ site_settings.currency_symbol }}{{ return.total_amount|floatformat:3 }}</td>
<td>
<span class="text-muted small">
<i class="bi bi-person me-1"></i>{{ return.created_by.username|default:"System" }}
</span>
</td>
<td class="text-end pe-4">
<div class="btn-group shadow-sm rounded-3">
<a href="{% url 'purchase_return_detail' return.id %}" class="btn btn-sm btn-white border" title="{% trans 'View & Print' %}">

View File

@ -36,8 +36,7 @@
<th>{% trans "Date" %}</th>
<th>{% trans "Supplier" %}</th>
<th>{% trans "Total" %}</th>
<th>{% trans "Paid" %}</th>
<th>{% trans "Balance" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Status" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
@ -51,13 +50,10 @@
<td>{{ purchase.created_at|date:"Y-m-d" }}</td>
<td>{{ purchase.supplier.name|default:"-" }}</td>
<td class="fw-bold text-dark">{{ site_settings.currency_symbol }}{{ purchase.total_amount|floatformat:3 }}</td>
<td class="text-success">{{ site_settings.currency_symbol }}{{ purchase.paid_amount|floatformat:3 }}</td>
<td class="text-danger fw-bold">
{% if purchase.balance_due > 0 %}
{{ site_settings.currency_symbol }}{{ purchase.balance_due|floatformat:3 }}
{% else %}
<span class="text-success">0.000</span>
{% endif %}
<td>
<span class="text-muted small">
<i class="bi bi-person me-1"></i>{{ purchase.created_by.username|default:"System" }}
</span>
</td>
<td>
{% if purchase.status == 'paid' %}
@ -103,10 +99,10 @@
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
<select name="payment_method" class="form-select rounded-3">
<option value="Cash">{% trans "Cash" %}</option>
<option value="Bank Transfer">{% trans "Bank Transfer" %}</option>
<option value="Cheque">{% trans "Cheque" %}</option>
<select name="payment_method_id" class="form-select rounded-3 shadow-none">
{% for method in payment_methods %}
<option value="{{ method.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ method.name_ar }}{% else %}{{ method.name_en }}{% endif %}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
@ -145,7 +141,7 @@
</tr>
{% empty %}
<tr>
<td colspan="8" class="text-center py-5">
<td colspan="7" class="text-center py-5">
<img src="https://illustrations.popsy.co/gray/delivery.svg" alt="Empty" style="width: 200px;" class="mb-3">
<p class="text-muted">{% trans "No purchases recorded yet." %}</p>
</td>

View File

@ -36,6 +36,7 @@
<th>{% trans "Date" %}</th>
<th>{% trans "Customer" %}</th>
<th>{% trans "Total" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Valid Until" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
@ -50,6 +51,11 @@
<td>{{ q.created_at|date:"Y-m-d" }}</td>
<td>{{ q.customer.name|default:_("Guest") }}</td>
<td class="fw-bold text-dark">{{ site_settings.currency_symbol }}{{ q.total_amount|floatformat:3 }}</td>
<td>
<span class="text-muted small">
<i class="bi bi-person me-1"></i>{{ q.created_by.username|default:"System" }}
</span>
</td>
<td>
{% if q.status == 'draft' %}
<span class="badge bg-secondary-subtle text-secondary rounded-pill px-3">{% trans "Draft" %}</span>
@ -120,7 +126,7 @@
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center py-5">
<td colspan="8" class="text-center py-5">
<img src="https://illustrations.popsy.co/gray/work-from-home.svg" alt="Empty" style="width: 200px;" class="mb-3">
<p class="text-muted">{% trans "No quotations found." %}</p>
</td>

View File

@ -35,8 +35,8 @@
<th class="ps-4">{% trans "Return #" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Customer" %}</th>
<th>{% trans "Original Sale" %}</th>
<th>{% trans "Total Amount" %}</th>
<th>{% trans "User" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
@ -48,16 +48,12 @@
</td>
<td>{{ return.created_at|date:"Y-m-d" }}</td>
<td>{{ return.customer.name|default:_("Guest") }}</td>
<td>
{% if return.sale %}
<a href="{% url 'invoice_detail' return.sale.id %}" class="text-decoration-none">
#{{ return.sale.invoice_number|default:return.sale.id }}
</a>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
<td class="fw-bold text-dark">{{ site_settings.currency_symbol }}{{ return.total_amount|floatformat:3 }}</td>
<td>
<span class="text-muted small">
<i class="bi bi-person me-1"></i>{{ return.created_by.username|default:"System" }}
</span>
</td>
<td class="text-end pe-4">
<div class="btn-group shadow-sm rounded-3">
<a href="{% url 'sale_return_detail' return.id %}" class="btn btn-sm btn-white border" title="{% trans 'View & Print' %}">

View File

@ -7,7 +7,7 @@
<div class="row mb-4 align-items-center">
<div class="col-md-6">
<h1 class="h3 mb-0 text-gray-800">{% trans "System Settings" %}</h1>
<p class="text-muted">{% trans "Manage your business profile and preferences." %}</p>
<p class="text-muted">{% trans "Manage your business profile and payments." %}</p>
</div>
</div>
@ -24,100 +24,289 @@
</div>
{% endif %}
<div class="row">
<div class="col-lg-8">
<div class="card shadow-sm border-0 glassmorphism mb-4">
<div class="card-header bg-transparent border-0 py-3">
<h5 class="card-title mb-0 fw-bold">{% trans "Business Profile" %}</h5>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="row g-3">
<div class="col-md-12 text-center mb-3">
<label class="form-label d-block fw-semibold">{% trans "Business Logo" %}</label>
{% if settings.logo %}
<img src="{{ settings.logo.url }}" alt="Logo" class="img-thumbnail mb-2" style="max-height: 100px;">
{% else %}
<div class="bg-light border rounded d-inline-flex align-items-center justify-content-center mb-2" style="width: 100px; height: 100px;">
<i class="bi bi-image text-muted fs-1"></i>
</div>
{% endif %}
<input type="file" name="logo" class="form-control form-control-sm mx-auto" style="max-width: 300px;" accept="image/*">
</div>
<ul class="nav nav-pills mb-4" id="settingsTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active fw-bold px-4" id="profile-tab" data-bs-toggle="pill" data-bs-target="#profile" type="button" role="tab">
<i class="bi bi-building me-2"></i>{% trans "Business Profile" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link fw-bold px-4" id="payments-tab" data-bs-toggle="pill" data-bs-target="#payments" type="button" role="tab">
<i class="bi bi-credit-card me-2"></i>{% trans "Payment Methods" %}
</button>
</li>
</ul>
<div class="col-md-12">
<label class="form-label fw-semibold">{% trans "Business Name" %}</label>
<input type="text" name="business_name" class="form-control" value="{{ settings.business_name }}" required>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Email Address" %}</label>
<input type="email" name="email" class="form-control" value="{{ settings.email }}">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Phone Number" %}</label>
<input type="text" name="phone" class="form-control" value="{{ settings.phone }}">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "VAT Number" %}</label>
<input type="text" name="vat_number" class="form-control" value="{{ settings.vat_number }}">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Registration Number" %}</label>
<input type="text" name="registration_number" class="form-control" value="{{ settings.registration_number }}">
</div>
<div class="col-12">
<label class="form-label fw-semibold">{% trans "Address" %}</label>
<textarea name="address" class="form-control" rows="3">{{ settings.address }}</textarea>
</div>
<hr class="my-4">
<h5 class="fw-bold mb-3">{% trans "Financial Preferences" %}</h5>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Currency Symbol" %}</label>
<input type="text" name="currency_symbol" class="form-control" value="{{ settings.currency_symbol }}" required>
<div class="form-text">{% trans "e.g., OMR, $, £, SAR" %}</div>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Default Tax Rate (%)" %}</label>
<input type="number" step="0.01" name="tax_rate" class="form-control" value="{{ settings.tax_rate }}" required>
</div>
<div class="tab-content" id="settingsTabsContent">
<!-- Business Profile Tab -->
<div class="tab-pane fade show active" id="profile" role="tabpanel">
<div class="row">
<div class="col-lg-8">
<div class="card shadow-sm border-0 glassmorphism mb-4">
<div class="card-header bg-transparent border-0 py-3">
<h5 class="card-title mb-0 fw-bold">{% trans "Business Profile" %}</h5>
</div>
<div class="mt-4 pt-3 border-top d-flex justify-content-end">
<button type="submit" class="btn btn-primary px-4 py-2">
<i class="bi bi-check-circle me-2"></i> {% trans "Save Changes" %}
</button>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="row g-3">
<div class="col-md-12 text-center mb-3">
<label class="form-label d-block fw-semibold">{% trans "Business Logo" %}</label>
{% if settings.logo %}
<img src="{{ settings.logo.url }}" alt="Logo" class="img-thumbnail mb-2" style="max-height: 100px;">
{% else %}
<div class="bg-light border rounded d-inline-flex align-items-center justify-content-center mb-2" style="width: 100px; height: 100px;">
<i class="bi bi-image text-muted fs-1"></i>
</div>
{% endif %}
<input type="file" name="logo" class="form-control form-control-sm mx-auto" style="max-width: 300px;" accept="image/*">
</div>
<div class="col-md-12">
<label class="form-label fw-semibold">{% trans "Business Name" %}</label>
<input type="text" name="business_name" class="form-control" value="{{ settings.business_name }}" required>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Email Address" %}</label>
<input type="email" name="email" class="form-control" value="{{ settings.email }}">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Phone Number" %}</label>
<input type="text" name="phone" class="form-control" value="{{ settings.phone }}">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "VAT Number" %}</label>
<input type="text" name="vat_number" class="form-control" value="{{ settings.vat_number }}">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Registration Number" %}</label>
<input type="text" name="registration_number" class="form-control" value="{{ settings.registration_number }}">
</div>
<div class="col-12">
<label class="form-label fw-semibold">{% trans "Address" %}</label>
<textarea name="address" class="form-control" rows="3">{{ settings.address }}</textarea>
</div>
<hr class="my-4">
<h5 class="fw-bold mb-3">{% trans "Financial Preferences" %}</h5>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Currency Symbol" %}</label>
<input type="text" name="currency_symbol" class="form-control" value="{{ settings.currency_symbol }}" required>
<div class="form-text">{% trans "e.g., OMR, $, £, SAR" %}</div>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Default Tax Rate (%)" %}</label>
<input type="number" step="0.01" name="tax_rate" class="form-control" value="{{ settings.tax_rate }}" required>
</div>
</div>
<div class="mt-4 pt-3 border-top d-flex justify-content-end">
<button type="submit" class="btn btn-primary px-4 py-2">
<i class="bi bi-check-circle me-2"></i> {% trans "Save Changes" %}
</button>
</div>
</form>
</div>
</form>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow-sm border-0 glassmorphism mb-4">
<div class="card-header bg-transparent border-0 py-3">
<h5 class="card-title mb-0 fw-bold">{% trans "Help & Support" %}</h5>
</div>
<div class="card-body">
<p class="text-muted small">
{% trans "Need help configuring your smart admin? Check our documentation or contact support." %}
</p>
<a href="#" class="btn btn-outline-secondary w-100 btn-sm">
<i class="bi bi-question-circle me-1"></i> {% trans "Documentation" %}
</a>
</div>
</div>
<div class="card shadow-sm border-0 glassmorphism bg-light">
<div class="card-body text-center py-4">
<i class="bi bi-info-circle fs-1 text-primary mb-3"></i>
<h6 class="fw-bold">{% trans "Smart Admin Version" %}</h6>
<p class="text-muted mb-0">v2.1.0-Meezan</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow-sm border-0 glassmorphism mb-4">
<div class="card-header bg-transparent border-0 py-3">
<h5 class="card-title mb-0 fw-bold">{% trans "Help & Support" %}</h5>
<!-- Payment Methods Tab -->
<div class="tab-pane fade" id="payments" role="tabpanel">
<div class="card shadow-sm border-0 glassmorphism">
<div class="card-header bg-transparent border-0 py-3 d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0 fw-bold">{% trans "Payment Methods" %}</h5>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addPaymentModal">
<i class="bi bi-plus-lg me-1"></i> {% trans "Add Method" %}
</button>
</div>
<div class="card-body">
<p class="text-muted small">
{% trans "Need help configuring your smart admin? Check our documentation or contact support." %}
</p>
<a href="#" class="btn btn-outline-secondary w-100 btn-sm">
<i class="bi bi-question-circle me-1"></i> {% trans "Documentation" %}
</a>
</div>
</div>
<div class="card shadow-sm border-0 glassmorphism bg-light">
<div class="card-body text-center py-4">
<i class="bi bi-info-circle fs-1 text-primary mb-3"></i>
<h6 class="fw-bold">{% trans "Smart Admin Version" %}</h6>
<p class="text-muted mb-0">v2.1.0-Meezan</p>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">{% trans "Name (EN)" %}</th>
<th>{% trans "Name (AR)" %}</th>
<th>{% trans "Status" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for pm in payment_methods %}
<tr>
<td class="ps-4 fw-semibold">{{ pm.name_en }}</td>
<td>{{ pm.name_ar }}</td>
<td>
{% if pm.is_active %}
<span class="badge bg-success-soft text-success">{% trans "Active" %}</span>
{% else %}
<span class="badge bg-danger-soft text-danger">{% trans "Inactive" %}</span>
{% endif %}
</td>
<td class="text-end pe-4">
<button class="btn btn-sm btn-light text-primary me-1" data-bs-toggle="modal" data-bs-target="#editPaymentModal{{ pm.id }}">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-light text-danger" data-bs-toggle="modal" data-bs-target="#deletePaymentModal{{ pm.id }}">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<!-- Edit Modal -->
<div class="modal fade" id="editPaymentModal{{ pm.id }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0">
<form action="{% url 'edit_payment_method' pm.id %}" method="post">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title fw-bold">{% trans "Edit Payment Method" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-semibold">{% trans "Name (English)" %}</label>
<input type="text" name="name_en" class="form-control" value="{{ pm.name_en }}" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">{% trans "Name (Arabic)" %}</label>
<input type="text" name="name_ar" class="form-control" value="{{ pm.name_ar }}" required>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="is_active" {% if pm.is_active %}checked{% endif %}>
<label class="form-check-label">{% trans "Active" %}</label>
</div>
</div>
<div class="modal-footer bg-light border-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Save Changes" %}</button>
</div>
</form>
</div>
</div>
</div>
<!-- Delete Modal -->
<div class="modal fade" id="deletePaymentModal{{ pm.id }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0">
<div class="modal-header">
<h5 class="modal-title fw-bold">{% trans "Confirm Delete" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>{% trans "Are you sure you want to delete" %} <strong>{{ pm.name_en }}</strong>?</p>
<div class="alert alert-warning mb-0">
<i class="bi bi-exclamation-triangle me-2"></i>
{% trans "Deleting this will not affect historical records but it will no longer be available for new transactions." %}
</div>
</div>
<div class="modal-footer bg-light border-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<a href="{% url 'delete_payment_method' pm.id %}" class="btn btn-danger">{% trans "Delete" %}</a>
</div>
</div>
</div>
</div>
{% empty %}
<tr>
<td colspan="4" class="text-center py-5">
<div class="text-muted">
<i class="bi bi-credit-card fs-1 d-block mb-3"></i>
{% trans "No payment methods found." %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Add Payment Method Modal -->
<div class="modal fade" id="addPaymentModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0">
<form action="{% url 'add_payment_method' %}" method="post">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title fw-bold">{% trans "Add Payment Method" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-semibold">{% trans "Name (English)" %}</label>
<input type="text" name="name_en" class="form-control" required placeholder="e.g. Bank Transfer">
</div>
<div class="mb-3">
<label class="form-label fw-semibold">{% trans "Name (Arabic)" %}</label>
<input type="text" name="name_ar" class="form-control" required placeholder="e.g. تحويل بنكي">
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="is_active" checked>
<label class="form-check-label">{% trans "Active" %}</label>
</div>
</div>
<div class="modal-footer bg-light border-0">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Create Method" %}</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// Persist active tab on reload
document.addEventListener('DOMContentLoaded', function() {
var hash = window.location.hash;
if (hash) {
var triggerEl = document.querySelector('button[data-bs-target="' + hash + '"]');
if (triggerEl) {
var tab = new bootstrap.Tab(triggerEl);
tab.show();
}
}
var tabEls = document.querySelectorAll('button[data-bs-toggle="pill"]');
tabEls.forEach(function(el) {
el.addEventListener('shown.bs.tab', function(event) {
window.location.hash = event.target.getAttribute('data-bs-target');
});
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,119 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "User Management" %} - {{ site_settings.business_name }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="h4 mb-0 fw-bold">{% trans "User Management" %}</h2>
<button class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addUserModal">
<i class="bi bi-person-plus me-2"></i> {% trans "Add New User" %}
</button>
</div>
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light text-muted small text-uppercase fw-bold">
<tr>
<th class="ps-4">{% trans "Username" %}</th>
<th>{% trans "Email" %}</th>
<th>{% trans "Role/Group" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Last Login" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for u in users %}
<tr>
<td class="ps-4">
<div class="d-flex align-items-center">
<div class="bg-primary-subtle text-primary rounded-circle p-2 me-3 d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
<i class="bi bi-person fs-5"></i>
</div>
<div>
<div class="fw-bold">{{ u.username }}</div>
{% if u.is_superuser %}<span class="badge bg-danger-subtle text-danger small">Superuser</span>{% endif %}
</div>
</div>
</td>
<td>{{ u.email|default:"-" }}</td>
<td>
{% for group in u.groups.all %}
<span class="badge bg-info-subtle text-info">{{ group.name }}</span>
{% empty %}
<span class="text-muted small">No Role</span>
{% endfor %}
</td>
<td>
{% if u.is_active %}
<span class="badge bg-success-subtle text-success">{% trans "Active" %}</span>
{% else %}
<span class="badge bg-secondary-subtle text-secondary">{% trans "Inactive" %}</span>
{% endif %}
</td>
<td>{{ u.last_login|date:"Y-m-d H:i"|default:"Never" }}</td>
<td class="text-end pe-4">
<form action="{% url 'user_management' %}" method="post" class="d-inline">
{% csrf_token %}
<input type="hidden" name="action" value="toggle_status">
<input type="hidden" name="user_id" value="{{ u.id }}">
<button type="submit" class="btn btn-sm {% if u.is_active %}btn-outline-warning{% else %}btn-outline-success{% endif %}"
{% if u == user %}disabled title="Cannot deactivate yourself"{% endif %}>
{% if u.is_active %}<i class="bi bi-person-x"></i>{% else %}<i class="bi bi-person-check"></i>{% endif %}
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Add User Modal -->
<div class="modal fade" id="addUserModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg rounded-4">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold">{% trans "Create New User" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="{% url 'user_management' %}" method="post">
{% csrf_token %}
<input type="hidden" name="action" value="add">
<div class="modal-body py-4">
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Username" %}</label>
<input type="text" name="username" class="form-control rounded-3" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Email" %}</label>
<input type="email" name="email" class="form-control rounded-3">
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Password" %}</label>
<input type="password" name="password" class="form-control rounded-3" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Role/Group" %}</label>
<select name="group" class="form-select rounded-3">
<option value="">{% trans "No Group (General Staff)" %}</option>
{% for g in groups %}
<option value="{{ g.id }}">{{ g.name|title }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-light rounded-3" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary rounded-3 px-4">{% trans "Create User" %}</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,56 @@
{% extends 'base.html' %}
{% load static i18n %}
{% block title %}{% trans "Login" %} - {{ site_settings.business_name }}{% endblock %}
{% block content %}
<div class="container h-100 d-flex align-items-center justify-content-center">
<div class="card glass-card border-0 shadow-lg p-4" style="max-width: 400px; width: 100%;">
<div class="text-center mb-4">
<h2 class="fw-bold text-primary">{% trans "Welcome Back" %}</h2>
<p class="text-muted small">{% trans "Please login to access your dashboard" %}</p>
</div>
{% if form.errors %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{% trans "Your username and password didn't match. Please try again." %}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<div class="mb-3">
<label for="id_username" class="form-label small fw-bold">{% trans "Username" %}</label>
<div class="input-group">
<span class="input-group-text bg-light border-0"><i class="bi bi-person text-muted"></i></span>
<input type="text" name="username" autofocus maxlength="150" required id="id_username" class="form-control bg-light border-0" placeholder="{% trans 'Enter username' %}">
</div>
</div>
<div class="mb-4">
<label for="id_password" class="form-label small fw-bold">{% trans "Password" %}</label>
<div class="input-group">
<span class="input-group-text bg-light border-0"><i class="bi bi-lock text-muted"></i></span>
<input type="password" name="password" required id="id_password" class="form-control bg-light border-0" placeholder="{% trans 'Enter password' %}">
</div>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold shadow-sm">
{% trans "Sign In" %}
</button>
<input type="hidden" name="next" value="{{ next }}">
</form>
<div class="mt-4 text-center">
<p class="small text-muted mb-0">{% trans "Need help? Contact your administrator." %}</p>
</div>
</div>
</div>
<style>
/* Center the login box specifically since we're using the base template with sidebar */
#sidebar { display: none !important; }
#content { margin-left: 0 !important; width: 100%; height: 100vh; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); }
.top-navbar { display: none !important; }
main { padding: 0 !important; height: 100%; display: flex; align-items: center; justify-content: center; }
</style>
{% endblock %}

View File

@ -10,6 +10,7 @@ urlpatterns = [
path('purchases/', views.purchases, name='purchases'),
path('reports/', views.reports, name='reports'),
path('settings/', views.settings_view, name='settings'),
path('users/', views.user_management, name='user_management'),
# Invoices (Sales)
path('invoices/', views.invoice_list, name='invoices'),
@ -80,4 +81,9 @@ urlpatterns = [
path('inventory/unit/edit/<int:pk>/', views.edit_unit, name='edit_unit'),
path('inventory/unit/delete/<int:pk>/', views.delete_unit, name='delete_unit'),
path('api/add-unit-ajax/', views.add_unit_ajax, name='add_unit_ajax'),
]
# Payment Methods
path('settings/payment-methods/add/', views.add_payment_method, name='add_payment_method'),
path('settings/payment-methods/edit/<int:pk>/', views.edit_payment_method, name='edit_payment_method'),
path('settings/payment-methods/delete/<int:pk>/', views.delete_payment_method, name='delete_payment_method'),
]

View File

@ -1,3 +1,4 @@
from django.urls import reverse
import random
import string
from django.shortcuts import render, get_object_or_404, redirect
@ -5,12 +6,14 @@ from django.db.models import Sum, Count, F
from django.db.models.functions import TruncDate, TruncMonth
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from .models import (
Product, Sale, Category, Unit, Customer, Supplier,
Purchase, PurchaseItem, PurchasePayment,
SaleItem, SalePayment, SystemSetting,
Quotation, QuotationItem,
SaleReturn, SaleReturnItem, PurchaseReturn, PurchaseReturnItem
SaleReturn, SaleReturnItem, PurchaseReturn, PurchaseReturnItem,
PaymentMethod
)
import json
from datetime import timedelta
@ -19,6 +22,7 @@ from django.contrib import messages
from django.utils.text import slugify
import openpyxl
@login_required
def index(request):
"""
Enhanced Meezan Dashboard View
@ -33,7 +37,7 @@ def index(request):
low_stock_products = Product.objects.filter(stock_quantity__lt=5)
# Recent Transactions
recent_sales = Sale.objects.order_by('-created_at')[:5]
recent_sales = Sale.objects.order_by('-created_at').select_related('created_by')[:5]
# Chart Data: Sales for the last 7 days
seven_days_ago = timezone.now().date() - timedelta(days=6)
@ -65,31 +69,45 @@ def index(request):
}
return render(request, 'core/index.html', context)
@login_required
def inventory(request):
products = Product.objects.all().select_related('category', 'unit', 'supplier')
categories = Category.objects.all()
units = Unit.objects.all()
suppliers = Supplier.objects.all()
context = {
'products': products,
'categories': categories,
'units': units,
'suppliers': suppliers
}
return render(request, 'core/inventory.html', context)
@login_required
def pos(request):
products = Product.objects.all().filter(stock_quantity__gt=0, is_active=True)
customers = Customer.objects.all()
categories = Category.objects.all()
context = {'products': products, 'customers': customers, 'categories': categories}
payment_methods = PaymentMethod.objects.filter(is_active=True)
# Ensure at least Cash exists
if not payment_methods.exists():
PaymentMethod.objects.create(name_en="Cash", name_ar="نقدي", is_active=True)
payment_methods = PaymentMethod.objects.filter(is_active=True)
context = {
'products': products,
'customers': customers,
'categories': categories,
'payment_methods': payment_methods
}
return render(request, 'core/pos.html', context)
@login_required
def customers(request):
customers_list = Customer.objects.all().annotate(total_sales=Sum('sales__total_amount'))
context = {'customers': customers_list}
return render(request, 'core/customers.html', context)
@login_required
def suppliers(request):
suppliers_list = Supplier.objects.all()
context = {'suppliers': suppliers_list}
@ -97,23 +115,37 @@ def suppliers(request):
# --- Purchase Views ---
@login_required
def purchases(request):
purchases_list = Purchase.objects.all().select_related('supplier').order_by('-created_at')
purchases_list = Purchase.objects.all().select_related('supplier', 'created_by').order_by('-created_at')
suppliers_list = Supplier.objects.all()
context = {'purchases': purchases_list, 'suppliers': suppliers_list}
payment_methods = PaymentMethod.objects.filter(is_active=True)
context = {
'purchases': purchases_list,
'suppliers': suppliers_list,
'payment_methods': payment_methods
}
return render(request, 'core/purchases.html', context)
@login_required
def purchase_create(request):
products = Product.objects.filter(is_active=True)
suppliers = Supplier.objects.all()
return render(request, 'core/purchase_create.html', {'products': products, 'suppliers': suppliers})
payment_methods = PaymentMethod.objects.filter(is_active=True)
return render(request, 'core/purchase_create.html', {
'products': products,
'suppliers': suppliers,
'payment_methods': payment_methods
})
@login_required
def purchase_detail(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/purchase_detail.html', {'purchase': purchase, 'settings': settings})
@csrf_exempt
@login_required
def create_purchase_api(request):
if request.method == 'POST':
try:
@ -124,6 +156,7 @@ def create_purchase_api(request):
total_amount = data.get('total_amount', 0)
paid_amount = data.get('paid_amount', 0)
payment_type = data.get('payment_type', 'cash')
payment_method_id = data.get('payment_method_id')
due_date = data.get('due_date')
notes = data.get('notes', '')
@ -139,7 +172,8 @@ def create_purchase_api(request):
balance_due=float(total_amount) - float(paid_amount),
payment_type=payment_type,
due_date=due_date if due_date else None,
notes=notes
notes=notes,
created_by=request.user
)
# Set status based on payments
@ -153,11 +187,17 @@ def create_purchase_api(request):
# Record the initial payment if any
if float(paid_amount) > 0:
pm = None
if payment_method_id:
pm = PaymentMethod.objects.filter(id=payment_method_id).first()
PurchasePayment.objects.create(
purchase=purchase,
amount=paid_amount,
payment_method=payment_type.capitalize(),
notes=_("Initial payment")
payment_method=pm,
payment_method_name=pm.name_en if pm else payment_type.capitalize(),
notes="Initial payment",
created_by=request.user
)
for item in items:
@ -179,25 +219,33 @@ def create_purchase_api(request):
return JsonResponse({'success': False, 'error': str(e)}, status=400)
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
@login_required
def add_purchase_payment(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
if request.method == 'POST':
amount = request.POST.get('amount')
payment_date = request.POST.get('payment_date', timezone.now().date())
payment_method = request.POST.get('payment_method', 'Cash')
payment_method_id = request.POST.get('payment_method_id')
notes = request.POST.get('notes', '')
pm = None
if payment_method_id:
pm = PaymentMethod.objects.filter(id=payment_method_id).first()
PurchasePayment.objects.create(
purchase=purchase,
amount=amount,
payment_date=payment_date,
payment_method=payment_method,
notes=notes
payment_method=pm,
payment_method_name=pm.name_en if pm else "Cash",
notes=notes,
created_by=request.user
)
purchase.update_balance()
messages.success(request, _("Payment added successfully!"))
messages.success(request, "Payment added successfully!")
return redirect('purchases')
@login_required
def delete_purchase(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
for item in purchase.items.all():
@ -205,27 +253,41 @@ def delete_purchase(request, pk):
item.product.save()
purchase.delete()
messages.success(request, _("Purchase deleted successfully!"))
messages.success(request, "Purchase deleted successfully!")
return redirect('purchases')
# --- Sale Views ---
@login_required
def invoice_list(request):
sales = Sale.objects.all().order_by('-created_at')
sales = Sale.objects.all().select_related('customer', 'created_by').order_by('-created_at')
customers = Customer.objects.all()
return render(request, 'core/invoices.html', {'sales': sales, 'customers': customers})
payment_methods = PaymentMethod.objects.filter(is_active=True)
return render(request, 'core/invoices.html', {
'sales': sales,
'customers': customers,
'payment_methods': payment_methods
})
@login_required
def invoice_create(request):
products = Product.objects.filter(is_active=True)
customers = Customer.objects.all()
return render(request, 'core/invoice_create.html', {'products': products, 'customers': customers})
payment_methods = PaymentMethod.objects.filter(is_active=True)
return render(request, 'core/invoice_create.html', {
'products': products,
'customers': customers,
'payment_methods': payment_methods
})
@login_required
def invoice_detail(request, pk):
sale = get_object_or_404(Sale, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/invoice_detail.html', {'sale': sale, 'settings': settings})
@csrf_exempt
@login_required
def create_sale_api(request):
if request.method == 'POST':
try:
@ -237,6 +299,7 @@ def create_sale_api(request):
paid_amount = data.get('paid_amount', 0)
discount = data.get('discount', 0)
payment_type = data.get('payment_type', 'cash')
payment_method_id = data.get('payment_method_id')
due_date = data.get('due_date')
notes = data.get('notes', '')
@ -253,7 +316,8 @@ def create_sale_api(request):
discount=discount,
payment_type=payment_type,
due_date=due_date if due_date else None,
notes=notes
notes=notes,
created_by=request.user
)
# Set status based on payments
@ -267,11 +331,17 @@ def create_sale_api(request):
# Record initial payment if any
if float(paid_amount) > 0:
pm = None
if payment_method_id:
pm = PaymentMethod.objects.filter(id=payment_method_id).first()
SalePayment.objects.create(
sale=sale,
amount=paid_amount,
payment_method=payment_type.capitalize(),
notes=_("Initial payment")
payment_method=pm,
payment_method_name=pm.name_en if pm else payment_type.capitalize(),
notes="Initial payment",
created_by=request.user
)
for item in items:
@ -325,52 +395,64 @@ def create_sale_api(request):
return JsonResponse({'success': False, 'error': str(e)}, status=400)
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
@login_required
def add_sale_payment(request, pk):
sale = get_object_or_404(Sale, pk=pk)
if request.method == 'POST':
amount = request.POST.get('amount')
payment_date = request.POST.get('payment_date', timezone.now().date())
payment_method = request.POST.get('payment_method', 'Cash')
payment_method_id = request.POST.get('payment_method_id')
notes = request.POST.get('notes', '')
pm = None
if payment_method_id:
pm = PaymentMethod.objects.filter(id=payment_method_id).first()
SalePayment.objects.create(
sale=sale,
amount=amount,
payment_date=payment_date,
payment_method=payment_method,
notes=notes
payment_method=pm,
payment_method_name=pm.name_en if pm else "Cash",
notes=notes,
created_by=request.user
)
sale.update_balance()
messages.success(request, _("Payment added successfully!"))
messages.success(request, "Payment added successfully!")
return redirect('invoices')
@login_required
def delete_sale(request, pk):
sale = get_object_or_404(Sale, pk=pk)
for item in sale.items.all():
item.product.stock_quantity += item.quantity
item.product.save()
sale.delete()
messages.success(request, _("Sale deleted successfully!"))
messages.success(request, "Sale deleted successfully!")
return redirect('invoices')
# --- Quotation Views ---
@login_required
def quotations(request):
quotations_list = Quotation.objects.all().order_by('-created_at')
quotations_list = Quotation.objects.all().select_related('customer', 'created_by').order_by('-created_at')
customers = Customer.objects.all()
return render(request, 'core/quotations.html', {'quotations': quotations_list, 'customers': customers})
@login_required
def quotation_create(request):
products = Product.objects.filter(is_active=True)
customers = Customer.objects.all()
return render(request, 'core/quotation_create.html', {'products': products, 'customers': customers})
@login_required
def quotation_detail(request, pk):
quotation = get_object_or_404(Quotation, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/quotation_detail.html', {'quotation': quotation, 'settings': settings})
@csrf_exempt
@login_required
def create_quotation_api(request):
if request.method == 'POST':
try:
@ -395,7 +477,8 @@ def create_quotation_api(request):
discount=discount,
valid_until=valid_until if valid_until else None,
terms_and_conditions=terms_and_conditions,
notes=notes
notes=notes,
created_by=request.user
)
for item in items:
@ -413,10 +496,11 @@ def create_quotation_api(request):
return JsonResponse({'success': False, 'error': str(e)}, status=400)
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
@login_required
def convert_quotation_to_invoice(request, pk):
quotation = get_object_or_404(Quotation, pk=pk)
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')
# Create Sale from Quotation
@ -428,7 +512,8 @@ def convert_quotation_to_invoice(request, pk):
balance_due=quotation.total_amount,
payment_type='cash',
status='unpaid',
notes=quotation.notes
notes=quotation.notes,
created_by=request.user
)
# Create SaleItems and Update Stock
@ -448,21 +533,24 @@ def convert_quotation_to_invoice(request, pk):
quotation.status = 'converted'
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)
@login_required
def delete_quotation(request, pk):
quotation = get_object_or_404(Quotation, pk=pk)
quotation.delete()
messages.success(request, _("Quotation deleted successfully!"))
messages.success(request, "Quotation deleted successfully!")
return redirect('quotations')
# --- Sale Return Views ---
@login_required
def sales_returns(request):
returns = SaleReturn.objects.all().order_by('-created_at')
returns = SaleReturn.objects.all().select_related('customer', 'created_by').order_by('-created_at')
return render(request, 'core/sales_returns.html', {'returns': returns})
@login_required
def sale_return_create(request):
products = Product.objects.filter(is_active=True)
customers = Customer.objects.all()
@ -473,12 +561,14 @@ def sale_return_create(request):
'sales': sales
})
@login_required
def sale_return_detail(request, pk):
sale_return = get_object_or_404(SaleReturn, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/sale_return_detail.html', {'sale_return': sale_return, 'settings': settings})
@csrf_exempt
@login_required
def create_sale_return_api(request):
if request.method == 'POST':
try:
@ -503,7 +593,8 @@ def create_sale_return_api(request):
customer=customer,
return_number=return_number,
total_amount=total_amount,
notes=notes
notes=notes,
created_by=request.user
)
for item in items:
@ -524,22 +615,25 @@ def create_sale_return_api(request):
return JsonResponse({'success': False, 'error': str(e)}, status=400)
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
@login_required
def delete_sale_return(request, pk):
sale_return = get_object_or_404(SaleReturn, pk=pk)
for item in sale_return.items.all():
item.product.stock_quantity -= item.quantity
item.product.save()
sale_return.delete()
messages.success(request, _("Sale return deleted successfully!"))
messages.success(request, "Sale return deleted successfully!")
return redirect('sales_returns')
# --- Purchase Return Views ---
@login_required
def purchase_returns(request):
returns = PurchaseReturn.objects.all().order_by('-created_at')
returns = PurchaseReturn.objects.all().select_related('supplier', 'created_by').order_by('-created_at')
return render(request, 'core/purchase_returns.html', {'returns': returns})
@login_required
def purchase_return_create(request):
products = Product.objects.filter(is_active=True)
suppliers = Supplier.objects.all()
@ -550,12 +644,14 @@ def purchase_return_create(request):
'purchases': purchases
})
@login_required
def purchase_return_detail(request, pk):
purchase_return = get_object_or_404(PurchaseReturn, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/purchase_return_detail.html', {'purchase_return': purchase_return, 'settings': settings})
@csrf_exempt
@login_required
def create_purchase_return_api(request):
if request.method == 'POST':
try:
@ -580,7 +676,8 @@ def create_purchase_return_api(request):
supplier=supplier,
return_number=return_number,
total_amount=total_amount,
notes=notes
notes=notes,
created_by=request.user
)
for item in items:
@ -601,17 +698,19 @@ def create_purchase_return_api(request):
return JsonResponse({'success': False, 'error': str(e)}, status=400)
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
@login_required
def delete_purchase_return(request, pk):
purchase_return = get_object_or_404(PurchaseReturn, pk=pk)
for item in purchase_return.items.all():
item.product.stock_quantity += item.quantity
item.product.save()
purchase_return.delete()
messages.success(request, _("Purchase return deleted successfully!"))
messages.success(request, "Purchase return deleted successfully!")
return redirect('purchase_returns')
# --- Other Management Views ---
@login_required
def reports(request):
"""
Smart Reports View
@ -633,6 +732,7 @@ def reports(request):
}
return render(request, 'core/reports.html', context)
@login_required
def settings_view(request):
"""
Smart Admin Settings View
@ -658,8 +758,42 @@ def settings_view(request):
messages.success(request, "Settings updated successfully!")
return redirect('settings')
return render(request, 'core/settings.html', {'settings': settings})
payment_methods = PaymentMethod.objects.all()
return render(request, 'core/settings.html', {
'settings': settings,
'payment_methods': payment_methods,
})
@login_required
def add_payment_method(request):
if request.method == 'POST':
name_en = request.POST.get('name_en')
name_ar = request.POST.get('name_ar')
is_active = request.POST.get('is_active') == 'on'
PaymentMethod.objects.create(name_en=name_en, name_ar=name_ar, is_active=is_active)
messages.success(request, "Payment method added successfully!")
return redirect('settings')
@login_required
def edit_payment_method(request, pk):
pm = get_object_or_404(PaymentMethod, pk=pk)
if request.method == 'POST':
pm.name_en = request.POST.get('name_en')
pm.name_ar = request.POST.get('name_ar')
pm.is_active = request.POST.get('is_active') == 'on'
pm.save()
messages.success(request, "Payment method updated successfully!")
return redirect('settings')
@login_required
def delete_payment_method(request, pk):
pm = get_object_or_404(PaymentMethod, pk=pk)
pm.delete()
messages.success(request, "Payment method deleted successfully!")
return redirect('settings')
@login_required
def add_customer(request):
if request.method == 'POST':
name = request.POST.get('name')
@ -670,6 +804,7 @@ def add_customer(request):
messages.success(request, "Customer added successfully!")
return redirect('customers')
@login_required
def edit_customer(request, pk):
customer = get_object_or_404(Customer, pk=pk)
if request.method == 'POST':
@ -681,12 +816,14 @@ def edit_customer(request, pk):
messages.success(request, "Customer updated successfully!")
return redirect('customers')
@login_required
def delete_customer(request, pk):
customer = get_object_or_404(Customer, pk=pk)
customer.delete()
messages.success(request, "Customer deleted successfully!")
return redirect('customers')
@login_required
def add_supplier(request):
if request.method == 'POST':
name = request.POST.get('name')
@ -696,6 +833,7 @@ def add_supplier(request):
messages.success(request, "Supplier added successfully!")
return redirect('suppliers')
@login_required
def edit_supplier(request, pk):
supplier = get_object_or_404(Supplier, pk=pk)
if request.method == 'POST':
@ -706,6 +844,7 @@ def edit_supplier(request, pk):
messages.success(request, "Supplier updated successfully!")
return redirect('suppliers')
@login_required
def delete_supplier(request, pk):
supplier = get_object_or_404(Supplier, pk=pk)
supplier.delete()
@ -713,6 +852,7 @@ def delete_supplier(request, pk):
return redirect('suppliers')
@login_required
def suggest_sku(request):
"""
API endpoint to suggest a unique SKU.
@ -723,6 +863,7 @@ def suggest_sku(request):
if not Product.objects.filter(sku=sku).exists():
return JsonResponse({"sku": sku})
@login_required
def add_product(request):
if request.method == 'POST':
name_en = request.POST.get('name_en')
@ -767,8 +908,9 @@ def add_product(request):
product.save()
messages.success(request, "Product added successfully!")
return redirect('inventory')
return redirect(reverse('inventory') + '#items')
@login_required
def edit_product(request, pk):
product = get_object_or_404(Product, pk=pk)
if request.method == 'POST':
@ -795,15 +937,17 @@ def edit_product(request, pk):
product.save()
messages.success(request, "Product updated successfully!")
return redirect('inventory')
return redirect('inventory')
return redirect(reverse('inventory') + '#items')
return redirect(reverse('inventory') + '#items')
@login_required
def delete_product(request, pk):
product = get_object_or_404(Product, pk=pk)
product.delete()
messages.success(request, "Product deleted successfully!")
return redirect('inventory')
return redirect(reverse('inventory') + '#items')
@login_required
def add_category(request):
if request.method == 'POST':
name_en = request.POST.get('name_en')
@ -811,8 +955,9 @@ def add_category(request):
slug = slugify(name_en)
Category.objects.create(name_en=name_en, name_ar=name_ar, slug=slug)
messages.success(request, "Category added successfully!")
return redirect('inventory')
return redirect(reverse('inventory') + '#categories-list')
@login_required
def edit_category(request, pk):
category = get_object_or_404(Category, pk=pk)
if request.method == 'POST':
@ -821,14 +966,16 @@ def edit_category(request, pk):
category.slug = slugify(category.name_en)
category.save()
messages.success(request, "Category updated successfully!")
return redirect('inventory')
return redirect(reverse('inventory') + '#categories-list')
@login_required
def delete_category(request, pk):
category = get_object_or_404(Category, pk=pk)
category.delete()
messages.success(request, "Category deleted successfully!")
return redirect('inventory')
return redirect(reverse('inventory') + '#categories-list')
@login_required
def add_unit(request):
if request.method == 'POST':
name_en = request.POST.get('name_en')
@ -836,8 +983,9 @@ def add_unit(request):
short_name = request.POST.get('short_name')
Unit.objects.create(name_en=name_en, name_ar=name_ar, short_name=short_name)
messages.success(request, "Unit added successfully!")
return redirect('inventory')
return redirect(reverse('inventory') + '#units-list')
@login_required
def edit_unit(request, pk):
unit = get_object_or_404(Unit, pk=pk)
if request.method == 'POST':
@ -846,19 +994,22 @@ def edit_unit(request, pk):
unit.short_name = request.POST.get('short_name')
unit.save()
messages.success(request, "Unit updated successfully!")
return redirect('inventory')
return redirect(reverse('inventory') + '#units-list')
@login_required
def delete_unit(request, pk):
unit = get_object_or_404(Unit, pk=pk)
unit.delete()
messages.success(request, "Unit deleted successfully!")
return redirect('inventory')
return redirect(reverse('inventory') + '#units-list')
@login_required
def barcode_labels(request):
products = Product.objects.filter(is_active=True).order_by('name_en')
context = {'products': products}
return render(request, 'core/barcode_labels.html', context)
@login_required
def import_products(request):
"""
Import products from an Excel (.xlsx) file.
@ -869,7 +1020,7 @@ def import_products(request):
if not excel_file.name.endswith('.xlsx'):
messages.error(request, "Please upload a valid .xlsx file.")
return redirect('inventory')
return redirect(reverse('inventory') + '#items')
try:
wb = openpyxl.load_workbook(excel_file)
@ -938,9 +1089,10 @@ def import_products(request):
except Exception as e:
messages.error(request, f"Error processing file: {str(e)}")
return redirect('inventory')
return redirect(reverse('inventory') + '#items')
@csrf_exempt
@login_required
def add_category_ajax(request):
if request.method == 'POST':
try:
@ -963,6 +1115,7 @@ def add_category_ajax(request):
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
@csrf_exempt
@login_required
def add_unit_ajax(request):
if request.method == 'POST':
try:
@ -985,6 +1138,7 @@ def add_unit_ajax(request):
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
@csrf_exempt
@login_required
def add_supplier_ajax(request):
if request.method == 'POST':
try:
@ -1004,3 +1158,46 @@ def add_supplier_ajax(request):
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)}, status=400)
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
@login_required
def user_management(request):
if not (request.user.is_superuser or request.user.groups.filter(name='admin').exists()):
messages.error(request, "Access denied.")
return redirect('index')
from django.contrib.auth.models import User, Group
users = User.objects.all().prefetch_related('groups')
groups = Group.objects.all()
if request.method == 'POST':
action = request.POST.get('action')
if action == 'add':
username = request.POST.get('username')
password = request.POST.get('password')
email = request.POST.get('email')
group_id = request.POST.get('group')
if User.objects.filter(username=username).exists():
messages.error(request, "Username already exists.")
else:
user = User.objects.create_user(username=username, email=email, password=password)
if group_id:
group = Group.objects.get(id=group_id)
user.groups.add(group)
user.is_staff = True
user.save()
messages.success(request, f"User {username} created successfully.")
elif action == 'toggle_status':
user_id = request.POST.get('user_id')
user = get_object_or_404(User, id=user_id)
if user == request.user:
messages.error(request, "You cannot deactivate yourself.")
else:
user.is_active = not user.is_active
user.save()
messages.success(request, f"User {user.username} status updated.")
return redirect('user_management')
return render(request, 'core/users.html', {'users': users, 'groups': groups})

View File

@ -101,6 +101,43 @@ body {
font-size: 1.2rem;
}
/* Collapsible Sidebar Styles */
#sidebar ul.components li.sidebar-group-header > a {
padding: 12px 25px;
font-size: 0.85rem;
text-transform: uppercase;
font-weight: 700;
color: #6c757d;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 0;
margin-inline-end: 0;
}
#sidebar ul.components li.sidebar-group-header > a:hover {
background: transparent;
color: var(--meezan-primary);
}
#sidebar ul.components li.sidebar-group-header > a i.chevron {
transition: transform 0.3s;
}
#sidebar ul.components li.sidebar-group-header > a[aria-expanded="true"] i.chevron {
transform: rotate(180deg);
}
#sidebar ul.sub-menu li a {
padding-inline-start: 50px;
font-size: 0.95rem;
}
[dir="rtl"] #sidebar ul.sub-menu li a {
padding-inline-end: 50px;
padding-inline-start: 25px;
}
/* Main Content Styling */
#content {
width: 100%;
@ -156,4 +193,4 @@ body {
#content {
width: 100%;
}
}
}

View File

@ -101,6 +101,43 @@ body {
font-size: 1.2rem;
}
/* Collapsible Sidebar Styles */
#sidebar ul.components li.sidebar-group-header > a {
padding: 12px 25px;
font-size: 0.85rem;
text-transform: uppercase;
font-weight: 700;
color: #6c757d;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 0;
margin-inline-end: 0;
}
#sidebar ul.components li.sidebar-group-header > a:hover {
background: transparent;
color: var(--meezan-primary);
}
#sidebar ul.components li.sidebar-group-header > a i.chevron {
transition: transform 0.3s;
}
#sidebar ul.components li.sidebar-group-header > a[aria-expanded="true"] i.chevron {
transform: rotate(180deg);
}
#sidebar ul.sub-menu li a {
padding-inline-start: 50px;
font-size: 0.95rem;
}
[dir="rtl"] #sidebar ul.sub-menu li a {
padding-inline-end: 50px;
padding-inline-start: 25px;
}
/* Main Content Styling */
#content {
width: 100%;
@ -156,4 +193,4 @@ body {
#content {
width: 100%;
}
}
}