Autosave: 20260202-073948

This commit is contained in:
Flatlogic Bot 2026-02-02 07:39:49 +00:00
parent a5d1439f46
commit 7d1c8df2b2
29 changed files with 2231 additions and 228 deletions

View File

@ -62,6 +62,7 @@ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
@ -85,6 +86,7 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
# IMPORTANT: do not remove injects PROJECT_DESCRIPTION/PROJECT_IMAGE_URL and cache-busting timestamp # IMPORTANT: do not remove injects PROJECT_DESCRIPTION/PROJECT_IMAGE_URL and cache-busting timestamp
'core.context_processors.project_context', 'core.context_processors.project_context',
'core.context_processors.global_settings',
], ],
}, },
}, },
@ -134,6 +136,12 @@ AUTH_PASSWORD_VALIDATORS = [
# https://docs.djangoproject.com/en/5.2/topics/i18n/ # https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-us'
LANGUAGES = [
('en', 'English'),
('ar', 'Arabic'),
]
LOCALE_PATHS = [BASE_DIR / 'locale']
TIME_ZONE = 'UTC' TIME_ZONE = 'UTC'
@ -148,6 +156,8 @@ USE_TZ = True
STATIC_URL = 'static/' STATIC_URL = 'static/'
# Collect static into a separate folder; avoid overlapping with STATICFILES_DIRS. # Collect static into a separate folder; avoid overlapping with STATICFILES_DIRS.
STATIC_ROOT = BASE_DIR / 'staticfiles' STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'media'
STATICFILES_DIRS = [ STATICFILES_DIRS = [
BASE_DIR / 'static', BASE_DIR / 'static',
@ -179,4 +189,4 @@ if EMAIL_USE_SSL:
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field # 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

@ -1,19 +1,3 @@
"""
URL configuration for config project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from django.conf import settings from django.conf import settings
@ -21,9 +5,11 @@ from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("i18n/", include("django.conf.urls.i18n")),
path("", include("core.urls")), path("", include("core.urls")),
] ]
if settings.DEBUG: if settings.DEBUG:
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets") urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -1,3 +1,36 @@
from django.contrib import admin from django.contrib import admin
from .models import Category, Product, Customer, Supplier, Sale, SaleItem, Purchase
# Register your models here. @admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name_en', 'name_ar', 'slug')
prepopulated_fields = {'slug': ('name_en',)}
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ('name_en', 'name_ar', 'sku', 'price', 'stock_quantity', 'category')
list_filter = ('category',)
search_fields = ('name_en', 'name_ar', 'sku')
@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
list_display = ('name', 'phone', 'email')
search_fields = ('name', 'phone')
@admin.register(Supplier)
class SupplierAdmin(admin.ModelAdmin):
list_display = ('name', 'contact_person', 'phone')
class SaleItemInline(admin.TabularInline):
model = SaleItem
extra = 1
@admin.register(Sale)
class SaleAdmin(admin.ModelAdmin):
list_display = ('id', 'customer', 'total_amount', 'created_at')
inlines = [SaleItemInline]
@admin.register(Purchase)
class PurchaseAdmin(admin.ModelAdmin):
list_display = ('id', 'supplier', 'total_amount', 'created_at')
list_filter = ('supplier', 'created_at')

View File

@ -1,13 +1,23 @@
from .models import SystemSetting
import os import os
import time from django.utils import timezone
def project_context(request): def project_context(request):
""" """
Adds project-specific environment variables to the template context globally. Injects project description and social image URL from environment variables.
Also injects a deployment timestamp for cache-busting.
""" """
return { return {
"project_description": os.getenv("PROJECT_DESCRIPTION", ""), "project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
# Used for cache-busting static assets "deployment_timestamp": int(timezone.now().timestamp()),
"deployment_timestamp": int(time.time()),
} }
def global_settings(request):
try:
settings = SystemSetting.objects.first()
if not settings:
settings = SystemSetting.objects.create()
return {'site_settings': settings}
except:
return {}

View File

@ -0,0 +1,91 @@
# Generated by Django 5.2.7 on 2026-02-02 06:51
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name_en', models.CharField(max_length=100, verbose_name='Name (English)')),
('name_ar', models.CharField(max_length=100, verbose_name='Name (Arabic)')),
('slug', models.SlugField(unique=True)),
],
options={
'verbose_name_plural': 'Categories',
},
),
migrations.CreateModel(
name='Customer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Name')),
('phone', models.CharField(blank=True, max_length=20, verbose_name='Phone')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='Email')),
('address', models.TextField(blank=True, verbose_name='Address')),
],
),
migrations.CreateModel(
name='Supplier',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Name')),
('contact_person', models.CharField(blank=True, max_length=200, verbose_name='Contact Person')),
('phone', models.CharField(blank=True, max_length=20, verbose_name='Phone')),
],
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name_en', models.CharField(max_length=200, verbose_name='Name (English)')),
('name_ar', models.CharField(max_length=200, verbose_name='Name (Arabic)')),
('sku', models.CharField(max_length=50, unique=True, verbose_name='SKU')),
('description', models.TextField(blank=True, verbose_name='Description')),
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Price')),
('stock_quantity', models.PositiveIntegerField(default=0, verbose_name='Stock Quantity')),
('image', models.URLField(blank=True, null=True, verbose_name='Product Image')),
('created_at', models.DateTimeField(auto_now_add=True)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='core.category')),
],
),
migrations.CreateModel(
name='Sale',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('total_amount', models.DecimalField(decimal_places=2, max_digits=12)),
('discount', models.DecimalField(decimal_places=2, default=0, max_digits=12)),
('created_at', models.DateTimeField(auto_now_add=True)),
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.customer')),
],
),
migrations.CreateModel(
name='SaleItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField()),
('unit_price', models.DecimalField(decimal_places=2, max_digits=10)),
('line_total', models.DecimalField(decimal_places=2, max_digits=12)),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product')),
('sale', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.sale')),
],
),
migrations.CreateModel(
name='Purchase',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('total_amount', models.DecimalField(decimal_places=2, max_digits=12)),
('created_at', models.DateTimeField(auto_now_add=True)),
('supplier', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.supplier')),
],
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 5.2.7 on 2026-02-02 07:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='SystemSetting',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('business_name', models.CharField(default='Meezan Accounting', max_length=200, verbose_name='Business Name')),
('address', models.TextField(blank=True, verbose_name='Address')),
('phone', models.CharField(blank=True, max_length=20, verbose_name='Phone')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='Email')),
('currency_symbol', models.CharField(default='$', max_length=10, verbose_name='Currency Symbol')),
('tax_rate', models.DecimalField(decimal_places=2, default=0, max_digits=5, verbose_name='Tax Rate (%)')),
('logo_url', models.URLField(blank=True, null=True, verbose_name='Logo URL')),
],
),
]

View File

@ -1,3 +1,77 @@
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _
# Create your models here. class Category(models.Model):
name_en = models.CharField(_("Name (English)"), max_length=100)
name_ar = models.CharField(_("Name (Arabic)"), max_length=100)
slug = models.SlugField(unique=True)
class Meta:
verbose_name_plural = _("Categories")
def __str__(self):
return f"{self.name_en} / {self.name_ar}"
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="products")
name_en = models.CharField(_("Name (English)"), max_length=200)
name_ar = models.CharField(_("Name (Arabic)"), max_length=200)
sku = models.CharField(_("SKU"), max_length=50, unique=True)
description = models.TextField(_("Description"), blank=True)
price = models.DecimalField(_("Price"), max_digits=10, decimal_places=2)
stock_quantity = models.PositiveIntegerField(_("Stock Quantity"), default=0)
image = models.URLField(_("Product Image"), blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.name_en} ({self.sku})"
class Customer(models.Model):
name = models.CharField(_("Name"), max_length=200)
phone = models.CharField(_("Phone"), max_length=20, blank=True)
email = models.EmailField(_("Email"), blank=True)
address = models.TextField(_("Address"), blank=True)
def __str__(self):
return self.name
class Supplier(models.Model):
name = models.CharField(_("Name"), max_length=200)
contact_person = models.CharField(_("Contact Person"), max_length=200, blank=True)
phone = models.CharField(_("Phone"), max_length=20, blank=True)
def __str__(self):
return self.name
class Sale(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True)
total_amount = models.DecimalField(max_digits=12, decimal_places=2)
discount = models.DecimalField(max_digits=12, decimal_places=2, default=0)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Sale #{self.id} - {self.total_amount}"
class SaleItem(models.Model):
sale = models.ForeignKey(Sale, on_delete=models.CASCADE, related_name="items")
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
unit_price = models.DecimalField(max_digits=10, decimal_places=2)
line_total = models.DecimalField(max_digits=12, decimal_places=2)
class Purchase(models.Model):
supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True)
total_amount = models.DecimalField(max_digits=12, decimal_places=2)
created_at = models.DateTimeField(auto_now_add=True)
class SystemSetting(models.Model):
business_name = models.CharField(_("Business Name"), max_length=200, default="Meezan Accounting")
address = models.TextField(_("Address"), blank=True)
phone = models.CharField(_("Phone"), max_length=20, blank=True)
email = models.EmailField(_("Email"), blank=True)
currency_symbol = models.CharField(_("Currency Symbol"), max_length=10, default="$")
tax_rate = models.DecimalField(_("Tax Rate (%)"), max_digits=5, decimal_places=2, default=0)
logo_url = models.URLField(_("Logo URL"), blank=True, null=True)
def __str__(self):
return self.business_name

View File

@ -1,25 +1,148 @@
<!DOCTYPE html> {% load static i18n %}{% get_current_language as LANGUAGE_CODE %}<!DOCTYPE html>
<html lang="en"> <html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_CODE == 'ar' %}rtl{% else %}ltr{% endif %}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{% block title %}Knowledge Base{% endblock %}</title> <meta name="viewport" content="width=device-width, initial-scale=1.0">
{% if project_description %} <title>{% block title %}{{ site_settings.business_name }}{% endblock %}</title>
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}"> {% if project_description %}
<meta property="twitter:description" content="{{ project_description }}"> <meta name="description" content="{{ project_description }}">
{% endif %} <meta property="og:description" content="{{ project_description }}">
{% if project_image_url %} {% endif %}
<meta property="og:image" content="{{ project_image_url }}">
<meta property="twitter:image" content="{{ project_image_url }}"> <link rel="preconnect" href="https://fonts.googleapis.com">
{% endif %} <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
{% load static %} <link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;700&family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %} {% if LANGUAGE_CODE == 'ar' %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.rtl.min.css">
{% else %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
{% endif %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
</head> </head>
<body> <body>
{% block content %}{% endblock %}
</body>
</html> <div id="wrapper">
<!-- Sidebar -->
<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' %}">
{% if site_settings.logo_url %}
<img src="{{ site_settings.logo_url }}" alt="Logo" height="30" class="me-2">
{% else %}
<i class="bi bi-calculator-fill me-2"></i>
{% endif %}
{{ site_settings.business_name|truncatechars:12 }}
</a>
</div>
<ul class="list-unstyled components">
<li>
<a href="{% url 'index' %}" class="{% if request.resolver_match.url_name == 'index' %}active{% endif %}">
<i class="bi bi-speedometer2"></i> {% trans "Dashboard" %}
</a>
</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" %}
</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 "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' %}active{% endif %}">
<i class="bi bi-cart-check"></i> {% trans "Purchases" %}
</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>
</ul>
<div class="mt-auto p-4 border-top">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<select name="language" onchange="this.form.submit()" class="form-select form-select-sm border-0 bg-light">
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}" {% if language.code == LANGUAGE_CODE %}selected{% endif %}>
{{ language.name_local }}
</option>
{% endfor %}
</select>
</form>
</div>
</nav>
<!-- Page Content -->
<div id="content">
<nav class="navbar navbar-expand-lg top-navbar sticky-top">
<div class="container-fluid">
<button type="button" id="sidebarCollapse" class="btn btn-light me-3">
<i class="bi bi-list"></i>
</button>
<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>
</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>
</ul>
</div>
</div>
</div>
</nav>
<main class="p-4">
{% block content %}{% endblock %}
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.getElementById('sidebarCollapse').addEventListener('click', function () {
document.getElementById('sidebar').classList.toggle('active');
});
</script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,103 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Customers" %} | Meezan Accounting{% endblock %}
{% block content %}
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold mb-0">{% trans "Customers" %}</h2>
<button class="btn btn-primary rounded-3" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
<i class="bi bi-person-plus me-2"></i>{% trans "Add Customer" %}
</button>
</div>
{% if messages %}
<div class="mb-4">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show rounded-3" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
{% endfor %}
</div>
{% endif %}
<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">
<tr>
<th class="ps-4">{% trans "Name" %}</th>
<th>{% trans "Phone" %}</th>
<th>{% trans "Email" %}</th>
<th>{% trans "Address" %}</th>
<th>{% trans "Total Sales" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for customer in customers %}
<tr>
<td class="ps-4 fw-bold">{{ customer.name }}</td>
<td>{{ customer.phone }}</td>
<td>{{ customer.email }}</td>
<td>{{ customer.address|truncatechars:30 }}</td>
<td><span class="badge bg-primary bg-opacity-10 text-primary">{{ site_settings.currency_symbol }}{{ customer.total_sales|default:"0.00" }}</span></td>
<td class="text-end pe-4">
<button class="btn btn-sm btn-light rounded-circle"><i class="bi bi-pencil"></i></button>
<button class="btn btn-sm btn-light rounded-circle text-danger"><i class="bi bi-trash"></i></button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-5 text-muted">
{% trans "No customers found." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Add Customer Modal -->
<div class="modal fade" id="addCustomerModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow rounded-4">
<div class="modal-header border-0">
<h5 class="fw-bold">{% trans "Add New Customer" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="{% url 'add_customer' %}" method="POST">
{% csrf_token %}
<div class="modal-body">
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Full Name" %}</label>
<input type="text" name="name" class="form-control rounded-3" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
<input type="text" name="phone" class="form-control rounded-3">
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Email Address" %}</label>
<input type="email" name="email" class="form-control rounded-3">
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Address" %}</label>
<textarea name="address" class="form-control rounded-3" rows="3"></textarea>
</div>
</div>
<div class="modal-footer border-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 "Save Customer" %}</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,145 +1,220 @@
{% extends "base.html" %} {% extends 'base.html' %}
{% load static i18n %}
{% block title %}{{ project_name }}{% endblock %} {% block title %}{% trans "Smart Dashboard" %} - {{ site_settings.business_name }}{% endblock %}
{% block head %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% {
background-position: 0% 0%;
}
100% {
background-position: 100% 100%;
}
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2.5rem 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
}
h1 {
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
font-weight: 700;
margin: 0 0 1.2rem;
letter-spacing: -0.02em;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
opacity: 0.92;
}
.loader {
margin: 1.5rem auto;
width: 56px;
height: 56px;
border: 4px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.runtime code {
background: rgba(0, 0, 0, 0.25);
padding: 0.15rem 0.45rem;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
footer {
position: absolute;
bottom: 1rem;
width: 100%;
text-align: center;
font-size: 0.85rem;
opacity: 0.75;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<main> <div class="container-fluid">
<div class="card"> <!-- Header -->
<h1>Analyzing your requirements and generating your app…</h1> <div class="row mb-4 align-items-center">
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes"> <div class="col">
<span class="sr-only">Loading…</span> <h1 class="h3 fw-bold mb-0">{% trans "Overview" %}</h1>
<p class="text-muted small mb-0">{% trans "Welcome back! Here's what's happening with your business today." %}</p>
</div>
<div class="col-auto">
<a href="{% url 'pos' %}" class="btn btn-primary shadow-sm">
<i class="bi bi-plus-lg me-2"></i> {% trans "New Sale" %}
</a>
</div>
</div> </div>
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will refresh automatically as the plan is implemented.</p> <!-- Stats Cards -->
<p class="runtime"> <div class="row g-3 mb-4">
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code> <div class="col-md-3">
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code> <div class="card glass-card border-0 p-3 stat-card">
</p> <div class="d-flex align-items-center">
</div> <div class="stat-icon bg-primary text-white bg-opacity-10 text-primary rounded-3 p-3 me-3">
</main> <i class="bi bi-cash-stack fs-4 text-primary"></i>
<footer> </div>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) <div>
</footer> <h6 class="text-muted small mb-1">{% trans "Total Revenue" %}</h6>
<h4 class="fw-bold mb-0">{{ site_settings.currency_symbol }}{{ total_sales_amount|floatformat:2 }}</h4>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card glass-card border-0 p-3 stat-card">
<div class="d-flex align-items-center">
<div class="stat-icon bg-success bg-opacity-10 rounded-3 p-3 me-3">
<i class="bi bi-cart-check fs-4 text-success"></i>
</div>
<div>
<h6 class="text-muted small mb-1">{% trans "Total Sales" %}</h6>
<h4 class="fw-bold mb-0">{{ total_sales_count }}</h4>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card glass-card border-0 p-3 stat-card">
<div class="d-flex align-items-center">
<div class="stat-icon bg-info bg-opacity-10 rounded-3 p-3 me-3">
<i class="bi bi-box-seam fs-4 text-info"></i>
</div>
<div>
<h6 class="text-muted small mb-1">{% trans "Total Products" %}</h6>
<h4 class="fw-bold mb-0">{{ total_products }}</h4>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card glass-card border-0 p-3 stat-card">
<div class="d-flex align-items-center">
<div class="stat-icon bg-warning bg-opacity-10 rounded-3 p-3 me-3">
<i class="bi bi-people fs-4 text-warning"></i>
</div>
<div>
<h6 class="text-muted small mb-1">{% trans "Total Customers" %}</h6>
<h4 class="fw-bold mb-0">{{ total_customers }}</h4>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<!-- Sales Chart -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm p-4 h-100">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="fw-bold mb-0">{% trans "Sales Revenue" %}</h5>
<span class="badge bg-primary bg-opacity-10 text-primary">{% trans "Last 7 Days" %}</span>
</div>
<canvas id="salesChart" height="300"></canvas>
</div>
</div>
<!-- Inventory Status -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm p-4 h-100">
<h5 class="fw-bold mb-4">{% trans "Low Stock Alerts" %}</h5>
{% if low_stock_products %}
<ul class="list-group list-group-flush">
{% for product in low_stock_products %}
<li class="list-group-item px-0 border-0 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<div class="bg-light rounded p-2 me-3">
<i class="bi bi-box"></i>
</div>
<div>
<p class="mb-0 fw-semibold">
{% if LANGUAGE_CODE == 'ar' %}{{ product.name_ar }}{% else %}{{ product.name_en }}{% endif %}
</p>
<small class="text-muted">
{% if LANGUAGE_CODE == 'ar' %}{{ product.category.name_ar }}{% else %}{{ product.category.name_en }}{% endif %}
</small>
</div>
</div>
<span class="badge bg-danger rounded-pill">{{ product.stock_quantity }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<div class="text-center py-5">
<i class="bi bi-check-circle text-success display-4"></i>
<p class="mt-3 text-muted">{% trans "All stock levels are healthy!" %}</p>
</div>
{% endif %}
<div class="mt-auto pt-3 border-top">
<a href="{% url 'inventory' %}" class="btn btn-light btn-sm w-100 fw-bold">{% trans "View Full Inventory" %}</a>
</div>
</div>
</div>
</div>
<!-- Recent Transactions -->
<div class="row g-4">
<div class="col-12">
<div class="card border-0 shadow-sm p-4">
<h5 class="fw-bold mb-4">{% trans "Recent Sales" %}</h5>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>{% trans "Sale ID" %}</th>
<th>{% trans "Customer" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Total Amount" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for sale in recent_sales %}
<tr>
<td class="fw-bold">#{{ sale.id }}</td>
<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 }}</td>
<td><span class="badge bg-success bg-opacity-10 text-success">{% trans "Completed" %}</span></td>
<td>
<button class="btn btn-sm btn-light">
<i class="bi bi-eye"></i>
</button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-4 text-muted">{% trans "No recent sales found." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ctx = document.getElementById('salesChart').getContext('2d');
const salesChart = new Chart(ctx, {
type: 'line',
data: {
labels: {{ chart_labels|safe }},
datasets: [{
label: '{% trans "Revenue" %} ({{ site_settings.currency_symbol }})',
data: {{ chart_data|safe }},
borderColor: '#2E5BFF',
backgroundColor: 'rgba(46, 91, 255, 0.1)',
borderWidth: 3,
tension: 0.4,
fill: true,
pointBackgroundColor: '#fff',
pointBorderColor: '#2E5BFF',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
grid: {
drawBorder: false,
color: 'rgba(0, 0, 0, 0.05)'
}
},
x: {
grid: {
display: false
}
}
}
}
});
</script>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,162 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "Inventory Management" %} | Meezan Accounting{% endblock %}
{% block content %}
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="fw-bold mb-1">{% trans "Inventory Management" %}</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'index' %}">{% trans "Dashboard" %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Inventory" %}</li>
</ol>
</nav>
</div>
<button class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addProductModal">
<i class="bi bi-plus-lg me-2"></i>{% trans "Add Product" %}
</button>
</div>
{% if messages %}
<div class="mb-4">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show rounded-3" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Search & Filter Bar -->
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-3">
<form class="row g-3">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search text-muted"></i></span>
<input type="text" class="form-control border-start-0" placeholder="{% trans 'Search by name or SKU...' %}">
</div>
</div>
<div class="col-md-4">
<select class="form-select">
<option value="">{% trans "All Categories" %}</option>
{% for category in categories %}
<option value="{{ category.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ category.name_ar }}{% else %}{{ category.name_en }}{% endif %}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<button type="button" class="btn btn-light w-100">{% trans "Filter" %}</button>
</div>
</form>
</div>
</div>
<!-- Product Grid -->
<div class="row g-4">
{% for product in products %}
<div class="col-md-6 col-lg-4 col-xl-3">
<div class="card product-card h-100 shadow-sm border-0">
<div class="position-relative">
{% if product.image %}
<img src="{{ product.image.url }}" class="card-img-top" alt="{{ product.name_en }}" style="height: 200px; object-fit: cover;">
{% else %}
<div class="bg-secondary bg-opacity-10 d-flex align-items-center justify-content-center" style="height: 200px;">
<i class="bi bi-box-seam text-secondary opacity-25" style="font-size: 4rem;"></i>
</div>
{% endif %}
<span class="position-absolute top-0 end-0 m-3 badge {% if product.stock_quantity < 5 %}bg-danger{% else %}bg-success{% endif %}">
{% trans "In Stock" %}: {{ product.stock_quantity }}
</span>
</div>
<div class="card-body">
<div class="text-muted small mb-1">
{% if LANGUAGE_CODE == 'ar' %}{{ product.category.name_ar }}{% else %}{{ product.category.name_en }}{% endif %}
</div>
<h5 class="card-title fw-bold mb-2">
{% if LANGUAGE_CODE == 'ar' %}{{ product.name_ar }}{% else %}{{ product.name_en }}{% endif %}
</h5>
<div class="d-flex justify-content-between align-items-center mt-3">
<h4 class="text-primary fw-bold mb-0">{{ site_settings.currency_symbol }}{{ product.price }}</h4>
<div class="btn-group">
<button class="btn btn-sm btn-outline-light text-dark"><i class="bi bi-pencil"></i></button>
<button class="btn btn-sm btn-outline-light text-dark"><i class="bi bi-eye"></i></button>
</div>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12 text-center py-5">
<div class="bg-white rounded-4 p-5 shadow-sm">
<i class="bi bi-box-seam text-muted opacity-25" style="font-size: 5rem;"></i>
<h4 class="mt-3 text-muted">{% trans "Your inventory is empty" %}</h4>
<p class="text-muted">{% trans "Start by adding your first product to the system." %}</p>
<button class="btn btn-primary mt-3" data-bs-toggle="modal" data-bs-target="#addProductModal">
{% trans "Add First Product" %}
</button>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Add Product Modal -->
<div class="modal fade" id="addProductModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content rounded-4 border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold">{% trans "Add New Product" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'add_product' %}" method="POST">
{% csrf_token %}
<div class="modal-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
<input type="text" name="name_en" class="form-control rounded-3" required>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
<input type="text" name="name_ar" class="form-control rounded-3" dir="rtl" required>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Category" %}</label>
<select name="category" class="form-select rounded-3" required>
<option value="">{% trans "Select Category" %}</option>
{% for category in categories %}
<option value="{{ category.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ category.name_ar }}{% else %}{{ category.name_en }}{% endif %}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "SKU" %}</label>
<input type="text" name="sku" class="form-control rounded-3" required>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Price" %}</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0">{{ site_settings.currency_symbol }}</span>
<input type="number" step="0.01" name="price" class="form-control rounded-3 border-start-0" required>
</div>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Initial Stock" %}</label>
<input type="number" name="stock" class="form-control rounded-3" value="0" required>
</div>
</div>
</div>
<div class="modal-footer border-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 "Save Product" %}</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,428 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "POS" %} | {{ site_settings.business_name }}{% endblock %}
{% block head %}
<style>
.product-card {
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
border: none;
border-radius: 15px;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1) !important;
}
.cart-container {
position: sticky;
top: 80px;
height: calc(100vh - 120px);
display: flex;
flex-direction: column;
}
.cart-items {
flex-grow: 1;
overflow-y: auto;
}
.category-badge {
cursor: pointer;
padding: 8px 15px;
border-radius: 20px;
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
background: #f8f9fa;
color: #6c757d;
transition: all 0.2s;
}
.category-badge.active {
background: var(--bs-primary);
color: white;
}
/* Invoice Print Styles */
@media print {
body * {
visibility: hidden;
}
#invoice-print, #invoice-print * {
visibility: visible;
}
#invoice-print {
position: absolute;
left: 0;
top: 0;
width: 80mm;
padding: 5mm;
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
color: black;
}
.no-print {
display: none !important;
}
}
#invoice-print {
display: none;
width: 80mm;
margin: auto;
border: 1px solid #eee;
padding: 5mm;
background: white;
}
.invoice-header { text-align: center; margin-bottom: 10px; border-bottom: 1px dashed #000; padding-bottom: 10px; }
.invoice-title { font-size: 16px; font-weight: bold; margin-bottom: 5px; }
.invoice-details { margin-bottom: 10px; font-size: 11px; }
.invoice-table { width: 100%; border-collapse: collapse; margin-bottom: 10px; }
.invoice-table th { border-bottom: 1px solid #000; text-align: left; padding: 2px 0; font-size: 11px; }
.invoice-table td { padding: 4px 0; font-size: 11px; vertical-align: top; }
.invoice-total { border-top: 1px dashed #000; padding-top: 5px; }
.bilingual { display: flex; justify-content: space-between; font-size: 10px; color: #555; }
.rtl { direction: rtl; text-align: right; }
.ltr { direction: ltr; text-align: left; }
</style>
{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<div class="row g-4">
<!-- Products Section -->
<div class="col-lg-8">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="fw-bold mb-0">{% trans "Point of Sale" %}</h4>
<div class="input-group w-50">
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search"></i></span>
<input type="text" id="productSearch" class="form-control border-start-0" placeholder="{% trans 'Search products...' %}">
</div>
</div>
<div class="mb-4">
<div class="category-badge active" data-category="all">{% trans "All" %}</div>
{% for category in categories %}
<div class="category-badge" data-category="{{ category.id }}">
{% if LANGUAGE_CODE == 'ar' %}{{ category.name_ar }}{% else %}{{ category.name_en }}{% endif %}
</div>
{% endfor %}
</div>
<div class="row row-cols-1 row-cols-md-3 g-3" id="productGrid">
{% for product in products %}
<div class="col product-item" data-category="{{ product.category.id }}" data-name-en="{{ product.name_en|lower }}" data-name-ar="{{ product.name_ar }}">
<div class="card h-100 shadow-sm product-card p-2" onclick="addToCart({{ product.id }}, '{{ product.name_en|escapejs }}', '{{ product.name_ar|escapejs }}', {{ product.price }})">
{% if product.image %}
<img src="{{ product.image }}" class="card-img-top rounded-3" alt="{{ product.name_en }}" style="height: 150px; object-fit: cover;">
{% else %}
<div class="bg-light rounded-3 d-flex align-items-center justify-content-center" style="height: 150px;">
<i class="bi bi-image text-muted opacity-25" style="font-size: 3rem;"></i>
</div>
{% endif %}
<div class="card-body p-2 text-center">
<h6 class="fw-bold mb-1">
{% if LANGUAGE_CODE == 'ar' %}{{ product.name_ar }}{% else %}{{ product.name_en }}{% endif %}
</h6>
<p class="text-primary fw-bold mb-0">{{ site_settings.currency_symbol }}{{ product.price }}</p>
<small class="text-muted">{% trans "Stock" %}: {{ product.stock_quantity }}</small>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Cart Section -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm rounded-4 cart-container">
<div class="card-header bg-white border-0 pt-4 px-4">
<h5 class="fw-bold">{% trans "Current Order" %}</h5>
<select id="customerSelect" class="form-select form-select-sm mt-3">
<option value="">{% trans "Walking Customer" %}</option>
{% for customer in customers %}
<option value="{{ customer.id }}">{{ customer.name }}</option>
{% endfor %}
</select>
</div>
<div class="card-body px-4 py-2 cart-items">
<div id="cartItemsList">
<!-- Cart items will be injected here -->
</div>
<div class="text-center py-5 text-muted opacity-50" id="emptyCartMsg">
<i class="bi bi-cart3 display-1 d-block mb-3"></i>
{% trans "Your cart is empty" %}
</div>
</div>
<div class="card-footer bg-light border-0 p-4 rounded-bottom-4">
<div class="d-flex justify-content-between mb-2">
<span>{% trans "Subtotal" %}</span>
<span id="subtotalAmount">{{ site_settings.currency_symbol }}0.00</span>
</div>
<div class="d-flex justify-content-between mb-3">
<span class="fw-bold fs-5">{% trans "Total" %}</span>
<span class="fw-bold fs-5 text-primary" id="totalAmount">{{ site_settings.currency_symbol }}0.00</span>
</div>
<button id="payNowBtn" class="btn btn-primary w-100 py-3 fw-bold rounded-3" onclick="checkout()" disabled>
{% trans "PAY NOW" %}
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Invoice Template (Hidden) -->
<div id="invoice-print">
<div class="invoice-header">
<div class="invoice-title" id="inv-business-name"></div>
<div class="bilingual"><span class="ltr">TAX INVOICE</span><span class="rtl">فاتورة ضريبية</span></div>
<div id="inv-business-address" style="font-size: 10px;"></div>
<div id="inv-business-phone" style="font-size: 10px;"></div>
</div>
<div class="invoice-details">
<div class="d-flex justify-content-between">
<span>Inv #: <span id="inv-id"></span></span>
<span class="rtl">رقم الفاتورة: <span id="inv-id-ar"></span></span>
</div>
<div class="d-flex justify-content-between">
<span>Date: <span id="inv-date"></span></span>
<span class="rtl">التاريخ: <span id="inv-date-ar"></span></span>
</div>
</div>
<table class="invoice-table">
<thead>
<tr>
<th>Item / الصنف</th>
<th style="text-align: center;">Qty</th>
<th style="text-align: right;">Total</th>
</tr>
</thead>
<tbody id="inv-items">
<!-- Items injected here -->
</tbody>
</table>
<div class="invoice-total">
<div class="d-flex justify-content-between fw-bold">
<span>TOTAL / المجموع</span>
<span id="inv-total"></span>
</div>
</div>
<div class="text-center mt-3" style="font-size: 10px; border-top: 1px dashed #000; padding-top: 5px;">
THANK YOU / شكراً لزيارتكم
</div>
</div>
<!-- Receipt Modal -->
<div class="modal fade" id="receiptModal" tabindex="-1">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-body text-center p-4">
<i class="bi bi-check-circle-fill text-success display-1 mb-3"></i>
<h4 class="fw-bold">{% trans "Success!" %}</h4>
<p class="text-muted">{% trans "Transaction completed." %}</p>
<div class="d-grid gap-2">
<button type="button" class="btn btn-primary" onclick="printInvoice()">
<i class="bi bi-printer me-2"></i> {% trans "Print Invoice" %}
</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let cart = [];
let lastSaleData = null;
const lang = '{{ LANGUAGE_CODE }}';
const currency = '{{ site_settings.currency_symbol }}';
function addToCart(id, nameEn, nameAr, price) {
const existing = cart.find(item => item.id === id);
if (existing) {
existing.quantity += 1;
existing.line_total = existing.quantity * price;
} else {
cart.push({
id,
name: lang === 'ar' ? nameAr : nameEn,
name_en: nameEn,
name_ar: nameAr,
price,
quantity: 1,
line_total: price
});
}
renderCart();
}
function updateQuantity(id, delta) {
const item = cart.find(item => item.id === id);
if (item) {
item.quantity += delta;
if (item.quantity <= 0) {
cart = cart.filter(i => i.id !== id);
} else {
item.line_total = item.quantity * item.price;
}
}
renderCart();
}
function renderCart() {
const listContainer = document.getElementById('cartItemsList');
const emptyMsg = document.getElementById('emptyCartMsg');
const payBtn = document.getElementById('payNowBtn');
if (cart.length === 0) {
emptyMsg.classList.remove('d-none');
listContainer.innerHTML = '';
document.getElementById('subtotalAmount').innerText = `${currency}0.00`;
document.getElementById('totalAmount').innerText = `${currency}0.00`;
payBtn.disabled = true;
return;
}
emptyMsg.classList.add('d-none');
payBtn.disabled = false;
let html = '';
let total = 0;
cart.forEach(item => {
total += item.line_total;
html += `
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<div class="fw-bold small">${item.name}</div>
<div class="text-muted small">${currency}${item.price} x ${item.quantity}</div>
</div>
<div class="d-flex align-items-center gap-2">
<button class="btn btn-sm btn-outline-secondary rounded-circle" onclick="updateQuantity(${item.id}, -1)">-</button>
<span class="fw-bold">${item.quantity}</span>
<button class="btn btn-sm btn-outline-secondary rounded-circle" onclick="updateQuantity(${item.id}, 1)">+</button>
</div>
</div>
`;
});
listContainer.innerHTML = html;
document.getElementById('subtotalAmount').innerText = `${currency}${total.toFixed(2)}`;
document.getElementById('totalAmount').innerText = `${currency}${total.toFixed(2)}`;
}
function checkout() {
if (cart.length === 0) return;
const payBtn = document.getElementById('payNowBtn');
const originalText = payBtn.innerText;
payBtn.disabled = true;
payBtn.innerText = '{% trans "Processing..." %}';
const data = {
customer_id: document.getElementById('customerSelect').value,
items: cart,
total_amount: cart.reduce((acc, item) => acc + item.line_total, 0),
discount: 0
};
fetch('{% url "create_sale_api" %}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}'
},
body: JSON.stringify(data)
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw err; });
}
return response.json();
})
.then(data => {
if (data.success) {
lastSaleData = data;
prepareInvoice(data);
cart = [];
renderCart();
const receiptModal = new bootstrap.Modal(document.getElementById('receiptModal'));
receiptModal.show();
} else {
alert('Error: ' + data.error);
}
})
.catch(error => {
console.error('Checkout error:', error);
alert('Checkout failed: ' + (error.error || error.message || 'Unknown error'));
})
.finally(() => {
payBtn.disabled = cart.length === 0;
payBtn.innerText = originalText;
});
}
function prepareInvoice(data) {
document.getElementById('inv-business-name').innerText = data.business.name;
document.getElementById('inv-business-address').innerText = data.business.address;
document.getElementById('inv-business-phone').innerText = 'Tel: ' + data.business.phone;
document.getElementById('inv-id').innerText = data.sale.id;
document.getElementById('inv-id-ar').innerText = data.sale.id;
document.getElementById('inv-date').innerText = data.sale.created_at;
document.getElementById('inv-date-ar').innerText = data.sale.created_at;
let itemsHtml = '';
data.sale.items.forEach(item => {
itemsHtml += `
<tr>
<td>
<div>${item.name_en}</div>
<div class="rtl text-muted" style="font-size: 9px;">${item.name_ar}</div>
</td>
<td style="text-align: center;">${item.qty}</td>
<td style="text-align: right;">${data.business.currency}${item.total.toFixed(2)}</td>
</tr>
`;
});
document.getElementById('inv-items').innerHTML = itemsHtml;
document.getElementById('inv-total').innerText = data.business.currency + data.sale.total.toFixed(2);
}
function printInvoice() {
window.print();
}
// Search and Category Filtering
document.getElementById('productSearch').addEventListener('input', filterProducts);
document.querySelectorAll('.category-badge').forEach(badge => {
badge.addEventListener('click', function() {
document.querySelectorAll('.category-badge').forEach(b => b.classList.remove('active'));
this.classList.add('active');
filterProducts();
});
});
function filterProducts() {
const searchTerm = document.getElementById('productSearch').value.toLowerCase();
const activeCategory = document.querySelector('.category-badge.active').dataset.category;
document.querySelectorAll('.product-item').forEach(item => {
const nameEn = item.dataset.nameEn;
const nameAr = item.dataset.nameAr;
const category = item.dataset.category;
const matchesSearch = nameEn.includes(searchTerm) || nameAr.includes(searchTerm);
const matchesCategory = activeCategory === 'all' || category === activeCategory;
if (matchesSearch && matchesCategory) {
item.classList.remove('d-none');
} else {
item.classList.add('d-none');
}
});
}
</script>
{% endblock %}

View File

@ -0,0 +1,98 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Purchases" %} | Meezan Accounting{% endblock %}
{% block content %}
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold mb-0">{% trans "Purchase History" %}</h2>
<button class="btn btn-primary rounded-3" data-bs-toggle="modal" data-bs-target="#addPurchaseModal">
<i class="bi bi-cart-plus me-2"></i>{% trans "New Purchase" %}
</button>
</div>
{% if messages %}
<div class="mb-4">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show rounded-3" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
{% endfor %}
</div>
{% endif %}
<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">
<tr>
<th class="ps-4">{% trans "Date" %}</th>
<th>{% trans "Supplier" %}</th>
<th>{% trans "Total Amount" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for purchase in purchases %}
<tr>
<td class="ps-4">{{ purchase.created_at|date:"Y-m-d H:i" }}</td>
<td>{{ purchase.supplier.name|default:"-" }}</td>
<td class="fw-bold text-primary">{{ site_settings.currency_symbol }}{{ purchase.total_amount }}</td>
<td class="text-end pe-4">
<button class="btn btn-sm btn-light rounded-circle"><i class="bi bi-eye"></i></button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center py-5 text-muted">
{% trans "No purchases found." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Add Purchase Modal -->
<div class="modal fade" id="addPurchaseModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow rounded-4">
<div class="modal-header border-0">
<h5 class="fw-bold">{% trans "Record New Purchase" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="{% url 'add_purchase' %}" method="POST">
{% csrf_token %}
<div class="modal-body">
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Supplier" %}</label>
<select name="supplier" class="form-select rounded-3" required>
<option value="">{% trans "Select Supplier" %}</option>
{% for supplier in suppliers %}
<option value="{{ supplier.id }}">{{ supplier.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Total Amount" %}</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0">{{ site_settings.currency_symbol }}</span>
<input type="number" step="0.01" name="total_amount" class="form-control rounded-3 border-start-0" required>
</div>
</div>
</div>
<div class="modal-footer border-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 "Record Purchase" %}</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,70 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Smart Reports" %} | Meezan Accounting{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row mb-4">
<div class="col">
<h1 class="h3 fw-bold">{% trans "Analytics & Reports" %}</h1>
<p class="text-muted">{% trans "Deep dive into your business performance." %}</p>
</div>
</div>
<div class="row g-4">
<!-- Monthly Revenue Table -->
<div class="col-lg-6">
<div class="card border-0 shadow-sm p-4 h-100">
<h5 class="fw-bold mb-4">{% trans "Monthly Revenue" %}</h5>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>{% trans "Month" %}</th>
<th class="text-end">{% trans "Revenue" %}</th>
</tr>
</thead>
<tbody>
{% for sale in monthly_sales %}
<tr>
<td>{{ sale.month|date:"F Y" }}</td>
<td class="text-end fw-bold">{{ site_settings.currency_symbol }}{{ sale.total|floatformat:2 }}</td>
</tr>
{% empty %}
<tr>
<td colspan="2" class="text-center text-muted">{% trans "No data available." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Top Products -->
<div class="col-lg-6">
<div class="card border-0 shadow-sm p-4 h-100">
<h5 class="fw-bold mb-4">{% trans "Top Selling Products" %}</h5>
<ul class="list-group list-group-flush">
{% for item in top_products %}
<li class="list-group-item px-0 py-3 border-0 d-flex justify-content-between align-items-center">
<div>
<p class="mb-0 fw-bold">
{% if LANGUAGE_CODE == 'ar' %}{{ item.product__name_ar }}{% else %}{{ item.product__name_en }}{% endif %}
</p>
<small class="text-muted">{{ item.total_qty }} {% trans "units sold" %}</small>
</div>
<div class="text-end">
<span class="d-block fw-bold text-primary">{{ site_settings.currency_symbol }}{{ item.revenue|floatformat:2 }}</span>
</div>
</li>
{% empty %}
<li class="list-group-item px-0 border-0 text-center text-muted">{% trans "No sales data." %}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,102 @@
{% extends 'base.html' %}
{% load i18n %}
{% block content %}
<div class="container-fluid">
<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>
</div>
</div>
{% if messages %}
<div class="row">
<div class="col-12">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
</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">
{% csrf_token %}
<div class="row g-3">
<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-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., $, £, SAR, AED" %}</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>
</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>
{% endblock %}

View File

@ -0,0 +1,95 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Suppliers" %} | Meezan Accounting{% endblock %}
{% block content %}
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold mb-0">{% trans "Suppliers" %}</h2>
<button class="btn btn-primary rounded-3" data-bs-toggle="modal" data-bs-target="#addSupplierModal">
<i class="bi bi-truck me-2"></i>{% trans "Add Supplier" %}
</button>
</div>
{% if messages %}
<div class="mb-4">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show rounded-3" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
{% endfor %}
</div>
{% endif %}
<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">
<tr>
<th class="ps-4">{% trans "Name" %}</th>
<th>{% trans "Contact Person" %}</th>
<th>{% trans "Phone" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for supplier in suppliers %}
<tr>
<td class="ps-4 fw-bold">{{ supplier.name }}</td>
<td>{{ supplier.contact_person }}</td>
<td>{{ supplier.phone }}</td>
<td class="text-end pe-4">
<button class="btn btn-sm btn-light rounded-circle"><i class="bi bi-pencil"></i></button>
<button class="btn btn-sm btn-light rounded-circle text-danger"><i class="bi bi-trash"></i></button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center py-5 text-muted">
{% trans "No suppliers found." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Add Supplier Modal -->
<div class="modal fade" id="addSupplierModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow rounded-4">
<div class="modal-header border-0">
<h5 class="fw-bold">{% trans "Add New Supplier" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="{% url 'add_supplier' %}" method="POST">
{% csrf_token %}
<div class="modal-body">
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Supplier Name" %}</label>
<input type="text" name="name" class="form-control rounded-3" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Contact Person" %}</label>
<input type="text" name="contact_person" class="form-control rounded-3">
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
<input type="text" name="phone" class="form-control rounded-3">
</div>
</div>
<div class="modal-footer border-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 "Save Supplier" %}</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,7 +1,20 @@
from django.urls import path from django.urls import path
from . import views
from .views import home
urlpatterns = [ urlpatterns = [
path("", home, name="home"), path('', views.index, name='index'),
] path('inventory/', views.inventory, name='inventory'),
path('pos/', views.pos, name='pos'),
path('customers/', views.customers, name='customers'),
path('suppliers/', views.suppliers, name='suppliers'),
path('purchases/', views.purchases, name='purchases'),
path('reports/', views.reports, name='reports'),
path('settings/', views.settings_view, name='settings'),
# API / Actions
path('api/create-sale/', views.create_sale_api, name='create_sale_api'),
path('customers/add/', views.add_customer, name='add_customer'),
path('suppliers/add/', views.add_supplier, name='add_supplier'),
path('purchases/add/', views.add_purchase, name='add_purchase'),
path('inventory/add/', views.add_product, name='add_product'),
]

View File

@ -1,25 +1,236 @@
import os from django.shortcuts import render, get_object_or_404, redirect
import platform from django.db.models import Sum, Count, F
from django.db.models.functions import TruncDate, TruncMonth
from django import get_version as django_version from django.http import JsonResponse
from django.shortcuts import render from django.views.decorators.csrf import csrf_exempt
from .models import Product, Sale, Category, Customer, Supplier, Purchase, SaleItem, SystemSetting
import json
from datetime import timedelta
from django.utils import timezone from django.utils import timezone
from django.contrib import messages
def index(request):
def home(request): """
"""Render the landing screen with loader and environment details.""" Enhanced Meezan Dashboard View
host_name = request.get_host().lower() """
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" # Summary Stats
now = timezone.now() total_products = Product.objects.count()
total_sales_count = Sale.objects.count()
total_sales_amount = Sale.objects.aggregate(total=Sum('total_amount'))['total'] or 0
total_customers = Customer.objects.count()
# Stock Alert (Low stock < 5)
low_stock_products = Product.objects.filter(stock_quantity__lt=5)
# Recent Transactions
recent_sales = Sale.objects.order_by('-created_at')[:5]
# Chart Data: Sales for the last 7 days
seven_days_ago = timezone.now().date() - timedelta(days=6)
sales_over_time = Sale.objects.filter(created_at__date__gte=seven_days_ago) \
.annotate(date=TruncDate('created_at')) \
.values('date') \
.annotate(total=Sum('total_amount')) \
.order_by('date')
# Prepare data for Chart.js
chart_labels = []
chart_data = []
date_dict = {s['date']: float(s['total']) for s in sales_over_time}
for i in range(7):
date = seven_days_ago + timedelta(days=i)
chart_labels.append(date.strftime('%b %d'))
chart_data.append(date_dict.get(date, 0))
context = { context = {
"project_name": "New Style", 'total_products': total_products,
"agent_brand": agent_brand, 'total_sales_count': total_sales_count,
"django_version": django_version(), 'total_sales_amount': total_sales_amount,
"python_version": platform.python_version(), 'total_customers': total_customers,
"current_time": now, 'low_stock_products': low_stock_products,
"host_name": host_name, 'recent_sales': recent_sales,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""), 'chart_labels': json.dumps(chart_labels),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), 'chart_data': json.dumps(chart_data),
} }
return render(request, "core/index.html", context) return render(request, 'core/index.html', context)
def inventory(request):
products = Product.objects.all().select_related('category')
categories = Category.objects.all()
context = {'products': products, 'categories': categories}
return render(request, 'core/inventory.html', context)
def pos(request):
products = Product.objects.all().filter(stock_quantity__gt=0)
customers = Customer.objects.all()
categories = Category.objects.all()
context = {'products': products, 'customers': customers, 'categories': categories}
return render(request, 'core/pos.html', context)
def customers(request):
customers_list = Customer.objects.all().annotate(total_sales=Sum('sale__total_amount'))
context = {'customers': customers_list}
return render(request, 'core/customers.html', context)
def suppliers(request):
suppliers_list = Supplier.objects.all()
context = {'suppliers': suppliers_list}
return render(request, 'core/suppliers.html', context)
def purchases(request):
purchases_list = Purchase.objects.all().select_related('supplier')
suppliers_list = Supplier.objects.all()
context = {'purchases': purchases_list, 'suppliers': suppliers_list}
return render(request, 'core/purchases.html', context)
def reports(request):
"""
Smart Reports View
"""
# Monthly Revenue
monthly_sales = Sale.objects.annotate(month=TruncMonth('created_at')) \
.values('month') \
.annotate(total=Sum('total_amount')) \
.order_by('-month')[:12]
# Top Selling Products
top_products = SaleItem.objects.values('product__name_en', 'product__name_ar') \
.annotate(total_qty=Sum('quantity'), revenue=Sum('line_total')) \
.order_by('-total_qty')[:5]
context = {
'monthly_sales': monthly_sales,
'top_products': top_products,
}
return render(request, 'core/reports.html', context)
def settings_view(request):
"""
Smart Admin Settings View
"""
settings = SystemSetting.objects.first()
if not settings:
settings = SystemSetting.objects.create()
if request.method == 'POST':
settings.business_name = request.POST.get('business_name')
settings.address = request.POST.get('address')
settings.phone = request.POST.get('phone')
settings.email = request.POST.get('email')
settings.currency_symbol = request.POST.get('currency_symbol')
settings.tax_rate = request.POST.get('tax_rate')
settings.save()
messages.success(request, "Settings updated successfully!")
return redirect('settings')
return render(request, 'core/settings.html', {'settings': settings})
@csrf_exempt
def create_sale_api(request):
if request.method == 'POST':
try:
data = json.loads(request.body)
customer_id = data.get('customer_id')
items = data.get('items', [])
total_amount = data.get('total_amount', 0)
discount = data.get('discount', 0)
customer = None
if customer_id:
customer = Customer.objects.get(id=customer_id)
sale = Sale.objects.create(
customer=customer,
total_amount=total_amount,
discount=discount
)
for item in items:
product = Product.objects.get(id=item['id'])
SaleItem.objects.create(
sale=sale,
product=product,
quantity=item['quantity'],
unit_price=item['price'],
line_total=item['line_total']
)
product.stock_quantity -= item['quantity']
product.save()
settings = SystemSetting.objects.first()
return JsonResponse({
'success': True,
'sale_id': sale.id,
'business': {
'name': settings.business_name,
'address': settings.address,
'phone': settings.phone,
'currency': settings.currency_symbol
},
'sale': {
'id': sale.id,
'created_at': sale.created_at.strftime("%Y-%m-%d %H:%M"),
'total': float(sale.total_amount),
'items': [
{
'name_en': item.product.name_en,
'name_ar': item.product.name_ar,
'qty': item.quantity,
'price': float(item.unit_price),
'total': float(item.line_total)
} for item in sale.items.all()
]
}
})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)}, status=400)
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
def add_customer(request):
if request.method == 'POST':
name = request.POST.get('name')
phone = request.POST.get('phone')
email = request.POST.get('email')
address = request.POST.get('address')
Customer.objects.create(name=name, phone=phone, email=email, address=address)
messages.success(request, "Customer added successfully!")
return redirect('customers')
def add_supplier(request):
if request.method == 'POST':
name = request.POST.get('name')
contact_person = request.POST.get('contact_person')
phone = request.POST.get('phone')
Supplier.objects.create(name=name, contact_person=contact_person, phone=phone)
messages.success(request, "Supplier added successfully!")
return redirect('suppliers')
def add_purchase(request):
if request.method == 'POST':
supplier_id = request.POST.get('supplier')
total_amount = request.POST.get('total_amount')
supplier = get_object_or_404(Supplier, id=supplier_id)
Purchase.objects.create(supplier=supplier, total_amount=total_amount)
messages.success(request, "Purchase recorded successfully!")
return redirect('purchases')
def add_product(request):
if request.method == 'POST':
name_en = request.POST.get('name_en')
name_ar = request.POST.get('name_ar')
category_id = request.POST.get('category')
sku = request.POST.get('sku')
price = request.POST.get('price')
stock = request.POST.get('stock')
category = get_object_or_404(Category, id=category_id)
Product.objects.create(
name_en=name_en,
name_ar=name_ar,
category=category,
sku=sku,
price=price,
stock_quantity=stock
)
messages.success(request, "Product added successfully!")
return redirect('inventory')

View File

@ -1,4 +1,159 @@
/* Custom styles for the application */ /* Meezan Accounting Custom Styles */
body { :root {
font-family: system-ui, -apple-system, sans-serif; --meezan-primary: #2E5BFF;
--meezan-secondary: #8C9EFF;
--meezan-accent: #FFAB40;
--meezan-bg: #F8FAFF;
--meezan-sidebar-bg: #FFFFFF;
--meezan-text: #1A202C;
--meezan-glass: rgba(255, 255, 255, 0.7);
--sidebar-width: 260px;
}
body {
font-family: 'Plus Jakarta Sans', 'Cairo', sans-serif;
background-color: var(--meezan-bg);
color: var(--meezan-text);
overflow-x: hidden;
}
/* RTL Specific Tweaks */
[dir="rtl"] {
font-family: 'Cairo', 'Plus Jakarta Sans', sans-serif;
}
.bg-primary {
background-color: var(--meezan-primary) !important;
}
.text-primary {
color: var(--meezan-primary) !important;
}
/* Glassmorphism Effect */
.glass-card {
background: var(--meezan-glass);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 16px;
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
}
/* Sidebar Styling */
#wrapper {
display: flex;
width: 100%;
align-items: stretch;
}
#sidebar {
min-width: var(--sidebar-width);
max-width: var(--sidebar-width);
background: var(--meezan-sidebar-bg);
color: var(--meezan-text);
transition: all 0.3s;
border-inline-end: 1px solid rgba(0,0,0,0.05);
z-index: 1000;
position: sticky;
top: 0;
height: 100vh;
}
#sidebar.active {
margin-inline-start: calc(-1 * var(--sidebar-width));
}
#sidebar .sidebar-header {
padding: 20px;
background: var(--meezan-sidebar-bg);
}
#sidebar ul.components {
padding: 20px 0;
}
#sidebar ul li a {
padding: 12px 25px;
font-size: 1rem;
display: flex;
align-items: center;
color: var(--meezan-text);
text-decoration: none;
transition: all 0.3s;
border-radius: 0 50px 50px 0;
margin-inline-end: 15px;
}
[dir="rtl"] #sidebar ul li a {
border-radius: 50px 0 0 50px;
margin-inline-start: 15px;
margin-inline-end: 0;
}
#sidebar ul li a:hover, #sidebar ul li a.active {
background: rgba(46, 91, 255, 0.1);
color: var(--meezan-primary);
font-weight: 600;
}
#sidebar ul li a i {
margin-inline-end: 15px;
font-size: 1.2rem;
}
/* Main Content Styling */
#content {
width: 100%;
padding: 0;
min-height: 100vh;
transition: all 0.3s;
}
.top-navbar {
padding: 15px 30px;
background: #fff;
border-bottom: 1px solid rgba(0,0,0,0.05);
}
/* Hero Section */
.hero-gradient {
background: linear-gradient(135deg, var(--meezan-primary) 0%, var(--meezan-secondary) 100%);
border-radius: 24px;
padding: 3rem;
color: white;
position: relative;
overflow: hidden;
}
/* Buttons */
.btn-primary {
background-color: var(--meezan-primary);
border: none;
padding: 0.6rem 1.5rem;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s ease;
}
/* Dashboard Stats */
.stat-card {
transition: transform 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
}
/* Mobile Tweaks */
@media (max-width: 992px) {
#sidebar {
margin-inline-start: calc(-1 * var(--sidebar-width));
position: fixed;
}
#sidebar.active {
margin-inline-start: 0;
}
#content {
width: 100%;
}
} }

View File

@ -1,21 +1,159 @@
/* Meezan Accounting Custom Styles */
:root { :root {
--bg-color-start: #6a11cb; --meezan-primary: #2E5BFF;
--bg-color-end: #2575fc; --meezan-secondary: #8C9EFF;
--text-color: #ffffff; --meezan-accent: #FFAB40;
--card-bg-color: rgba(255, 255, 255, 0.01); --meezan-bg: #F8FAFF;
--card-border-color: rgba(255, 255, 255, 0.1); --meezan-sidebar-bg: #FFFFFF;
--meezan-text: #1A202C;
--meezan-glass: rgba(255, 255, 255, 0.7);
--sidebar-width: 260px;
} }
body { body {
margin: 0; font-family: 'Plus Jakarta Sans', 'Cairo', sans-serif;
font-family: 'Inter', sans-serif; background-color: var(--meezan-bg);
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); color: var(--meezan-text);
color: var(--text-color); overflow-x: hidden;
display: flex; }
justify-content: center;
align-items: center; /* RTL Specific Tweaks */
min-height: 100vh; [dir="rtl"] {
text-align: center; font-family: 'Cairo', 'Plus Jakarta Sans', sans-serif;
overflow: hidden; }
position: relative;
.bg-primary {
background-color: var(--meezan-primary) !important;
}
.text-primary {
color: var(--meezan-primary) !important;
}
/* Glassmorphism Effect */
.glass-card {
background: var(--meezan-glass);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 16px;
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
}
/* Sidebar Styling */
#wrapper {
display: flex;
width: 100%;
align-items: stretch;
}
#sidebar {
min-width: var(--sidebar-width);
max-width: var(--sidebar-width);
background: var(--meezan-sidebar-bg);
color: var(--meezan-text);
transition: all 0.3s;
border-inline-end: 1px solid rgba(0,0,0,0.05);
z-index: 1000;
position: sticky;
top: 0;
height: 100vh;
}
#sidebar.active {
margin-inline-start: calc(-1 * var(--sidebar-width));
}
#sidebar .sidebar-header {
padding: 20px;
background: var(--meezan-sidebar-bg);
}
#sidebar ul.components {
padding: 20px 0;
}
#sidebar ul li a {
padding: 12px 25px;
font-size: 1rem;
display: flex;
align-items: center;
color: var(--meezan-text);
text-decoration: none;
transition: all 0.3s;
border-radius: 0 50px 50px 0;
margin-inline-end: 15px;
}
[dir="rtl"] #sidebar ul li a {
border-radius: 50px 0 0 50px;
margin-inline-start: 15px;
margin-inline-end: 0;
}
#sidebar ul li a:hover, #sidebar ul li a.active {
background: rgba(46, 91, 255, 0.1);
color: var(--meezan-primary);
font-weight: 600;
}
#sidebar ul li a i {
margin-inline-end: 15px;
font-size: 1.2rem;
}
/* Main Content Styling */
#content {
width: 100%;
padding: 0;
min-height: 100vh;
transition: all 0.3s;
}
.top-navbar {
padding: 15px 30px;
background: #fff;
border-bottom: 1px solid rgba(0,0,0,0.05);
}
/* Hero Section */
.hero-gradient {
background: linear-gradient(135deg, var(--meezan-primary) 0%, var(--meezan-secondary) 100%);
border-radius: 24px;
padding: 3rem;
color: white;
position: relative;
overflow: hidden;
}
/* Buttons */
.btn-primary {
background-color: var(--meezan-primary);
border: none;
padding: 0.6rem 1.5rem;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s ease;
}
/* Dashboard Stats */
.stat-card {
transition: transform 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
}
/* Mobile Tweaks */
@media (max-width: 992px) {
#sidebar {
margin-inline-start: calc(-1 * var(--sidebar-width));
position: fixed;
}
#sidebar.active {
margin-inline-start: 0;
}
#content {
width: 100%;
}
} }