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.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
@ -85,6 +86,7 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages',
# IMPORTANT: do not remove injects PROJECT_DESCRIPTION/PROJECT_IMAGE_URL and cache-busting timestamp
'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/
LANGUAGE_CODE = 'en-us'
LANGUAGES = [
('en', 'English'),
('ar', 'Arabic'),
]
LOCALE_PATHS = [BASE_DIR / 'locale']
TIME_ZONE = 'UTC'
@ -148,6 +156,8 @@ USE_TZ = True
STATIC_URL = 'static/'
# Collect static into a separate folder; avoid overlapping with STATICFILES_DIRS.
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'media'
STATICFILES_DIRS = [
BASE_DIR / 'static',

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.urls import include, path
from django.conf import settings
@ -21,9 +5,11 @@ from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("i18n/", include("django.conf.urls.i18n")),
path("", include("core.urls")),
]
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)

View File

@ -1,3 +1,36 @@
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 time
from django.utils import timezone
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 {
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
# Used for cache-busting static assets
"deployment_timestamp": int(time.time()),
"deployment_timestamp": int(timezone.now().timestamp()),
}
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.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>
<html lang="en">
{% load static i18n %}{% get_current_language as LANGUAGE_CODE %}<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_CODE == 'ar' %}rtl{% else %}ltr{% endif %}">
<head>
<meta charset="UTF-8">
<title>{% block title %}Knowledge Base{% endblock %}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}">
<meta property="twitter:description" content="{{ project_description }}">
{% endif %}
{% if project_image_url %}
<meta property="og:image" content="{{ project_image_url }}">
<meta property="twitter:image" content="{{ project_image_url }}">
{% endif %}
{% load static %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ site_settings.business_name }}{% endblock %}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}">
{% endif %}
<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=Cairo:wght@400;700&family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" rel="stylesheet">
{% 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>
<body>
{% block content %}{% endblock %}
</body>
<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 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 title %}{% trans "Smart Dashboard" %} - {{ site_settings.business_name }}{% endblock %}
{% block content %}
<main>
<div class="card">
<h1>Analyzing your requirements and generating your app…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
<div class="container-fluid">
<!-- Header -->
<div class="row mb-4 align-items-center">
<div class="col">
<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>
<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>
<p class="runtime">
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
</p>
</div>
</main>
<footer>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
</footer>
<!-- Stats Cards -->
<div class="row g-3 mb-4">
<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-primary text-white bg-opacity-10 text-primary rounded-3 p-3 me-3">
<i class="bi bi-cash-stack fs-4 text-primary"></i>
</div>
<div>
<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 %}

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 .views import home
from . import views
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
import platform
from django import get_version as django_version
from django.shortcuts import render
from django.shortcuts import render, get_object_or_404, redirect
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 .models import Product, Sale, Category, Customer, Supplier, Purchase, SaleItem, SystemSetting
import json
from datetime import timedelta
from django.utils import timezone
from django.contrib import messages
def index(request):
"""
Enhanced Meezan Dashboard View
"""
# Summary Stats
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()
def home(request):
"""Render the landing screen with loader and environment details."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
# 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 = {
"project_name": "New Style",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
'total_products': total_products,
'total_sales_count': total_sales_count,
'total_sales_amount': total_sales_amount,
'total_customers': total_customers,
'low_stock_products': low_stock_products,
'recent_sales': recent_sales,
'chart_labels': json.dumps(chart_labels),
'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 */
body {
font-family: system-ui, -apple-system, sans-serif;
/* Meezan Accounting Custom Styles */
:root {
--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 {
--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);
--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 {
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;
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%;
}
}