Autosave: 20260202-093633

This commit is contained in:
Flatlogic Bot 2026-02-02 09:36:33 +00:00
parent 7d1c8df2b2
commit 5391ba1010
33 changed files with 3038 additions and 304 deletions

View File

@ -1,15 +1,19 @@
from django.contrib import admin
from .models import Category, Product, Customer, Supplier, Sale, SaleItem, Purchase
from .models import Category, Unit, Product, Customer, Supplier, Sale, SaleItem, Purchase, SystemSetting
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name_en', 'name_ar', 'slug')
prepopulated_fields = {'slug': ('name_en',)}
@admin.register(Unit)
class UnitAdmin(admin.ModelAdmin):
list_display = ('name_en', 'name_ar', 'short_name')
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ('name_en', 'name_ar', 'sku', 'price', 'stock_quantity', 'category')
list_filter = ('category',)
list_display = ('name_en', 'name_ar', 'sku', 'price', 'stock_quantity', 'category', 'unit')
list_filter = ('category', 'unit')
search_fields = ('name_en', 'name_ar', 'sku')
@admin.register(Customer)
@ -34,3 +38,7 @@ class SaleAdmin(admin.ModelAdmin):
class PurchaseAdmin(admin.ModelAdmin):
list_display = ('id', 'supplier', 'total_amount', 'created_at')
list_filter = ('supplier', 'created_at')
@admin.register(SystemSetting)
class SystemSettingAdmin(admin.ModelAdmin):
list_display = ('business_name', 'phone', 'email', 'vat_number')

View File

@ -0,0 +1,67 @@
# Generated by Django 5.2.7 on 2026-02-02 07:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0002_systemsetting'),
]
operations = [
migrations.RemoveField(
model_name='systemsetting',
name='logo_url',
),
migrations.AddField(
model_name='systemsetting',
name='logo',
field=models.ImageField(blank=True, null=True, upload_to='business_logos/', verbose_name='Logo'),
),
migrations.AddField(
model_name='systemsetting',
name='registration_number',
field=models.CharField(blank=True, max_length=50, verbose_name='Registration Number'),
),
migrations.AddField(
model_name='systemsetting',
name='vat_number',
field=models.CharField(blank=True, max_length=50, verbose_name='VAT Number'),
),
migrations.AlterField(
model_name='product',
name='price',
field=models.DecimalField(decimal_places=3, max_digits=12, verbose_name='Price'),
),
migrations.AlterField(
model_name='purchase',
name='total_amount',
field=models.DecimalField(decimal_places=3, max_digits=15),
),
migrations.AlterField(
model_name='sale',
name='discount',
field=models.DecimalField(decimal_places=3, default=0, max_digits=15),
),
migrations.AlterField(
model_name='sale',
name='total_amount',
field=models.DecimalField(decimal_places=3, max_digits=15),
),
migrations.AlterField(
model_name='saleitem',
name='line_total',
field=models.DecimalField(decimal_places=3, max_digits=15),
),
migrations.AlterField(
model_name='saleitem',
name='unit_price',
field=models.DecimalField(decimal_places=3, max_digits=12),
),
migrations.AlterField(
model_name='systemsetting',
name='currency_symbol',
field=models.CharField(default='OMR', max_length=10, verbose_name='Currency Symbol'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 5.2.7 on 2026-02-02 08:00
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0003_remove_systemsetting_logo_url_systemsetting_logo_and_more'),
]
operations = [
migrations.CreateModel(
name='Unit',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name_en', models.CharField(max_length=50, verbose_name='Name (English)')),
('name_ar', models.CharField(max_length=50, verbose_name='Name (Arabic)')),
('short_name', models.CharField(max_length=10, verbose_name='Short Name')),
],
),
migrations.AddField(
model_name='product',
name='unit',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='core.unit'),
),
]

View File

@ -0,0 +1,59 @@
# Generated by Django 5.2.7 on 2026-02-02 08:19
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0004_unit_product_unit'),
]
operations = [
migrations.AddField(
model_name='product',
name='cost_price',
field=models.DecimalField(decimal_places=3, default=0, max_digits=12, verbose_name='Cost Price'),
),
migrations.AddField(
model_name='product',
name='is_active',
field=models.BooleanField(default=True, verbose_name='Active'),
),
migrations.AddField(
model_name='product',
name='opening_stock',
field=models.PositiveIntegerField(default=0, verbose_name='Opening Stock'),
),
migrations.AddField(
model_name='product',
name='supplier',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='core.supplier'),
),
migrations.AddField(
model_name='product',
name='vat',
field=models.DecimalField(decimal_places=2, default=0, max_digits=5, verbose_name='VAT (%)'),
),
migrations.AlterField(
model_name='product',
name='image',
field=models.ImageField(blank=True, null=True, upload_to='product_images/', verbose_name='Product Image'),
),
migrations.AlterField(
model_name='product',
name='price',
field=models.DecimalField(decimal_places=3, max_digits=12, verbose_name='Sale Price'),
),
migrations.AlterField(
model_name='product',
name='sku',
field=models.CharField(max_length=50, unique=True, verbose_name='Barcode/SKU'),
),
migrations.AlterField(
model_name='product',
name='stock_quantity',
field=models.PositiveIntegerField(default=0, verbose_name='In Stock'),
),
]

View File

@ -0,0 +1,82 @@
# Generated by Django 5.2.7 on 2026-02-02 08:35
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_product_cost_price_product_is_active_and_more'),
]
operations = [
migrations.AddField(
model_name='purchase',
name='balance_due',
field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Balance Due'),
),
migrations.AddField(
model_name='purchase',
name='due_date',
field=models.DateField(blank=True, null=True, verbose_name='Due Date'),
),
migrations.AddField(
model_name='purchase',
name='invoice_number',
field=models.CharField(blank=True, max_length=50, verbose_name='Invoice Number'),
),
migrations.AddField(
model_name='purchase',
name='notes',
field=models.TextField(blank=True, verbose_name='Notes'),
),
migrations.AddField(
model_name='purchase',
name='paid_amount',
field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Paid Amount'),
),
migrations.AddField(
model_name='purchase',
name='payment_type',
field=models.CharField(choices=[('cash', 'Cash'), ('credit', 'Credit'), ('partial', 'Partial')], default='cash', max_length=20, verbose_name='Payment Type'),
),
migrations.AddField(
model_name='purchase',
name='status',
field=models.CharField(choices=[('paid', 'Paid'), ('partial', 'Partial'), ('unpaid', 'Unpaid')], default='paid', max_length=20, verbose_name='Status'),
),
migrations.AlterField(
model_name='purchase',
name='supplier',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchases', to='core.supplier'),
),
migrations.AlterField(
model_name='purchase',
name='total_amount',
field=models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Total Amount'),
),
migrations.CreateModel(
name='PurchaseItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.PositiveIntegerField(verbose_name='Quantity')),
('cost_price', models.DecimalField(decimal_places=3, max_digits=12, verbose_name='Cost Price')),
('line_total', models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Line Total')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product')),
('purchase', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.purchase')),
],
),
migrations.CreateModel(
name='PurchasePayment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Amount')),
('payment_date', models.DateField(default=django.utils.timezone.now, verbose_name='Payment Date')),
('payment_method', models.CharField(default='Cash', max_length=50, verbose_name='Payment Method')),
('notes', models.TextField(blank=True, verbose_name='Notes')),
('purchase', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='core.purchase')),
],
),
]

View File

@ -0,0 +1,91 @@
# Generated by Django 5.2.7 on 2026-02-02 09:25
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_purchase_balance_due_purchase_due_date_and_more'),
]
operations = [
migrations.AddField(
model_name='sale',
name='balance_due',
field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Balance Due'),
),
migrations.AddField(
model_name='sale',
name='due_date',
field=models.DateField(blank=True, null=True, verbose_name='Due Date'),
),
migrations.AddField(
model_name='sale',
name='invoice_number',
field=models.CharField(blank=True, max_length=50, verbose_name='Invoice Number'),
),
migrations.AddField(
model_name='sale',
name='notes',
field=models.TextField(blank=True, verbose_name='Notes'),
),
migrations.AddField(
model_name='sale',
name='paid_amount',
field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Paid Amount'),
),
migrations.AddField(
model_name='sale',
name='payment_type',
field=models.CharField(choices=[('cash', 'Cash'), ('credit', 'Credit'), ('partial', 'Partial')], default='cash', max_length=20, verbose_name='Payment Type'),
),
migrations.AddField(
model_name='sale',
name='status',
field=models.CharField(choices=[('paid', 'Paid'), ('partial', 'Partial'), ('unpaid', 'Unpaid')], default='paid', max_length=20, verbose_name='Status'),
),
migrations.AlterField(
model_name='sale',
name='customer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sales', to='core.customer'),
),
migrations.AlterField(
model_name='sale',
name='discount',
field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Discount'),
),
migrations.AlterField(
model_name='sale',
name='total_amount',
field=models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Total Amount'),
),
migrations.AlterField(
model_name='saleitem',
name='line_total',
field=models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Line Total'),
),
migrations.AlterField(
model_name='saleitem',
name='quantity',
field=models.PositiveIntegerField(verbose_name='Quantity'),
),
migrations.AlterField(
model_name='saleitem',
name='unit_price',
field=models.DecimalField(decimal_places=3, max_digits=12, verbose_name='Unit Price'),
),
migrations.CreateModel(
name='SalePayment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Amount')),
('payment_date', models.DateField(default=django.utils.timezone.now, verbose_name='Payment Date')),
('payment_method', models.CharField(default='Cash', max_length=50, verbose_name='Payment Method')),
('notes', models.TextField(blank=True, verbose_name='Notes')),
('sale', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='core.sale')),
],
),
]

View File

@ -1,5 +1,6 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
class Category(models.Model):
name_en = models.CharField(_("Name (English)"), max_length=100)
@ -12,15 +13,29 @@ class Category(models.Model):
def __str__(self):
return f"{self.name_en} / {self.name_ar}"
class Unit(models.Model):
name_en = models.CharField(_("Name (English)"), max_length=50)
name_ar = models.CharField(_("Name (Arabic)"), max_length=50)
short_name = models.CharField(_("Short Name"), max_length=10)
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")
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, related_name="products")
supplier = models.ForeignKey('Supplier', on_delete=models.SET_NULL, null=True, blank=True, 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)
sku = models.CharField(_("Barcode/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)
cost_price = models.DecimalField(_("Cost Price"), max_digits=12, decimal_places=3, default=0)
price = models.DecimalField(_("Sale Price"), max_digits=12, decimal_places=3)
vat = models.DecimalField(_("VAT (%)"), max_digits=5, decimal_places=2, default=0)
opening_stock = models.PositiveIntegerField(_("Opening Stock"), default=0)
stock_quantity = models.PositiveIntegerField(_("In Stock"), default=0)
image = models.ImageField(_("Product Image"), upload_to="product_images/", blank=True, null=True)
is_active = models.BooleanField(_("Active"), default=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
@ -44,34 +59,132 @@ class Supplier(models.Model):
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)
PAYMENT_TYPE_CHOICES = [
('cash', _('Cash')),
('credit', _('Credit')),
('partial', _('Partial')),
]
STATUS_CHOICES = [
('paid', _('Paid')),
('partial', _('Partial')),
('unpaid', _('Unpaid')),
]
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True, related_name="sales")
invoice_number = models.CharField(_("Invoice Number"), max_length=50, blank=True)
total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3)
paid_amount = models.DecimalField(_("Paid Amount"), max_digits=15, decimal_places=3, default=0)
balance_due = models.DecimalField(_("Balance Due"), max_digits=15, decimal_places=3, default=0)
discount = models.DecimalField(_("Discount"), max_digits=15, decimal_places=3, default=0)
payment_type = models.CharField(_("Payment Type"), max_length=20, choices=PAYMENT_TYPE_CHOICES, default='cash')
status = models.CharField(_("Status"), max_length=20, choices=STATUS_CHOICES, default='paid')
due_date = models.DateField(_("Due Date"), null=True, blank=True)
notes = models.TextField(_("Notes"), blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Sale #{self.id} - {self.total_amount}"
return f"Sale #{self.id} - {self.customer.name if self.customer else 'Guest'}"
def update_balance(self):
payments_total = self.payments.aggregate(total=models.Sum('amount'))['total'] or 0
self.paid_amount = payments_total
self.balance_due = self.total_amount - self.paid_amount
if self.balance_due <= 0:
self.status = 'paid'
elif self.paid_amount > 0:
self.status = 'partial'
else:
self.status = 'unpaid'
self.save()
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)
quantity = models.PositiveIntegerField(_("Quantity"))
unit_price = models.DecimalField(_("Unit Price"), max_digits=12, decimal_places=3)
line_total = models.DecimalField(_("Line Total"), max_digits=15, decimal_places=3)
def __str__(self):
return f"{self.product.name_en} x {self.quantity}"
class SalePayment(models.Model):
sale = models.ForeignKey(Sale, on_delete=models.CASCADE, related_name="payments")
amount = models.DecimalField(_("Amount"), max_digits=15, decimal_places=3)
payment_date = models.DateField(_("Payment Date"), default=timezone.now)
payment_method = models.CharField(_("Payment Method"), max_length=50, default="Cash")
notes = models.TextField(_("Notes"), blank=True)
def __str__(self):
return f"Payment of {self.amount} for Sale #{self.sale.id}"
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)
PAYMENT_TYPE_CHOICES = [
('cash', _('Cash')),
('credit', _('Credit')),
('partial', _('Partial')),
]
STATUS_CHOICES = [
('paid', _('Paid')),
('partial', _('Partial')),
('unpaid', _('Unpaid')),
]
supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True, related_name="purchases")
invoice_number = models.CharField(_("Invoice Number"), max_length=50, blank=True)
total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3)
paid_amount = models.DecimalField(_("Paid Amount"), max_digits=15, decimal_places=3, default=0)
balance_due = models.DecimalField(_("Balance Due"), max_digits=15, decimal_places=3, default=0)
payment_type = models.CharField(_("Payment Type"), max_length=20, choices=PAYMENT_TYPE_CHOICES, default='cash')
status = models.CharField(_("Status"), max_length=20, choices=STATUS_CHOICES, default='paid')
due_date = models.DateField(_("Due Date"), null=True, blank=True)
notes = models.TextField(_("Notes"), blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Purchase #{self.id} - {self.supplier.name if self.supplier else 'N/A'}"
def update_balance(self):
payments_total = self.payments.aggregate(total=models.Sum('amount'))['total'] or 0
self.paid_amount = payments_total
self.balance_due = self.total_amount - self.paid_amount
if self.balance_due <= 0:
self.status = 'paid'
elif self.paid_amount > 0:
self.status = 'partial'
else:
self.status = 'unpaid'
self.save()
class PurchaseItem(models.Model):
purchase = models.ForeignKey(Purchase, on_delete=models.CASCADE, related_name="items")
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(_("Quantity"))
cost_price = models.DecimalField(_("Cost Price"), max_digits=12, decimal_places=3)
line_total = models.DecimalField(_("Line Total"), max_digits=15, decimal_places=3)
def __str__(self):
return f"{self.product.name_en} x {self.quantity}"
class PurchasePayment(models.Model):
purchase = models.ForeignKey(Purchase, on_delete=models.CASCADE, related_name="payments")
amount = models.DecimalField(_("Amount"), max_digits=15, decimal_places=3)
payment_date = models.DateField(_("Payment Date"), default=timezone.now)
payment_method = models.CharField(_("Payment Method"), max_length=50, default="Cash")
notes = models.TextField(_("Notes"), blank=True)
def __str__(self):
return f"Payment of {self.amount} for Purchase #{self.purchase.id}"
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="$")
currency_symbol = models.CharField(_("Currency Symbol"), max_length=10, default="OMR")
tax_rate = models.DecimalField(_("Tax Rate (%)"), max_digits=5, decimal_places=2, default=0)
logo_url = models.URLField(_("Logo URL"), blank=True, null=True)
logo = models.ImageField(_("Logo"), upload_to="business_logos/", blank=True, null=True)
vat_number = models.CharField(_("VAT Number"), max_length=50, blank=True)
registration_number = models.CharField(_("Registration Number"), max_length=50, blank=True)
def __str__(self):
return self.business_name

View File

@ -32,8 +32,8 @@
<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">
{% if site_settings.logo %}
<img src="{{ site_settings.logo.url }}" alt="Logo" height="30" class="me-2">
{% else %}
<i class="bi bi-calculator-fill me-2"></i>
{% endif %}
@ -52,6 +52,11 @@
<i class="bi bi-shop"></i> {% trans "POS System" %}
</a>
</li>
<li>
<a href="{% url 'invoices' %}" class="{% if request.resolver_match.url_name == 'invoices' %}active{% endif %}">
<i class="bi bi-file-earmark-text"></i> {% trans "Invoices" %}
</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" %}

View File

@ -1,13 +1,21 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Customers" %} | Meezan Accounting{% endblock %}
{% block title %}{% trans "Customers" %} | {{ site_settings.business_name }}{% 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">
<div>
<h2 class="fw-bold mb-1">{% trans "Customers" %}</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 "Customers" %}</li>
</ol>
</nav>
</div>
<button class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
<i class="bi bi-person-plus me-2"></i>{% trans "Add Customer" %}
</button>
</div>
@ -17,7 +25,7 @@
{% 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>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
@ -32,7 +40,6 @@
<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>
@ -40,20 +47,105 @@
<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="ps-4 fw-bold text-dark">{{ customer.name }}</td>
<td>{{ customer.phone|default:"-" }}</td>
<td>{{ customer.email|default:"-" }}</td>
<td><span class="badge bg-primary bg-opacity-10 text-primary">{{ site_settings.currency_symbol }}{{ customer.total_sales|default:"0.000"|floatformat:3 }}</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>
<div class="btn-group">
<button class="btn btn-sm btn-light rounded-pill px-2 me-1" data-bs-toggle="modal" data-bs-target="#viewCustomerModal{{ customer.id }}" title="{% trans 'View' %}">
<i class="bi bi-eye text-primary"></i>
</button>
<button class="btn btn-sm btn-light rounded-pill px-2 me-1" data-bs-toggle="modal" data-bs-target="#editCustomerModal{{ customer.id }}" title="{% trans 'Edit' %}">
<i class="bi bi-pencil text-success"></i>
</button>
<a href="{% url 'delete_customer' customer.id %}" class="btn btn-sm btn-light rounded-pill px-2" onclick="return confirm('{% trans 'Are you sure you want to delete this customer?' %}')" title="{% trans 'Delete' %}">
<i class="bi bi-trash text-danger"></i>
</a>
</div>
</td>
</tr>
<!-- View Customer Modal -->
<div class="modal fade" id="viewCustomerModal{{ customer.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded-4 border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold">{% trans "Customer Details" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="text-center mb-4">
<div class="bg-primary bg-opacity-10 text-primary rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
<i class="bi bi-person" style="font-size: 2.5rem;"></i>
</div>
<h4 class="fw-bold mb-0">{{ customer.name }}</h4>
</div>
<hr>
<div class="row g-3">
<div class="col-6">
<small class="text-muted d-block">{% trans "Phone" %}</small>
<span class="fw-bold">{{ customer.phone|default:"-" }}</span>
</div>
<div class="col-6">
<small class="text-muted d-block">{% trans "Email" %}</small>
<span class="fw-bold">{{ customer.email|default:"-" }}</span>
</div>
<div class="col-12">
<small class="text-muted d-block">{% trans "Address" %}</small>
<span class="fw-bold">{{ customer.address|default:"-" }}</span>
</div>
<div class="col-12">
<small class="text-muted d-block">{% trans "Total Sales" %}</small>
<span class="badge bg-primary bg-opacity-10 text-primary fs-6">{{ site_settings.currency_symbol }}{{ customer.total_sales|default:"0.000"|floatformat:3 }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Edit Customer Modal -->
<div class="modal fade" id="editCustomerModal{{ customer.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded-4 border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold">{% trans "Edit Customer" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'edit_customer' customer.id %}" 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" value="{{ customer.name }}" 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" value="{{ customer.phone }}">
</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" value="{{ customer.email }}">
</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">{{ customer.address }}</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 "Update Customer" %}</button>
</div>
</form>
</div>
</div>
</div>
{% empty %}
<tr>
<td colspan="6" class="text-center py-5 text-muted">
{% trans "No customers found." %}
<td colspan="5" class="text-center py-5">
<i class="bi bi-person text-muted opacity-25" style="font-size: 3rem;"></i>
<p class="mt-2 text-muted">{% trans "No customers found." %}</p>
</td>
</tr>
{% endfor %}
@ -65,12 +157,12 @@
</div>
<!-- Add Customer Modal -->
<div class="modal fade" id="addCustomerModal" tabindex="-1">
<div class="modal fade" id="addCustomerModal" tabindex="-1" aria-hidden="true">
<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 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 Customer" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'add_customer' %}" method="POST">
{% csrf_token %}

View File

@ -28,7 +28,7 @@
</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>
<h4 class="fw-bold mb-0">{{ site_settings.currency_symbol }}{{ total_sales_amount|floatformat:3 }}</h4>
</div>
</div>
</div>
@ -147,7 +147,7 @@
<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 class="fw-bold">{{ site_settings.currency_symbol }}{{ sale.total_amount|floatformat:3 }}</td>
<td><span class="badge bg-success bg-opacity-10 text-success">{% trans "Completed" %}</span></td>
<td>
<button class="btn btn-sm btn-light">

View File

@ -1,23 +1,31 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "Inventory Management" %} | Meezan Accounting{% endblock %}
{% block title %}{% trans "Stock Management" %} | {{ site_settings.business_name }}{% 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>
<h2 class="fw-bold mb-1">{% trans "Stock 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>
<li class="breadcrumb-item active" aria-current="page">{% trans "Stock" %}</li>
</ol>
</nav>
</div>
<div class="btn-group">
<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" %}
<i class="bi bi-plus-lg me-2"></i>{% trans "Add Item" %}
</button>
<button class="btn btn-outline-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addCategoryModal">
<i class="bi bi-folder-plus me-2"></i>{% trans "Add Category" %}
</button>
<button class="btn btn-outline-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addUnitModal">
<i class="bi bi-rulers me-2"></i>{% trans "Add Unit" %}
</button>
</div>
</div>
{% if messages %}
@ -25,12 +33,34 @@
{% 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>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Tabs for Stock Management -->
<ul class="nav nav-pills mb-4 bg-white p-2 rounded-4 shadow-sm" id="stockTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active px-4 rounded-3" id="items-tab" data-bs-toggle="tab" data-bs-target="#items" type="button" role="tab">
<i class="bi bi-box-seam me-2"></i>{% trans "Items" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link px-4 rounded-3" id="categories-tab" data-bs-toggle="tab" data-bs-target="#categories-list" type="button" role="tab">
<i class="bi bi-folder me-2"></i>{% trans "Categories" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link px-4 rounded-3" id="units-tab" data-bs-toggle="tab" data-bs-target="#units-list" type="button" role="tab">
<i class="bi bi-rulers me-2"></i>{% trans "Units" %}
</button>
</li>
</ul>
<div class="tab-content" id="stockTabsContent">
<!-- Items Tab -->
<div class="tab-pane fade show active" id="items" role="tabpanel">
<!-- Search & Filter Bar -->
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-3">
@ -56,67 +86,426 @@
</div>
</div>
<!-- Product Grid -->
<div class="row g-4">
<!-- Product Table (Grid) -->
<div class="card border-0 shadow-sm rounded-4">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">{% trans "Item" %}</th>
<th>{% trans "SKU/Barcode" %}</th>
<th>{% trans "Category" %}</th>
<th>{% trans "Stock" %}</th>
<th>{% trans "Cost" %}</th>
<th>{% trans "Price" %}</th>
<th>{% trans "Status" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% 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">
<tr class="{% if not product.is_active %}opacity-50{% endif %}">
<td class="ps-4">
<div class="d-flex align-items-center">
{% if product.image %}
<img src="{{ product.image.url }}" class="card-img-top" alt="{{ product.name_en }}" style="height: 200px; object-fit: cover;">
<img src="{{ product.image.url }}" class="rounded-2 me-3" alt="" style="width: 40px; height: 40px; 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 class="bg-light rounded-2 me-3 d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
<i class="bi bi-box-seam text-muted"></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 }}
<div>
<div class="fw-bold text-dark">{% if LANGUAGE_CODE == 'ar' %}{{ product.name_ar }}{% else %}{{ product.name_en }}{% endif %}</div>
<small class="text-muted">{{ product.unit.name_en|default:"" }}</small>
</div>
</div>
</td>
<td><code>{{ product.sku }}</code></td>
<td>{% if LANGUAGE_CODE == 'ar' %}{{ product.category.name_ar }}{% else %}{{ product.category.name_en }}{% endif %}</td>
<td>
<span class="badge {% if product.stock_quantity < 5 %}bg-danger{% else %}bg-success-subtle text-success{% endif %} rounded-pill">
{{ product.stock_quantity }} {{ product.unit.short_name|default:"" }}
</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>
</td>
<td>{{ site_settings.currency_symbol }}{{ product.cost_price|floatformat:3 }}</td>
<td class="fw-bold text-primary">{{ site_settings.currency_symbol }}{{ product.price|floatformat:3 }}</td>
<td>
{% if product.is_active %}
<span class="badge bg-success-subtle text-success border-success-subtle">{% trans "Active" %}</span>
{% else %}
<span class="badge bg-secondary-subtle text-secondary">{% trans "Inactive" %}</span>
{% endif %}
</td>
<td class="text-end pe-4">
<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>
<button class="btn btn-sm btn-light rounded-pill px-2 me-1" data-bs-toggle="modal" data-bs-target="#viewProductModal{{ product.id }}" title="{% trans 'View' %}">
<i class="bi bi-eye text-primary"></i>
</button>
<button class="btn btn-sm btn-light rounded-pill px-2 me-1" data-bs-toggle="modal" data-bs-target="#editProductModal{{ product.id }}" title="{% trans 'Edit' %}">
<i class="bi bi-pencil text-success"></i>
</button>
<a href="{% url 'delete_product' product.id %}" class="btn btn-sm btn-light rounded-pill px-2" onclick="return confirm('{% trans 'Are you sure you want to delete this item?' %}')" title="{% trans 'Delete' %}">
<i class="bi bi-trash text-danger"></i>
</a>
</div>
</td>
</tr>
<!-- View Product Modal -->
<div class="modal fade" id="viewProductModal{{ product.id }}" 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 "Item Details" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-4 text-center mb-3">
{% if product.image %}
<img src="{{ product.image.url }}" class="img-fluid rounded-4 shadow-sm" alt="">
{% else %}
<div class="bg-light rounded-4 d-flex align-items-center justify-content-center" style="height: 200px;">
<i class="bi bi-box-seam text-muted opacity-25" style="font-size: 4rem;"></i>
</div>
{% endif %}
</div>
<div class="col-md-8">
<h3 class="fw-bold mb-1">{% if LANGUAGE_CODE == 'ar' %}{{ product.name_ar }}{% else %}{{ product.name_en }}{% endif %}</h3>
<p class="text-muted mb-3">{{ product.sku }} | {% if LANGUAGE_CODE == 'ar' %}{{ product.category.name_ar }}{% else %}{{ product.category.name_en }}{% endif %}</p>
<hr>
<div class="row g-3">
<div class="col-6">
<small class="text-muted d-block">{% trans "Sale Price" %}</small>
<span class="h5 fw-bold text-primary">{{ site_settings.currency_symbol }}{{ product.price|floatformat:3 }}</span>
</div>
<div class="col-6">
<small class="text-muted d-block">{% trans "Cost Price" %}</small>
<span class="h5 fw-bold">{{ site_settings.currency_symbol }}{{ product.cost_price|floatformat:3 }}</span>
</div>
<div class="col-6">
<small class="text-muted d-block">{% trans "Stock Quantity" %}</small>
<span class="badge {% if product.stock_quantity < 5 %}bg-danger{% else %}bg-success{% endif %}">{{ product.stock_quantity }} {{ product.unit.short_name|default:"" }}</span>
</div>
<div class="col-6">
<small class="text-muted d-block">{% trans "VAT" %}</small>
<span>{{ product.vat }}%</span>
</div>
<div class="col-6">
<small class="text-muted d-block">{% trans "Supplier" %}</small>
<span>{{ product.supplier.name|default:"-" }}</span>
</div>
<div class="col-6">
<small class="text-muted d-block">{% trans "Status" %}</small>
{% if product.is_active %}<span class="text-success">{% trans "Active" %}</span>{% else %}<span class="text-danger">{% trans "Inactive" %}</span>{% endif %}
</div>
<div class="col-6">
<small class="text-muted d-block">{% trans "Entry Date" %}</small>
<span>{{ product.created_at|date:"Y-m-d H:i" }}</span>
</div>
<div class="col-6">
<small class="text-muted d-block">{% trans "Opening Stock" %}</small>
<span>{{ product.opening_stock }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Edit Product Modal -->
<div class="modal fade" id="editProductModal{{ product.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content rounded-4 border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold">{% trans "Edit Item" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'edit_product' product.id %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-body">
<div class="row g-3">
<!-- Identity Section -->
<div class="col-12"><h6 class="fw-bold text-primary mb-0 mt-2">{% trans "Basic Information" %}</h6><hr class="my-2"></div>
<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" value="{{ product.name_en }}" 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" value="{{ product.name_ar }}" dir="rtl" required>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "Barcode / SKU" %}</label>
<input type="text" name="sku" class="form-control rounded-3" value="{{ product.sku }}" required>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "Category" %}</label>
<select name="category" class="form-select rounded-3" required>
{% for category in categories %}
<option value="{{ category.id }}" {% if product.category.id == category.id %}selected{% endif %}>
{% if LANGUAGE_CODE == 'ar' %}{{ category.name_ar }}{% else %}{{ category.name_en }}{% endif %}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "Unit" %}</label>
<select name="unit" class="form-select rounded-3">
<option value="">{% trans "Select Unit" %}</option>
{% for unit in units %}
<option value="{{ unit.id }}" {% if product.unit.id == unit.id %}selected{% endif %}>
{% if LANGUAGE_CODE == 'ar' %}{{ unit.name_ar }}{% else %}{{ unit.name_en }}{% endif %}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Supplier" %}</label>
<select name="supplier" class="form-select rounded-3">
<option value="">{% trans "Select Supplier" %}</option>
{% for supplier in suppliers %}
<option value="{{ supplier.id }}" {% if product.supplier.id == supplier.id %}selected{% endif %}>
{{ supplier.name }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Change Picture" %}</label>
<input type="file" name="image" class="form-control rounded-3" accept="image/*">
{% if product.image %}
<small class="text-muted">{% trans "Current" %}: {{ product.image.name }}</small>
{% endif %}
</div>
<!-- Pricing & Stock Section -->
<div class="col-12"><h6 class="fw-bold text-primary mb-0 mt-3">{% trans "Pricing & Stock" %}</h6><hr class="my-2"></div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "Cost 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.001" name="cost_price" class="form-control rounded-3 border-start-0" value="{{ product.cost_price|floatformat:3 }}" required>
</div>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "Sale 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.001" name="price" class="form-control rounded-3 border-start-0" value="{{ product.price|floatformat:3 }}" required>
</div>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "VAT (%)" %}</label>
<div class="input-group">
<input type="number" step="0.01" name="vat" class="form-control rounded-3" value="{{ product.vat|floatformat:2 }}" required>
<span class="input-group-text bg-light">%</span>
</div>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "Opening Stock" %}</label>
<input type="number" name="opening_stock" class="form-control rounded-3" value="{{ product.opening_stock }}" required>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "In Stock (Current)" %}</label>
<input type="number" name="stock_quantity" class="form-control rounded-3" value="{{ product.stock_quantity }}" required>
</div>
<div class="col-md-4 d-flex align-items-center pt-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="is_active" id="editActiveSwitch{{ product.id }}" {% if product.is_active %}checked{% endif %}>
<label class="form-check-label fw-bold small" for="editActiveSwitch{{ product.id }}">{% trans "Active / Visible" %}</label>
</div>
</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 "Update Product" %}</button>
</div>
</form>
</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>
<tr>
<td colspan="8" class="text-center py-5">
<i class="bi bi-box-seam text-muted opacity-25" style="font-size: 3rem;"></i>
<p class="mt-2 text-muted">{% trans "No products found." %}</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Categories Tab -->
<div class="tab-pane fade" id="categories-list" role="tabpanel">
<div class="card border-0 shadow-sm rounded-4">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">{% trans "Category Name" %}</th>
<th>{% trans "Arabic Name" %}</th>
<th>{% trans "Slug" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr>
<td class="ps-4 fw-bold text-dark">{{ category.name_en }}</td>
<td dir="rtl">{{ category.name_ar }}</td>
<td><code>{{ category.slug }}</code></td>
<td class="text-end pe-4">
<div class="btn-group">
<button class="btn btn-sm btn-light rounded-pill px-2 me-1" data-bs-toggle="modal" data-bs-target="#editCategoryModal{{ category.id }}" title="{% trans 'Edit' %}">
<i class="bi bi-pencil text-success"></i>
</button>
<a href="{% url 'delete_category' category.id %}" class="btn btn-sm btn-light rounded-pill px-2" onclick="return confirm('{% trans 'Are you sure you want to delete this category?' %}')" title="{% trans 'Delete' %}">
<i class="bi bi-trash text-danger"></i>
</a>
</div>
</td>
</tr>
<!-- Edit Category Modal -->
<div class="modal fade" id="editCategoryModal{{ category.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded-4 border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold">{% trans "Edit Category" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'edit_category' category.id %}" method="POST">
{% csrf_token %}
<div class="modal-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
<input type="text" name="name_en" class="form-control rounded-3" value="{{ category.name_en }}" required>
</div>
<div class="col-12">
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
<input type="text" name="name_ar" class="form-control rounded-3" value="{{ category.name_ar }}" dir="rtl" 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 "Update Category" %}</button>
</div>
</form>
</div>
</div>
</div>
{% empty %}
<tr>
<td colspan="4" class="text-center py-4 text-muted">{% trans "No categories found." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Units Tab -->
<div class="tab-pane fade" id="units-list" role="tabpanel">
<div class="card border-0 shadow-sm rounded-4">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">{% trans "Unit Name" %}</th>
<th>{% trans "Arabic Name" %}</th>
<th>{% trans "Short Name" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for unit in units %}
<tr>
<td class="ps-4 fw-bold text-dark">{{ unit.name_en }}</td>
<td dir="rtl">{{ unit.name_ar }}</td>
<td><span class="badge bg-light text-dark border">{{ unit.short_name }}</span></td>
<td class="text-end pe-4">
<div class="btn-group">
<button class="btn btn-sm btn-light rounded-pill px-2 me-1" data-bs-toggle="modal" data-bs-target="#editUnitModal{{ unit.id }}" title="{% trans 'Edit' %}">
<i class="bi bi-pencil text-success"></i>
</button>
<a href="{% url 'delete_unit' unit.id %}" class="btn btn-sm btn-light rounded-pill px-2" onclick="return confirm('{% trans 'Are you sure you want to delete this unit?' %}')" title="{% trans 'Delete' %}">
<i class="bi bi-trash text-danger"></i>
</a>
</div>
</td>
</tr>
<!-- Edit Unit Modal -->
<div class="modal fade" id="editUnitModal{{ unit.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded-4 border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold">{% trans "Edit Unit" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'edit_unit' unit.id %}" method="POST">
{% csrf_token %}
<div class="modal-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
<input type="text" name="name_en" class="form-control rounded-3" value="{{ unit.name_en }}" required>
</div>
<div class="col-12">
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
<input type="text" name="name_ar" class="form-control rounded-3" value="{{ unit.name_ar }}" dir="rtl" required>
</div>
<div class="col-12">
<label class="form-label small fw-bold">{% trans "Short Name" %}</label>
<input type="text" name="short_name" class="form-control rounded-3" value="{{ unit.short_name }}" 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 "Update Unit" %}</button>
</div>
</form>
</div>
</div>
</div>
{% empty %}
<tr>
<td colspan="4" class="text-center py-4 text-muted">{% trans "No units found." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</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-dialog modal-xl">
<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>
<h5 class="modal-title fw-bold">{% trans "Add New Item" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'add_product' %}" method="POST">
<form action="{% url 'add_product' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-body">
<div class="row g-3">
<!-- Identity Section -->
<div class="col-12"><h6 class="fw-bold text-primary mb-0 mt-2">{% trans "Basic Information" %}</h6><hr class="my-2"></div>
<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>
@ -125,7 +514,11 @@
<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">
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "Barcode / SKU" %}</label>
<input type="text" name="sku" class="form-control rounded-3" required>
</div>
<div class="col-md-4">
<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>
@ -134,20 +527,65 @@
{% 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 class="col-md-4">
<label class="form-label small fw-bold">{% trans "Unit" %}</label>
<select name="unit" class="form-select rounded-3">
<option value="">{% trans "Select Unit" %}</option>
{% for unit in units %}
<option value="{{ unit.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ unit.name_ar }}{% else %}{{ unit.name_en }}{% endif %}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Price" %}</label>
<label class="form-label small fw-bold">{% trans "Supplier" %}</label>
<select name="supplier" class="form-select rounded-3">
<option value="">{% trans "Select Supplier" %}</option>
{% for supplier in suppliers %}
<option value="{{ supplier.id }}">{{ supplier.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Product Picture" %}</label>
<input type="file" name="image" class="form-control rounded-3" accept="image/*">
</div>
<!-- Pricing & Stock Section -->
<div class="col-12"><h6 class="fw-bold text-primary mb-0 mt-3">{% trans "Pricing & Stock" %}</h6><hr class="my-2"></div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "Cost 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>
<input type="number" step="0.001" name="cost_price" class="form-control rounded-3 border-start-0" value="0.000" 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 class="col-md-4">
<label class="form-label small fw-bold">{% trans "Sale 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.001" name="price" class="form-control rounded-3 border-start-0" value="0.000" required>
</div>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "VAT (%)" %}</label>
<div class="input-group">
<input type="number" step="0.01" name="vat" class="form-control rounded-3" value="0.00" required>
<span class="input-group-text bg-light">%</span>
</div>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "Opening Stock" %}</label>
<input type="number" name="opening_stock" class="form-control rounded-3" value="0" required>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "In Stock (Current)" %}</label>
<input type="number" name="stock_quantity" class="form-control rounded-3" value="0" required>
</div>
<div class="col-md-4 d-flex align-items-center pt-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="is_active" id="isActiveSwitch" checked>
<label class="form-check-label fw-bold small" for="isActiveSwitch">{% trans "Active / Visible" %}</label>
</div>
</div>
</div>
</div>
@ -159,4 +597,70 @@
</div>
</div>
</div>
<!-- Add Category Modal -->
<div class="modal fade" id="addCategoryModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<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 Category" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'add_category' %}" method="POST">
{% csrf_token %}
<div class="modal-body">
<div class="row g-3">
<div class="col-12">
<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-12">
<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>
</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 Category" %}</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Unit Modal -->
<div class="modal fade" id="addUnitModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<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 Unit" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'add_unit' %}" method="POST">
{% csrf_token %}
<div class="modal-body">
<div class="row g-3">
<div class="col-12">
<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-12">
<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-12">
<label class="form-label small fw-bold">{% trans "Short Name (e.g., kg, pcs)" %}</label>
<input type="text" name="short_name" class="form-control rounded-3" 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 Unit" %}</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,287 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "New Invoice" %} | {{ site_settings.business_name }}{% endblock %}
{% block content %}
<div class="container-fluid px-4" id="saleApp">
<div class="row">
<!-- Main Form -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-header bg-white border-0 pt-4 px-4">
<h5 class="fw-bold mb-0"><i class="bi bi-receipt me-2 text-primary"></i>{% trans "Create Sales Invoice" %}</h5>
</div>
<div class="card-body p-4">
<!-- Customer & Invoice Info -->
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Customer" %}</label>
<select class="form-select rounded-3 shadow-none border-secondary-subtle" v-model="customerId">
<option value="">{% trans "Walking Customer / Guest" %}</option>
{% for customer in customers %}
<option value="{{ customer.id }}">{{ customer.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Invoice #" %}</label>
<input type="text" class="form-control rounded-3 shadow-none border-secondary-subtle" v-model="invoiceNumber" placeholder="{% trans 'e.g. INV-1001' %}">
</div>
</div>
<!-- Item Selection -->
<div class="mb-4">
<label class="form-label small fw-bold">{% trans "Search Products" %}</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0 border-secondary-subtle"><i class="bi bi-search"></i></span>
<input type="text" class="form-control rounded-3 border-start-0 border-secondary-subtle shadow-none" placeholder="{% trans 'Search by Name or SKU...' %}" v-model="searchQuery" @input="filterProducts">
</div>
<div class="position-relative">
<div class="list-group position-absolute w-100 shadow rounded-3 mt-1" style="z-index: 1000;" v-if="filteredProducts.length > 0">
<button v-for="product in filteredProducts" :key="product.id" class="list-group-item list-group-item-action border-0 py-3" @click="addItem(product)">
<div class="d-flex justify-content-between">
<div>
<span class="fw-bold">[[ product.name_en ]]</span> / [[ product.name_ar ]]
<div class="text-muted small">SKU: [[ product.sku ]] | Stock: [[ product.stock ]]</div>
</div>
<div class="text-primary fw-bold">[[ currencySymbol ]][[ product.price ]]</div>
</div>
</button>
</div>
</div>
</div>
<!-- Items Table -->
<div class="table-responsive">
<table class="table align-middle">
<thead class="bg-light-subtle">
<tr class="small text-uppercase text-muted fw-bold">
<th style="width: 40%;">{% trans "Product" %}</th>
<th class="text-center">{% trans "Unit Price" %}</th>
<th class="text-center" style="width: 15%;">{% trans "Quantity" %}</th>
<th class="text-end">{% trans "Total" %}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in cart" :key="index">
<td>
<div class="fw-bold">[[ item.name_en ]]</div>
<div class="text-muted small">[[ item.sku ]]</div>
</td>
<td>
<input type="number" step="0.001" class="form-control form-control-sm text-center border-0 border-bottom rounded-0" v-model="item.price" @input="calculateTotal">
</td>
<td>
<input type="number" class="form-control form-control-sm text-center border-0 border-bottom rounded-0" v-model="item.quantity" @input="calculateTotal">
</td>
<td class="text-end fw-bold">[[ currencySymbol ]][[ (item.price * item.quantity).toFixed(3) ]]</td>
<td class="text-end">
<button class="btn btn-link text-danger p-0" @click="removeItem(index)"><i class="bi bi-x-circle"></i></button>
</td>
</tr>
<tr v-if="cart.length === 0">
<td colspan="5" class="text-center py-5 text-muted">
{% trans "Search and add products to this invoice." %}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Invoice Summary -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm rounded-4 sticky-top" style="top: 20px;">
<div class="card-body p-4">
<h5 class="fw-bold mb-4">{% trans "Invoice Summary" %}</h5>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">{% trans "Subtotal" %}</span>
<span class="fw-bold">[[ currencySymbol ]][[ subtotal.toFixed(3) ]]</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">{% trans "Discount" %}</span>
<div class="input-group input-group-sm" style="width: 120px;">
<input type="number" class="form-control text-end border-0 border-bottom rounded-0" v-model="discount" @input="calculateTotal">
</div>
</div>
<hr class="my-4">
<div class="d-flex justify-content-between mb-4">
<h4 class="fw-bold mb-0">{% trans "Grand Total" %}</h4>
<h4 class="fw-bold text-primary mb-0">[[ currencySymbol ]][[ grandTotal.toFixed(3) ]]</h4>
</div>
<!-- Payment Details -->
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
<select class="form-select rounded-3 shadow-none border-secondary-subtle" v-model="paymentType">
<option value="cash">{% trans "Cash (Full)" %}</option>
<option value="credit">{% trans "Credit (Unpaid)" %}</option>
<option value="partial">{% trans "Partial Payment" %}</option>
</select>
</div>
<div class="mb-3" v-if="paymentType === 'partial'">
<label class="form-label small fw-bold">{% trans "Paid Amount" %}</label>
<input type="number" step="0.001" class="form-control rounded-3" v-model="paidAmount">
</div>
<div class="mb-4" v-if="paymentType !== 'cash'">
<label class="form-label small fw-bold">{% trans "Due Date" %}</label>
<input type="date" class="form-control rounded-3" v-model="dueDate">
</div>
<div class="mb-4">
<label class="form-label small fw-bold">{% trans "Internal Notes" %}</label>
<textarea class="form-control rounded-3" rows="2" v-model="notes"></textarea>
</div>
<div class="d-grid">
<button class="btn btn-primary rounded-3 py-3 fw-bold shadow-sm" :disabled="isProcessing || cart.length === 0" @click="saveSale">
<span v-if="isProcessing" class="spinner-border spinner-border-sm me-2"></span>
<i class="bi bi-printer me-2" v-else></i>
{% trans "Generate Invoice" %}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Vue.js 3 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script>
const { createApp } = Vue;
createApp({
delimiters: ['[[', ']]'],
data() {
return {
products: [
{% for p in products %}
{
id: {{ p.id }},
name_en: "{{ p.name_en }}",
name_ar: "{{ p.name_ar }}",
sku: "{{ p.sku }}",
price: {{ p.price }},
stock: {{ p.stock_quantity }}
},
{% endfor %}
],
searchQuery: '',
filteredProducts: [],
cart: [],
customerId: '',
invoiceNumber: '',
paymentType: 'cash',
paidAmount: 0,
discount: 0,
dueDate: '',
notes: '',
currencySymbol: '{{ site_settings.currency_symbol }}',
isProcessing: false
}
},
computed: {
subtotal() {
return this.cart.reduce((total, item) => total + (item.price * item.quantity), 0);
},
grandTotal() {
return Math.max(0, this.subtotal - this.discount);
}
},
methods: {
filterProducts() {
if (this.searchQuery.length > 1) {
const query = this.searchQuery.toLowerCase();
this.filteredProducts = this.products.filter(p =>
p.name_en.toLowerCase().includes(query) ||
p.sku.toLowerCase().includes(query) ||
p.name_ar.includes(query)
).slice(0, 5);
} else {
this.filteredProducts = [];
}
},
addItem(product) {
const existing = this.cart.find(item => item.id === product.id);
if (existing) {
existing.quantity++;
} else {
this.cart.push({
id: product.id,
name_en: product.name_en,
sku: product.sku,
price: product.price,
quantity: 1
});
}
this.searchQuery = '';
this.filteredProducts = [];
},
removeItem(index) {
this.cart.splice(index, 1);
},
saveSale() {
this.isProcessing = true;
let actualPaidAmount = 0;
if (this.paymentType === 'cash') {
actualPaidAmount = this.grandTotal;
} else if (this.paymentType === 'partial') {
actualPaidAmount = this.paidAmount;
}
const payload = {
customer_id: this.customerId,
invoice_number: this.invoiceNumber,
items: this.cart.map(item => ({
id: item.id,
quantity: item.quantity,
price: item.price,
line_total: item.price * item.quantity
})),
total_amount: this.grandTotal,
discount: this.discount,
paid_amount: actualPaidAmount,
payment_type: this.paymentType,
due_date: this.dueDate,
notes: this.notes
};
fetch("{% url 'create_sale_api' %}", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
})
.then(res => res.json())
.then(data => {
if (data.success) {
window.location.href = "{% url 'invoices' %}";
} else {
alert("Error: " + data.error);
this.isProcessing = false;
}
})
.catch(err => {
console.error(err);
alert("An unexpected error occurred.");
this.isProcessing = false;
});
}
}
}).mount('#saleApp');
</script>
{% endblock %}

View File

@ -0,0 +1,195 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Sales Invoice" %} #{{ sale.invoice_number|default:sale.id }} | {{ site_settings.business_name }}{% endblock %}
{% block content %}
<div class="container py-4">
<!-- Action Bar -->
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<a href="{% url 'invoices' %}" class="btn btn-light rounded-3">
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Invoices" %}
</a>
<button onclick="window.print()" class="btn btn-primary rounded-3 px-4 shadow-sm">
<i class="bi bi-printer me-2"></i>{% trans "Print Invoice" %}
</button>
</div>
<!-- Invoice Content -->
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
<div class="card-body p-0">
<!-- Header Section -->
<div class="p-5 bg-white">
<div class="row mb-5">
<div class="col-sm-6">
{% if settings.logo %}
<img src="{{ settings.logo.url }}" alt="Logo" style="max-height: 80px;" class="mb-4">
{% else %}
<h3 class="fw-bold text-primary mb-4">{{ settings.business_name }}</h3>
{% endif %}
<div class="text-muted small">
<p class="mb-1"><i class="bi bi-geo-alt me-2"></i>{{ settings.address }}</p>
<p class="mb-1"><i class="bi bi-telephone me-2"></i>{{ settings.phone }}</p>
<p class="mb-1"><i class="bi bi-envelope me-2"></i>{{ settings.email }}</p>
{% if settings.vat_number %}
<p class="mb-0"><i class="bi bi-receipt me-2"></i>{% trans "VAT" %}: {{ settings.vat_number }}</p>
{% endif %}
</div>
</div>
<div class="col-sm-6 text-sm-end">
<h1 class="fw-bold text-uppercase text-muted opacity-50 mb-4">{% trans "Tax Invoice" %}</h1>
<div class="mb-4">
<div class="fw-bold text-dark">{% trans "Invoice Number" %}</div>
<div class="h5">{{ sale.invoice_number|default:sale.id }}</div>
</div>
<div class="row g-3">
<div class="col-6">
<div class="small text-muted fw-bold">{% trans "Issue Date" %}</div>
<div>{{ sale.created_at|date:"Y-m-d" }}</div>
</div>
<div class="col-6">
<div class="small text-muted fw-bold">{% trans "Due Date" %}</div>
<div>{{ sale.due_date|date:"Y-m-d"|default:"-" }}</div>
</div>
</div>
</div>
</div>
<div class="row mb-5">
<div class="col-sm-6">
<div class="small text-muted fw-bold mb-3 text-uppercase tracking-wider">{% trans "Customer Information" %}</div>
<div class="h5 fw-bold mb-1">{{ sale.customer.name|default:_("Guest Customer") }}</div>
{% if sale.customer.phone %}
<div class="text-muted small"><i class="bi bi-telephone me-2"></i>{{ sale.customer.phone }}</div>
{% endif %}
{% if sale.customer.address %}
<div class="text-muted small"><i class="bi bi-geo-alt me-2"></i>{{ sale.customer.address }}</div>
{% endif %}
</div>
<div class="col-sm-6 text-sm-end">
<div class="small text-muted fw-bold mb-3 text-uppercase tracking-wider">{% trans "Payment Status" %}</div>
<div>
{% if sale.status == 'paid' %}
<span class="h5 badge bg-success text-white rounded-pill px-4">{% trans "Fully Paid" %}</span>
{% elif sale.status == 'partial' %}
<span class="h5 badge bg-warning text-dark rounded-pill px-4">{% trans "Partially Paid" %}</span>
{% else %}
<span class="h5 badge bg-danger text-white rounded-pill px-4">{% trans "Unpaid" %}</span>
{% endif %}
</div>
</div>
</div>
<!-- Table Section -->
<div class="table-responsive mb-5">
<table class="table table-hover align-middle">
<thead class="bg-light">
<tr>
<th class="py-3 ps-4 border-0">{% trans "Item Description" %}</th>
<th class="py-3 text-center border-0">{% trans "Unit Price" %}</th>
<th class="py-3 text-center border-0">{% trans "Quantity" %}</th>
<th class="py-3 text-end pe-4 border-0">{% trans "Total" %}</th>
</tr>
</thead>
<tbody>
{% for item in sale.items.all %}
<tr>
<td class="py-3 ps-4">
<div class="fw-bold">{{ item.product.name_en }}</div>
<div class="text-muted small">{{ item.product.name_ar }}</div>
</td>
<td class="py-3 text-center">{{ settings.currency_symbol }}{{ item.unit_price|floatformat:3 }}</td>
<td class="py-3 text-center">{{ item.quantity }}</td>
<td class="py-3 text-end pe-4 fw-bold text-primary">{{ settings.currency_symbol }}{{ item.line_total|floatformat:3 }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="2" class="border-0"></td>
<td class="text-center py-3 fw-bold border-top">{% trans "Subtotal" %}</td>
<td class="text-end pe-4 py-3 fw-bold border-top">{{ settings.currency_symbol }}{{ sale.total_amount|add:sale.discount|floatformat:3 }}</td>
</tr>
{% if sale.discount > 0 %}
<tr class="text-muted">
<td colspan="2" class="border-0"></td>
<td class="text-center py-2 fw-bold">{% trans "Discount" %}</td>
<td class="text-end pe-4 py-2 fw-bold">-{{ settings.currency_symbol }}{{ sale.discount|floatformat:3 }}</td>
</tr>
{% endif %}
<tr>
<td colspan="2" class="border-0"></td>
<td class="text-center py-3 fw-bold">{% trans "Grand Total" %}</td>
<td class="text-end pe-4 py-3 h5 fw-bold text-primary">{{ settings.currency_symbol }}{{ sale.total_amount|floatformat:3 }}</td>
</tr>
<tr class="text-success">
<td colspan="2" class="border-0"></td>
<td class="text-center py-2 fw-bold">{% trans "Total Paid" %}</td>
<td class="text-end pe-4 py-2 fw-bold">{{ settings.currency_symbol }}{{ sale.paid_amount|floatformat:3 }}</td>
</tr>
<tr class="text-danger">
<td colspan="2" class="border-0"></td>
<td class="text-center py-2 fw-bold border-top">{% trans "Balance Due" %}</td>
<td class="text-end pe-4 py-2 h5 fw-bold border-top">{{ settings.currency_symbol }}{{ sale.balance_due|floatformat:3 }}</td>
</tr>
</tfoot>
</table>
</div>
<!-- Payment History -->
{% if sale.payments.exists %}
<div class="mb-5">
<h5 class="fw-bold mb-3"><i class="bi bi-credit-card me-2"></i>{% trans "Payment Records" %}</h5>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead class="bg-light small">
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "Method" %}</th>
<th>{% trans "Amount" %}</th>
<th>{% trans "Notes" %}</th>
</tr>
</thead>
<tbody class="small">
{% for payment in sale.payments.all %}
<tr>
<td>{{ payment.payment_date|date:"Y-m-d" }}</td>
<td>{{ payment.payment_method }}</td>
<td class="fw-bold">{{ settings.currency_symbol }}{{ payment.amount|floatformat:3 }}</td>
<td class="text-muted">{{ payment.notes }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<!-- Notes -->
{% if sale.notes %}
<div class="bg-light p-4 rounded-3 mb-5">
<h6 class="fw-bold small text-uppercase mb-2 text-muted">{% trans "Internal Notes" %}</h6>
<p class="mb-0 small">{{ sale.notes }}</p>
</div>
{% endif %}
<div class="text-center text-muted small mt-5 border-top pt-4">
<p class="mb-1">{% trans "Thank you for your business!" %}</p>
<p class="mb-0">{% trans "Software by Meezan" %}</p>
</div>
</div>
</div>
</div>
</div>
<style>
@media print {
@page { size: portrait; margin: 0; }
body { background-color: white !important; }
.container { width: 100% !important; max-width: none !important; margin: 0 !important; padding: 0 !important; }
.card { box-shadow: none !important; border: none !important; }
.d-print-none { display: none !important; }
.p-5 { padding: 2rem !important; }
}
</style>
{% endblock %}

View File

@ -0,0 +1,160 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Sales Invoices" %} | {{ site_settings.business_name }}{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="fw-bold mb-0">{% trans "Sales Invoices" %}</h2>
<p class="text-muted small mb-0">{% trans "Track and manage your customer sales" %}</p>
</div>
<a href="{% url 'invoice_create' %}" class="btn btn-primary rounded-3 px-4 shadow-sm">
<i class="bi bi-plus-circle me-2"></i>{% trans "Create Sales Invoice" %}
</a>
</div>
{% if messages %}
<div class="mb-4">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show rounded-3 shadow-sm border-0" role="alert">
<i class="bi bi-info-circle me-2"></i>{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></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 "Invoice #" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Customer" %}</th>
<th>{% trans "Total" %}</th>
<th>{% trans "Paid" %}</th>
<th>{% trans "Balance" %}</th>
<th>{% trans "Status" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for sale in sales %}
<tr>
<td class="ps-4 fw-bold">
{{ sale.invoice_number|default:sale.id }}
</td>
<td>{{ sale.created_at|date:"Y-m-d" }}</td>
<td>{{ sale.customer.name|default:_("Guest") }}</td>
<td class="fw-bold text-dark">{{ site_settings.currency_symbol }}{{ sale.total_amount|floatformat:3 }}</td>
<td class="text-success">{{ site_settings.currency_symbol }}{{ sale.paid_amount|floatformat:3 }}</td>
<td class="text-danger fw-bold">
{% if sale.balance_due > 0 %}
{{ site_settings.currency_symbol }}{{ sale.balance_due|floatformat:3 }}
{% else %}
<span class="text-success">0.000</span>
{% endif %}
</td>
<td>
{% if sale.status == 'paid' %}
<span class="badge bg-success-subtle text-success rounded-pill px-3">{% trans "Paid" %}</span>
{% elif sale.status == 'partial' %}
<span class="badge bg-warning-subtle text-warning rounded-pill px-3">{% trans "Partial" %}</span>
{% else %}
<span class="badge bg-danger-subtle text-danger rounded-pill px-3">{% trans "Unpaid" %}</span>
{% endif %}
</td>
<td class="text-end pe-4">
<div class="btn-group shadow-sm rounded-3">
<a href="{% url 'invoice_detail' sale.id %}" class="btn btn-sm btn-white border" title="{% trans 'View & Print' %}">
<i class="bi bi-printer"></i>
</a>
{% if sale.balance_due > 0 %}
<button type="button" class="btn btn-sm btn-white border" data-bs-toggle="modal" data-bs-target="#paymentModal{{ sale.id }}" title="{% trans 'Record Payment' %}">
<i class="bi bi-cash-stack"></i>
</button>
{% endif %}
<button type="button" class="btn btn-sm btn-white border text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal{{ sale.id }}">
<i class="bi bi-trash"></i>
</button>
</div>
<!-- Payment Modal -->
<div class="modal fade text-start" id="paymentModal{{ sale.id }}" 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 Customer Payment" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="{% url 'add_sale_payment' sale.id %}" method="POST">
{% csrf_token %}
<div class="modal-body">
<div class="alert alert-info border-0 rounded-3 small">
{% trans "Remaining Balance" %}: <strong>{{ site_settings.currency_symbol }}{{ sale.balance_due|floatformat:3 }}</strong>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Amount" %}</label>
<input type="number" step="0.001" name="amount" max="{{ sale.balance_due }}" value="{{ sale.balance_due }}" class="form-control rounded-3" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
<select name="payment_method" class="form-select rounded-3">
<option value="Cash">{% trans "Cash" %}</option>
<option value="Bank Transfer">{% trans "Bank Transfer" %}</option>
<option value="Cheque">{% trans "Cheque" %}</option>
</select>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Notes" %}</label>
<textarea name="notes" class="form-control rounded-3" rows="2"></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 Payment" %}</button>
</div>
</form>
</div>
</div>
</div>
<!-- Delete Modal -->
<div class="modal fade text-start" id="deleteModal{{ sale.id }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow rounded-4">
<div class="modal-body p-4 text-center">
<div class="text-danger mb-3">
<i class="bi bi-exclamation-octagon" style="font-size: 3rem;"></i>
</div>
<h4 class="fw-bold">{% trans "Delete Sales Invoice?" %}</h4>
<p class="text-muted">{% trans "This will restore the product quantities and delete all payment history. This action cannot be undone." %}</p>
<div class="d-grid gap-2">
<a href="{% url 'delete_sale' sale.id %}" class="btn btn-danger rounded-3 py-2">{% trans "Yes, Delete" %}</a>
<button type="button" class="btn btn-light rounded-3 py-2" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
</div>
</div>
</div>
</div>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" class="text-center py-5">
<img src="https://illustrations.popsy.co/gray/success.svg" alt="Empty" style="width: 200px;" class="mb-3">
<p class="text-muted">{% trans "No sales invoices found." %}</p>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -48,21 +48,32 @@
visibility: hidden;
}
#invoice-print, #invoice-print * {
visibility: visible;
visibility: visible !important;
}
#invoice-print {
position: absolute;
display: block !important;
position: fixed;
left: 0;
top: 0;
width: 80mm;
padding: 5mm;
margin: 0;
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
font-size: 11px;
color: black;
background: white;
z-index: 9999;
}
.no-print {
display: none !important;
}
#sidebar, .top-navbar, .btn, .modal {
display: none !important;
}
main {
padding: 0 !important;
margin: 0 !important;
}
}
#invoice-print {
@ -75,19 +86,19 @@
}
.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-details { margin-bottom: 10px; font-size: 10px; }
.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-table th { border-bottom: 1px solid #000; text-align: left; padding: 2px 0; font-size: 10px; }
.invoice-table td { padding: 4px 0; font-size: 10px; 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; }
.bilingual { display: flex; justify-content: space-between; font-size: 9px; 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="container-fluid px-4 no-print">
<div class="row g-4">
<!-- Products Section -->
<div class="col-lg-8">
@ -113,7 +124,7 @@
<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;">
<img src="{{ product.image.url }}" 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>
@ -123,7 +134,7 @@
<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>
<p class="text-primary fw-bold mb-0">{{ site_settings.currency_symbol }}{{ product.price|floatformat:3 }}</p>
<small class="text-muted">{% trans "Stock" %}: {{ product.stock_quantity }}</small>
</div>
</div>
@ -158,11 +169,11 @@
<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>
<span id="subtotalAmount">{{ site_settings.currency_symbol }}0.000</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>
<span class="fw-bold fs-5 text-primary" id="totalAmount">{{ site_settings.currency_symbol }}0.000</span>
</div>
<button id="payNowBtn" class="btn btn-primary w-100 py-3 fw-bold rounded-3" onclick="checkout()" disabled>
{% trans "PAY NOW" %}
@ -176,10 +187,24 @@
<!-- Invoice Template (Hidden) -->
<div id="invoice-print">
<div class="invoice-header">
<div id="inv-logo-container" class="mb-2">
<img id="inv-logo" src="" alt="Logo" style="max-height: 50px; display: none;">
</div>
<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 id="inv-business-email" style="font-size: 10px;"></div>
<div class="mt-1" style="border-top: 1px solid #eee; padding-top: 2px;">
<div class="bilingual">
<span>VAT No / الرقم الضريبي:</span>
<span id="inv-vat-no"></span>
</div>
<div class="bilingual">
<span>CR No / السجل التجاري:</span>
<span id="inv-cr-no"></span>
</div>
</div>
</div>
<div class="invoice-details">
<div class="d-flex justify-content-between">
@ -215,7 +240,7 @@
</div>
<!-- Receipt Modal -->
<div class="modal fade" id="receiptModal" tabindex="-1">
<div class="modal fade no-print" id="receiptModal" tabindex="-1">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-body text-center p-4">
@ -240,6 +265,11 @@
let lastSaleData = null;
const lang = '{{ LANGUAGE_CODE }}';
const currency = '{{ site_settings.currency_symbol }}';
const decimalPlaces = currency === 'OMR' ? 3 : 2;
function formatAmount(amount) {
return parseFloat(amount).toFixed(decimalPlaces);
}
function addToCart(id, nameEn, nameAr, price) {
const existing = cart.find(item => item.id === id);
@ -281,8 +311,8 @@
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`;
document.getElementById('subtotalAmount').innerText = `${currency} ${formatAmount(0)}`;
document.getElementById('totalAmount').innerText = `${currency} ${formatAmount(0)}`;
payBtn.disabled = true;
return;
}
@ -298,7 +328,7 @@
<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 class="text-muted small">${currency} ${formatAmount(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>
@ -310,8 +340,8 @@
});
listContainer.innerHTML = html;
document.getElementById('subtotalAmount').innerText = `${currency}${total.toFixed(2)}`;
document.getElementById('totalAmount').innerText = `${currency}${total.toFixed(2)}`;
document.getElementById('subtotalAmount').innerText = `${currency} ${formatAmount(total)}`;
document.getElementById('totalAmount').innerText = `${currency} ${formatAmount(total)}`;
}
function checkout() {
@ -366,9 +396,23 @@
}
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;
console.log('Preparing invoice with data:', data);
const logo = document.getElementById('inv-logo');
if (data.business.logo_url) {
logo.src = data.business.logo_url;
logo.style.display = 'inline-block';
} else {
logo.style.display = 'none';
}
document.getElementById('inv-business-name').innerText = data.business.name || 'Business Name';
document.getElementById('inv-business-address').innerText = data.business.address || '';
document.getElementById('inv-business-phone').innerText = data.business.phone ? 'Tel: ' + data.business.phone : '';
document.getElementById('inv-business-email').innerText = data.business.email ? 'Email: ' + data.business.email : '';
document.getElementById('inv-vat-no').innerText = data.business.vat_number || '---';
document.getElementById('inv-cr-no').innerText = data.business.registration_number || '---';
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;
@ -383,15 +427,16 @@
<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>
<td style="text-align: right;">${data.business.currency} ${formatAmount(item.total)}</td>
</tr>
`;
});
document.getElementById('inv-items').innerHTML = itemsHtml;
document.getElementById('inv-total').innerText = data.business.currency + data.sale.total.toFixed(2);
document.getElementById('inv-total').innerText = data.business.currency + ' ' + formatAmount(data.sale.total);
}
function printInvoice() {
console.log('Printing invoice...');
window.print();
}

View File

@ -0,0 +1,275 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "New Purchase" %} | {{ site_settings.business_name }}{% endblock %}
{% block content %}
<div class="container-fluid px-4" id="purchaseApp">
<div class="row">
<!-- Main Form -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-header bg-white border-0 pt-4 px-4">
<h5 class="fw-bold mb-0"><i class="bi bi-cart-plus me-2 text-primary"></i>{% trans "Create Purchase Invoice" %}</h5>
</div>
<div class="card-body p-4">
<!-- Supplier & Invoice Info -->
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Supplier" %}</label>
<select class="form-select rounded-3 shadow-none border-secondary-subtle" v-model="supplierId">
<option value="">{% trans "Select Supplier" %}</option>
{% for supplier in suppliers %}
<option value="{{ supplier.id }}">{{ supplier.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold">{% trans "Reference / Invoice #" %}</label>
<input type="text" class="form-control rounded-3 shadow-none border-secondary-subtle" v-model="invoiceNumber" placeholder="{% trans 'e.g. INV-2024-001' %}">
</div>
</div>
<!-- Item Selection -->
<div class="mb-4">
<label class="form-label small fw-bold">{% trans "Add Items to Invoice" %}</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0 border-secondary-subtle"><i class="bi bi-search"></i></span>
<input type="text" class="form-control rounded-3 border-start-0 border-secondary-subtle shadow-none" placeholder="{% trans 'Search by Name or SKU...' %}" v-model="searchQuery" @input="filterProducts">
</div>
<div class="position-relative">
<div class="list-group position-absolute w-100 shadow rounded-3 mt-1" style="z-index: 1000;" v-if="filteredProducts.length > 0">
<button v-for="product in filteredProducts" :key="product.id" class="list-group-item list-group-item-action border-0 py-3" @click="addItem(product)">
<div class="d-flex justify-content-between">
<div>
<span class="fw-bold">[[ product.name_en ]]</span> / [[ product.name_ar ]]
<div class="text-muted small">SKU: [[ product.sku ]] | Stock: [[ product.stock ]]</div>
</div>
<div class="text-primary fw-bold">[[ currencySymbol ]][[ product.cost_price ]]</div>
</div>
</button>
</div>
</div>
</div>
<!-- Items Table -->
<div class="table-responsive">
<table class="table align-middle">
<thead class="bg-light-subtle">
<tr class="small text-uppercase text-muted fw-bold">
<th style="width: 40%;">{% trans "Product" %}</th>
<th class="text-center">{% trans "Cost Price" %}</th>
<th class="text-center" style="width: 15%;">{% trans "Quantity" %}</th>
<th class="text-end">{% trans "Total" %}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in cart" :key="index">
<td>
<div class="fw-bold">[[ item.name_en ]]</div>
<div class="text-muted small">[[ item.sku ]]</div>
</td>
<td>
<input type="number" step="0.001" class="form-control form-control-sm text-center border-0 border-bottom rounded-0" v-model="item.price" @input="calculateTotal">
</td>
<td>
<input type="number" class="form-control form-control-sm text-center border-0 border-bottom rounded-0" v-model="item.quantity" @input="calculateTotal">
</td>
<td class="text-end fw-bold">[[ currencySymbol ]][[ (item.price * item.quantity).toFixed(3) ]]</td>
<td class="text-end">
<button class="btn btn-link text-danger p-0" @click="removeItem(index)"><i class="bi bi-x-circle"></i></button>
</td>
</tr>
<tr v-if="cart.length === 0">
<td colspan="5" class="text-center py-5 text-muted">
{% trans "No items added yet." %}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Order Summary -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm rounded-4 sticky-top" style="top: 20px;">
<div class="card-body p-4">
<h5 class="fw-bold mb-4">{% trans "Purchase Summary" %}</h5>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">{% trans "Subtotal" %}</span>
<span class="fw-bold">[[ currencySymbol ]][[ subtotal.toFixed(3) ]]</span>
</div>
<hr class="my-4">
<div class="d-flex justify-content-between mb-4">
<h4 class="fw-bold mb-0">{% trans "Grand Total" %}</h4>
<h4 class="fw-bold text-primary mb-0">[[ currencySymbol ]][[ subtotal.toFixed(3) ]]</h4>
</div>
<!-- Payment Details -->
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Payment Type" %}</label>
<select class="form-select rounded-3 shadow-none border-secondary-subtle" v-model="paymentType">
<option value="cash">{% trans "Full Cash" %}</option>
<option value="credit">{% trans "Full Credit" %}</option>
<option value="partial">{% trans "Partial Payment" %}</option>
</select>
</div>
<div class="mb-3" v-if="paymentType === 'partial'">
<label class="form-label small fw-bold">{% trans "Amount Paid" %}</label>
<input type="number" step="0.001" class="form-control rounded-3" v-model="paidAmount">
</div>
<div class="mb-4" v-if="paymentType !== 'cash'">
<label class="form-label small fw-bold">{% trans "Due Date" %}</label>
<input type="date" class="form-control rounded-3" v-model="dueDate">
</div>
<div class="mb-4">
<label class="form-label small fw-bold">{% trans "Notes" %}</label>
<textarea class="form-control rounded-3" rows="2" v-model="notes"></textarea>
</div>
<div class="d-grid">
<button class="btn btn-primary rounded-3 py-3 fw-bold shadow-sm" :disabled="isProcessing || cart.length === 0 || !supplierId" @click="savePurchase">
<span v-if="isProcessing" class="spinner-border spinner-border-sm me-2"></span>
<i class="bi bi-check2-circle me-2" v-else></i>
{% trans "Finalize Purchase" %}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Vue.js 3 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script>
const { createApp } = Vue;
createApp({
delimiters: ['[[', ']]'],
data() {
return {
products: [
{% for p in products %}
{
id: {{ p.id }},
name_en: "{{ p.name_en }}",
name_ar: "{{ p.name_ar }}",
sku: "{{ p.sku }}",
cost_price: {{ p.cost_price }},
stock: {{ p.stock_quantity }}
},
{% endfor %}
],
searchQuery: '',
filteredProducts: [],
cart: [],
supplierId: '',
invoiceNumber: '',
paymentType: 'cash',
paidAmount: 0,
dueDate: '',
notes: '',
currencySymbol: '{{ site_settings.currency_symbol }}',
isProcessing: false
}
},
computed: {
subtotal() {
return this.cart.reduce((total, item) => total + (item.price * item.quantity), 0);
}
},
methods: {
filterProducts() {
if (this.searchQuery.length > 1) {
const query = this.searchQuery.toLowerCase();
this.filteredProducts = this.products.filter(p =>
p.name_en.toLowerCase().includes(query) ||
p.sku.toLowerCase().includes(query) ||
p.name_ar.includes(query)
).slice(0, 5);
} else {
this.filteredProducts = [];
}
},
addItem(product) {
const existing = this.cart.find(item => item.id === product.id);
if (existing) {
existing.quantity++;
} else {
this.cart.push({
id: product.id,
name_en: product.name_en,
sku: product.sku,
price: product.cost_price,
quantity: 1
});
}
this.searchQuery = '';
this.filteredProducts = [];
},
removeItem(index) {
this.cart.splice(index, 1);
},
savePurchase() {
this.isProcessing = true;
let actualPaidAmount = 0;
if (this.paymentType === 'cash') {
actualPaidAmount = this.subtotal;
} else if (this.paymentType === 'partial') {
actualPaidAmount = this.paidAmount;
}
const payload = {
supplier_id: this.supplierId,
invoice_number: this.invoiceNumber,
items: this.cart.map(item => ({
id: item.id,
quantity: item.quantity,
price: item.price,
line_total: item.price * item.quantity
})),
total_amount: this.subtotal,
paid_amount: actualPaidAmount,
payment_type: this.paymentType,
due_date: this.dueDate,
notes: this.notes
};
fetch("{% url 'create_purchase_api' %}", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
})
.then(res => res.json())
.then(data => {
if (data.success) {
window.location.href = "{% url 'purchases' %}";
} else {
alert("Error: " + data.error);
this.isProcessing = false;
}
})
.catch(err => {
console.error(err);
alert("An unexpected error occurred.");
this.isProcessing = false;
});
}
}
}).mount('#purchaseApp');
</script>
{% endblock %}

View File

@ -0,0 +1,182 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Purchase Detail" %} #{{ purchase.id }} | {{ site_settings.business_name }}{% endblock %}
{% block content %}
<div class="container py-4">
<!-- Action Bar -->
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<a href="{% url 'purchases' %}" class="btn btn-light rounded-3">
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to List" %}
</a>
<button onclick="window.print()" class="btn btn-primary rounded-3 px-4 shadow-sm">
<i class="bi bi-printer me-2"></i>{% trans "Print Invoice" %}
</button>
</div>
<!-- Invoice Content -->
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
<div class="card-body p-0">
<!-- Header Section -->
<div class="p-5 bg-white">
<div class="row mb-5">
<div class="col-sm-6">
{% if settings.logo %}
<img src="{{ settings.logo.url }}" alt="Logo" style="max-height: 80px;" class="mb-4">
{% else %}
<h3 class="fw-bold text-primary mb-4">{{ settings.business_name }}</h3>
{% endif %}
<div class="text-muted small">
<p class="mb-1"><i class="bi bi-geo-alt me-2"></i>{{ settings.address }}</p>
<p class="mb-1"><i class="bi bi-telephone me-2"></i>{{ settings.phone }}</p>
<p class="mb-1"><i class="bi bi-envelope me-2"></i>{{ settings.email }}</p>
{% if settings.vat_number %}
<p class="mb-0"><i class="bi bi-receipt me-2"></i>{% trans "VAT" %}: {{ settings.vat_number }}</p>
{% endif %}
</div>
</div>
<div class="col-sm-6 text-sm-end">
<h1 class="fw-bold text-uppercase text-muted opacity-50 mb-4">{% trans "Purchase Invoice" %}</h1>
<div class="mb-4">
<div class="fw-bold text-dark">{% trans "Invoice Number" %}</div>
<div class="h5">{{ purchase.invoice_number|default:purchase.id }}</div>
</div>
<div class="row g-3">
<div class="col-6">
<div class="small text-muted fw-bold">{% trans "Issue Date" %}</div>
<div>{{ purchase.created_at|date:"Y-m-d" }}</div>
</div>
<div class="col-6">
<div class="small text-muted fw-bold">{% trans "Due Date" %}</div>
<div>{{ purchase.due_date|date:"Y-m-d"|default:"-" }}</div>
</div>
</div>
</div>
</div>
<div class="row mb-5">
<div class="col-sm-6">
<div class="small text-muted fw-bold mb-3 text-uppercase tracking-wider">{% trans "Supplier Information" %}</div>
<div class="h5 fw-bold mb-1">{{ purchase.supplier.name }}</div>
{% if purchase.supplier.phone %}
<div class="text-muted small"><i class="bi bi-telephone me-2"></i>{{ purchase.supplier.phone }}</div>
{% endif %}
{% if purchase.supplier.contact_person %}
<div class="text-muted small"><i class="bi bi-person me-2"></i>{{ purchase.supplier.contact_person }}</div>
{% endif %}
</div>
<div class="col-sm-6 text-sm-end">
<div class="small text-muted fw-bold mb-3 text-uppercase tracking-wider">{% trans "Payment Status" %}</div>
<div>
{% if purchase.status == 'paid' %}
<span class="h5 badge bg-success text-white rounded-pill px-4">{% trans "Fully Paid" %}</span>
{% elif purchase.status == 'partial' %}
<span class="h5 badge bg-warning text-dark rounded-pill px-4">{% trans "Partially Paid" %}</span>
{% else %}
<span class="h5 badge bg-danger text-white rounded-pill px-4">{% trans "Unpaid" %}</span>
{% endif %}
</div>
</div>
</div>
<!-- Table Section -->
<div class="table-responsive mb-5">
<table class="table table-hover align-middle">
<thead class="bg-light">
<tr>
<th class="py-3 ps-4 border-0">{% trans "Item Description" %}</th>
<th class="py-3 text-center border-0">{% trans "Cost Price" %}</th>
<th class="py-3 text-center border-0">{% trans "Quantity" %}</th>
<th class="py-3 text-end pe-4 border-0">{% trans "Total" %}</th>
</tr>
</thead>
<tbody>
{% for item in purchase.items.all %}
<tr>
<td class="py-3 ps-4">
<div class="fw-bold">{{ item.product.name_en }}</div>
<div class="text-muted small">{{ item.product.name_ar }}</div>
</td>
<td class="py-3 text-center">{{ settings.currency_symbol }}{{ item.cost_price|floatformat:3 }}</td>
<td class="py-3 text-center">{{ item.quantity }}</td>
<td class="py-3 text-end pe-4 fw-bold text-primary">{{ settings.currency_symbol }}{{ item.line_total|floatformat:3 }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="2" class="border-0"></td>
<td class="text-center py-3 fw-bold border-top">{% trans "Grand Total" %}</td>
<td class="text-end pe-4 py-3 h5 fw-bold text-primary border-top">{{ settings.currency_symbol }}{{ purchase.total_amount|floatformat:3 }}</td>
</tr>
<tr class="text-success">
<td colspan="2" class="border-0"></td>
<td class="text-center py-2 fw-bold">{% trans "Total Paid" %}</td>
<td class="text-end pe-4 py-2 fw-bold">{{ settings.currency_symbol }}{{ purchase.paid_amount|floatformat:3 }}</td>
</tr>
<tr class="text-danger">
<td colspan="2" class="border-0"></td>
<td class="text-center py-2 fw-bold border-top">{% trans "Balance Due" %}</td>
<td class="text-end pe-4 py-2 h5 fw-bold border-top">{{ settings.currency_symbol }}{{ purchase.balance_due|floatformat:3 }}</td>
</tr>
</tfoot>
</table>
</div>
<!-- Payment History -->
{% if purchase.payments.exists %}
<div class="mb-5">
<h5 class="fw-bold mb-3"><i class="bi bi-credit-card me-2"></i>{% trans "Payment History" %}</h5>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead class="bg-light small">
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "Method" %}</th>
<th>{% trans "Amount" %}</th>
<th>{% trans "Notes" %}</th>
</tr>
</thead>
<tbody class="small">
{% for payment in purchase.payments.all %}
<tr>
<td>{{ payment.payment_date|date:"Y-m-d" }}</td>
<td>{{ payment.payment_method }}</td>
<td class="fw-bold">{{ settings.currency_symbol }}{{ payment.amount|floatformat:3 }}</td>
<td class="text-muted">{{ payment.notes }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<!-- Notes -->
{% if purchase.notes %}
<div class="bg-light p-4 rounded-3 mb-5">
<h6 class="fw-bold small text-uppercase mb-2 text-muted">{% trans "Notes" %}</h6>
<p class="mb-0 small">{{ purchase.notes }}</p>
</div>
{% endif %}
<div class="text-center text-muted small mt-5 border-top pt-4">
<p class="mb-0">{% trans "Thank you for your business!" %}</p>
</div>
</div>
</div>
</div>
</div>
<style>
@media print {
@page { size: portrait; margin: 0; }
body { background-color: white !important; }
.container { width: 100% !important; max-width: none !important; margin: 0 !important; padding: 0 !important; }
.card { box-shadow: none !important; border: none !important; }
.d-print-none { display: none !important; }
.p-5 { padding: 2rem !important; }
}
</style>
{% endblock %}

View File

@ -1,23 +1,26 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Purchases" %} | Meezan Accounting{% endblock %}
{% block title %}{% trans "Stock Purchases" %} | {{ site_settings.business_name }}{% endblock %}
{% block content %}
<div class="container">
<div class="container-fluid px-4">
<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>
<h2 class="fw-bold mb-0">{% trans "Purchase Invoices" %}</h2>
<p class="text-muted small mb-0">{% trans "Manage and track your stock procurement" %}</p>
</div>
<a href="{% url 'purchase_create' %}" class="btn btn-primary rounded-3 px-4 shadow-sm">
<i class="bi bi-plus-circle me-2"></i>{% trans "Create Purchase" %}
</a>
</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 class="alert alert-{{ message.tags }} alert-dismissible fade show rounded-3 shadow-sm border-0" role="alert">
<i class="bi bi-info-circle me-2"></i>{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
@ -29,26 +32,122 @@
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">{% trans "Date" %}</th>
<th class="ps-4">{% trans "Invoice #" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Supplier" %}</th>
<th>{% trans "Total Amount" %}</th>
<th>{% trans "Total" %}</th>
<th>{% trans "Paid" %}</th>
<th>{% trans "Balance" %}</th>
<th>{% trans "Status" %}</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 class="ps-4 fw-bold">
{{ purchase.invoice_number|default:purchase.id }}
</td>
<td>{{ purchase.created_at|date:"Y-m-d" }}</td>
<td>{{ purchase.supplier.name|default:"-" }}</td>
<td class="fw-bold text-primary">{{ site_settings.currency_symbol }}{{ purchase.total_amount }}</td>
<td class="fw-bold text-dark">{{ site_settings.currency_symbol }}{{ purchase.total_amount|floatformat:3 }}</td>
<td class="text-success">{{ site_settings.currency_symbol }}{{ purchase.paid_amount|floatformat:3 }}</td>
<td class="text-danger fw-bold">
{% if purchase.balance_due > 0 %}
{{ site_settings.currency_symbol }}{{ purchase.balance_due|floatformat:3 }}
{% else %}
<span class="text-success">0.000</span>
{% endif %}
</td>
<td>
{% if purchase.status == 'paid' %}
<span class="badge bg-success-subtle text-success rounded-pill px-3">{% trans "Paid" %}</span>
{% elif purchase.status == 'partial' %}
<span class="badge bg-warning-subtle text-warning rounded-pill px-3">{% trans "Partial" %}</span>
{% else %}
<span class="badge bg-danger-subtle text-danger rounded-pill px-3">{% trans "Unpaid" %}</span>
{% endif %}
</td>
<td class="text-end pe-4">
<button class="btn btn-sm btn-light rounded-circle"><i class="bi bi-eye"></i></button>
<div class="btn-group shadow-sm rounded-3">
<a href="{% url 'purchase_detail' purchase.id %}" class="btn btn-sm btn-white border" title="{% trans 'View & Print' %}">
<i class="bi bi-printer"></i>
</a>
{% if purchase.balance_due > 0 %}
<button type="button" class="btn btn-sm btn-white border" data-bs-toggle="modal" data-bs-target="#paymentModal{{ purchase.id }}" title="{% trans 'Add Payment' %}">
<i class="bi bi-cash-stack"></i>
</button>
{% endif %}
<button type="button" class="btn btn-sm btn-white border text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal{{ purchase.id }}">
<i class="bi bi-trash"></i>
</button>
</div>
<!-- Payment Modal -->
<div class="modal fade text-start" id="paymentModal{{ purchase.id }}" 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 Payment" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form action="{% url 'add_purchase_payment' purchase.id %}" method="POST">
{% csrf_token %}
<div class="modal-body">
<div class="alert alert-info border-0 rounded-3 small">
{% trans "Balance Due" %}: <strong>{{ site_settings.currency_symbol }}{{ purchase.balance_due|floatformat:3 }}</strong>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Amount" %}</label>
<input type="number" step="0.001" name="amount" max="{{ purchase.balance_due }}" value="{{ purchase.balance_due }}" class="form-control rounded-3" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
<select name="payment_method" class="form-select rounded-3">
<option value="Cash">{% trans "Cash" %}</option>
<option value="Bank Transfer">{% trans "Bank Transfer" %}</option>
<option value="Cheque">{% trans "Cheque" %}</option>
</select>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">{% trans "Notes" %}</label>
<textarea name="notes" class="form-control rounded-3" rows="2"></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 Payment" %}</button>
</div>
</form>
</div>
</div>
</div>
<!-- Delete Modal -->
<div class="modal fade text-start" id="deleteModal{{ purchase.id }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow rounded-4">
<div class="modal-body p-4 text-center">
<div class="text-danger mb-3">
<i class="bi bi-exclamation-octagon" style="font-size: 3rem;"></i>
</div>
<h4 class="fw-bold">{% trans "Delete Purchase?" %}</h4>
<p class="text-muted">{% trans "This will revert the stock changes and delete all payment history. This action cannot be undone." %}</p>
<div class="d-grid gap-2">
<a href="{% url 'delete_purchase' purchase.id %}" class="btn btn-danger rounded-3 py-2">{% trans "Yes, Delete" %}</a>
<button type="button" class="btn btn-light rounded-3 py-2" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
</div>
</div>
</div>
</div>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center py-5 text-muted">
{% trans "No purchases found." %}
<td colspan="8" class="text-center py-5">
<img src="https://illustrations.popsy.co/gray/delivery.svg" alt="Empty" style="width: 200px;" class="mb-3">
<p class="text-muted">{% trans "No purchases recorded yet." %}</p>
</td>
</tr>
{% endfor %}
@ -58,41 +157,4 @@
</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

@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Smart Reports" %} | Meezan Accounting{% endblock %}
{% block title %}{% trans "Smart Reports" %} | {{ site_settings.business_name }}{% endblock %}
{% block content %}
<div class="container-fluid">
@ -29,7 +29,7 @@
{% 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>
<td class="text-end fw-bold">{{ site_settings.currency_symbol }}{{ sale.total|floatformat:3 }}</td>
</tr>
{% empty %}
<tr>
@ -56,7 +56,7 @@
<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>
<span class="d-block fw-bold text-primary">{{ site_settings.currency_symbol }}{{ item.revenue|floatformat:3 }}</span>
</div>
</li>
{% empty %}

View File

@ -1,5 +1,6 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% block content %}
<div class="container-fluid">
@ -30,9 +31,21 @@
<h5 class="card-title mb-0 fw-bold">{% trans "Business Profile" %}</h5>
</div>
<div class="card-body">
<form method="post">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="row g-3">
<div class="col-md-12 text-center mb-3">
<label class="form-label d-block fw-semibold">{% trans "Business Logo" %}</label>
{% if settings.logo %}
<img src="{{ settings.logo.url }}" alt="Logo" class="img-thumbnail mb-2" style="max-height: 100px;">
{% else %}
<div class="bg-light border rounded d-inline-flex align-items-center justify-content-center mb-2" style="width: 100px; height: 100px;">
<i class="bi bi-image text-muted fs-1"></i>
</div>
{% endif %}
<input type="file" name="logo" class="form-control form-control-sm mx-auto" style="max-width: 300px;" accept="image/*">
</div>
<div class="col-md-12">
<label class="form-label fw-semibold">{% trans "Business Name" %}</label>
<input type="text" name="business_name" class="form-control" value="{{ settings.business_name }}" required>
@ -45,6 +58,14 @@
<label class="form-label fw-semibold">{% trans "Phone Number" %}</label>
<input type="text" name="phone" class="form-control" value="{{ settings.phone }}">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "VAT Number" %}</label>
<input type="text" name="vat_number" class="form-control" value="{{ settings.vat_number }}">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Registration Number" %}</label>
<input type="text" name="registration_number" class="form-control" value="{{ settings.registration_number }}">
</div>
<div class="col-12">
<label class="form-label fw-semibold">{% trans "Address" %}</label>
<textarea name="address" class="form-control" rows="3">{{ settings.address }}</textarea>
@ -56,7 +77,7 @@
<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 class="form-text">{% trans "e.g., OMR, $, £, SAR" %}</div>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">{% trans "Default Tax Rate (%)" %}</label>

View File

@ -6,8 +6,16 @@
{% 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">
<div>
<h2 class="fw-bold mb-1">{% trans "Suppliers" %}</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 "Suppliers" %}</li>
</ol>
</nav>
</div>
<button class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addSupplierModal">
<i class="bi bi-truck me-2"></i>{% trans "Add Supplier" %}
</button>
</div>
@ -17,7 +25,7 @@
{% 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>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
@ -29,7 +37,7 @@
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">{% trans "Name" %}</th>
<th class="ps-4">{% trans "Supplier" %}</th>
<th>{% trans "Contact Person" %}</th>
<th>{% trans "Phone" %}</th>
<th class="text-end pe-4">{% trans "Actions" %}</th>
@ -38,18 +46,92 @@
<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="ps-4 fw-bold text-dark">{{ supplier.name }}</td>
<td>{{ supplier.contact_person|default:"-" }}</td>
<td>{{ supplier.phone|default:"-" }}</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>
<div class="btn-group">
<button class="btn btn-sm btn-light rounded-pill px-2 me-1" data-bs-toggle="modal" data-bs-target="#viewSupplierModal{{ supplier.id }}" title="{% trans 'View' %}">
<i class="bi bi-eye text-primary"></i>
</button>
<button class="btn btn-sm btn-light rounded-pill px-2 me-1" data-bs-toggle="modal" data-bs-target="#editSupplierModal{{ supplier.id }}" title="{% trans 'Edit' %}">
<i class="bi bi-pencil text-success"></i>
</button>
<a href="{% url 'delete_supplier' supplier.id %}" class="btn btn-sm btn-light rounded-pill px-2" onclick="return confirm('{% trans 'Are you sure you want to delete this supplier?' %}')" title="{% trans 'Delete' %}">
<i class="bi bi-trash text-danger"></i>
</a>
</div>
</td>
</tr>
<!-- View Supplier Modal -->
<div class="modal fade" id="viewSupplierModal{{ supplier.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded-4 border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold">{% trans "Supplier Details" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="text-center mb-4">
<div class="bg-primary bg-opacity-10 text-primary rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
<i class="bi bi-truck" style="font-size: 2.5rem;"></i>
</div>
<h4 class="fw-bold mb-0">{{ supplier.name }}</h4>
</div>
<hr>
<div class="row g-3">
<div class="col-6">
<small class="text-muted d-block">{% trans "Contact Person" %}</small>
<span class="fw-bold">{{ supplier.contact_person|default:"-" }}</span>
</div>
<div class="col-6">
<small class="text-muted d-block">{% trans "Phone" %}</small>
<span class="fw-bold">{{ supplier.phone|default:"-" }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Edit Supplier Modal -->
<div class="modal fade" id="editSupplierModal{{ supplier.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded-4 border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold">{% trans "Edit Supplier" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'edit_supplier' supplier.id %}" 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" value="{{ supplier.name }}" 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" value="{{ supplier.contact_person }}">
</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" value="{{ supplier.phone }}">
</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 "Update Supplier" %}</button>
</div>
</form>
</div>
</div>
</div>
{% empty %}
<tr>
<td colspan="4" class="text-center py-5 text-muted">
{% trans "No suppliers found." %}
<td colspan="4" class="text-center py-5">
<i class="bi bi-truck text-muted opacity-25" style="font-size: 3rem;"></i>
<p class="mt-2 text-muted">{% trans "No suppliers found." %}</p>
</td>
</tr>
{% endfor %}
@ -61,12 +143,12 @@
</div>
<!-- Add Supplier Modal -->
<div class="modal fade" id="addSupplierModal" tabindex="-1">
<div class="modal fade" id="addSupplierModal" tabindex="-1" aria-hidden="true">
<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 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 Supplier" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="{% url 'add_supplier' %}" method="POST">
{% csrf_token %}

View File

@ -11,10 +11,45 @@ urlpatterns = [
path('reports/', views.reports, name='reports'),
path('settings/', views.settings_view, name='settings'),
# Invoices (Sales)
path('invoices/', views.invoice_list, name='invoices'), # Changed to 'invoices' for consistency with sidebar
path('invoices/create/', views.invoice_create, name='invoice_create'),
path('invoices/<int:pk>/', views.invoice_detail, name='invoice_detail'),
path('invoices/payment/<int:pk>/', views.add_sale_payment, name='add_sale_payment'),
path('invoices/delete/<int:pk>/', views.delete_sale, name='delete_sale'),
# Purchases (Invoices)
path('purchases/create/', views.purchase_create, name='purchase_create'),
path('purchases/<int:pk>/', views.purchase_detail, name='purchase_detail'),
path('purchases/payment/<int:pk>/', views.add_purchase_payment, name='add_purchase_payment'),
path('purchases/delete/<int:pk>/', views.delete_purchase, name='delete_purchase'),
# API / Actions
path('api/create-sale/', views.create_sale_api, name='create_sale_api'),
path('api/create-purchase/', views.create_purchase_api, name='create_purchase_api'),
# Customers
path('customers/add/', views.add_customer, name='add_customer'),
path('customers/edit/<int:pk>/', views.edit_customer, name='edit_customer'),
path('customers/delete/<int:pk>/', views.delete_customer, name='delete_customer'),
# Suppliers
path('suppliers/add/', views.add_supplier, name='add_supplier'),
path('purchases/add/', views.add_purchase, name='add_purchase'),
path('suppliers/edit/<int:pk>/', views.edit_supplier, name='edit_supplier'),
path('suppliers/delete/<int:pk>/', views.delete_supplier, name='delete_supplier'),
# Inventory
path('inventory/add/', views.add_product, name='add_product'),
path('inventory/edit/<int:pk>/', views.edit_product, name='edit_product'),
path('inventory/delete/<int:pk>/', views.delete_product, name='delete_product'),
# Categories
path('inventory/category/add/', views.add_category, name='add_category'),
path('inventory/category/edit/<int:pk>/', views.edit_category, name='edit_category'),
path('inventory/category/delete/<int:pk>/', views.delete_category, name='delete_category'),
# Units
path('inventory/unit/add/', views.add_unit, name='add_unit'),
path('inventory/unit/edit/<int:pk>/', views.edit_unit, name='edit_unit'),
path('inventory/unit/delete/<int:pk>/', views.delete_unit, name='delete_unit'),
]

View File

@ -3,11 +3,16 @@ 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
from .models import (
Product, Sale, Category, Unit, Customer, Supplier,
Purchase, PurchaseItem, PurchasePayment,
SaleItem, SalePayment, SystemSetting
)
import json
from datetime import timedelta
from django.utils import timezone
from django.contrib import messages
from django.utils.text import slugify
def index(request):
"""
@ -56,20 +61,27 @@ def index(request):
return render(request, 'core/index.html', context)
def inventory(request):
products = Product.objects.all().select_related('category')
products = Product.objects.all().select_related('category', 'unit', 'supplier')
categories = Category.objects.all()
context = {'products': products, 'categories': categories}
units = Unit.objects.all()
suppliers = Supplier.objects.all()
context = {
'products': products,
'categories': categories,
'units': units,
'suppliers': suppliers
}
return render(request, 'core/inventory.html', context)
def pos(request):
products = Product.objects.all().filter(stock_quantity__gt=0)
products = Product.objects.all().filter(stock_quantity__gt=0, is_active=True)
customers = Customer.objects.all()
categories = Category.objects.all()
context = {'products': products, 'customers': customers, 'categories': categories}
return render(request, 'core/pos.html', context)
def customers(request):
customers_list = Customer.objects.all().annotate(total_sales=Sum('sale__total_amount'))
customers_list = Customer.objects.all().annotate(total_sales=Sum('sales__total_amount'))
context = {'customers': customers_list}
return render(request, 'core/customers.html', context)
@ -78,12 +90,266 @@ def suppliers(request):
context = {'suppliers': suppliers_list}
return render(request, 'core/suppliers.html', context)
# --- Purchase Views ---
def purchases(request):
purchases_list = Purchase.objects.all().select_related('supplier')
purchases_list = Purchase.objects.all().select_related('supplier').order_by('-created_at')
suppliers_list = Supplier.objects.all()
context = {'purchases': purchases_list, 'suppliers': suppliers_list}
return render(request, 'core/purchases.html', context)
def purchase_create(request):
products = Product.objects.filter(is_active=True)
suppliers = Supplier.objects.all()
return render(request, 'core/purchase_create.html', {'products': products, 'suppliers': suppliers})
def purchase_detail(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/purchase_detail.html', {'purchase': purchase, 'settings': settings})
@csrf_exempt
def create_purchase_api(request):
if request.method == 'POST':
try:
data = json.loads(request.body)
supplier_id = data.get('supplier_id')
invoice_number = data.get('invoice_number', '')
items = data.get('items', [])
total_amount = data.get('total_amount', 0)
paid_amount = data.get('paid_amount', 0)
payment_type = data.get('payment_type', 'cash')
due_date = data.get('due_date')
notes = data.get('notes', '')
supplier = None
if supplier_id:
supplier = Supplier.objects.get(id=supplier_id)
purchase = Purchase.objects.create(
supplier=supplier,
invoice_number=invoice_number,
total_amount=total_amount,
paid_amount=paid_amount,
balance_due=float(total_amount) - float(paid_amount),
payment_type=payment_type,
due_date=due_date if due_date else None,
notes=notes
)
# Set status based on payments
if float(paid_amount) >= float(total_amount):
purchase.status = 'paid'
elif float(paid_amount) > 0:
purchase.status = 'partial'
else:
purchase.status = 'unpaid'
purchase.save()
# Record the initial payment if any
if float(paid_amount) > 0:
PurchasePayment.objects.create(
purchase=purchase,
amount=paid_amount,
payment_method=payment_type.capitalize(),
notes=_("Initial payment")
)
for item in items:
product = Product.objects.get(id=item['id'])
PurchaseItem.objects.create(
purchase=purchase,
product=product,
quantity=item['quantity'],
cost_price=item['price'],
line_total=item['line_total']
)
# Update Stock
product.stock_quantity += int(item['quantity'])
product.cost_price = item['price']
product.save()
return JsonResponse({'success': True, 'purchase_id': purchase.id})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)}, status=400)
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
def add_purchase_payment(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
if request.method == 'POST':
amount = request.POST.get('amount')
payment_date = request.POST.get('payment_date', timezone.now().date())
payment_method = request.POST.get('payment_method', 'Cash')
notes = request.POST.get('notes', '')
PurchasePayment.objects.create(
purchase=purchase,
amount=amount,
payment_date=payment_date,
payment_method=payment_method,
notes=notes
)
purchase.update_balance()
messages.success(request, _("Payment added successfully!"))
return redirect('purchases')
def delete_purchase(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
for item in purchase.items.all():
item.product.stock_quantity -= item.quantity
item.product.save()
purchase.delete()
messages.success(request, _("Purchase deleted successfully!"))
return redirect('purchases')
# --- Sale Views ---
def invoice_list(request):
sales = Sale.objects.all().order_by('-created_at')
customers = Customer.objects.all()
return render(request, 'core/invoices.html', {'sales': sales, 'customers': customers})
def invoice_create(request):
products = Product.objects.filter(is_active=True)
customers = Customer.objects.all()
return render(request, 'core/invoice_create.html', {'products': products, 'customers': customers})
def invoice_detail(request, pk):
sale = get_object_or_404(Sale, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/invoice_detail.html', {'sale': sale, 'settings': settings})
@csrf_exempt
def create_sale_api(request):
if request.method == 'POST':
try:
data = json.loads(request.body)
customer_id = data.get('customer_id')
invoice_number = data.get('invoice_number', '')
items = data.get('items', [])
total_amount = data.get('total_amount', 0)
paid_amount = data.get('paid_amount', 0)
discount = data.get('discount', 0)
payment_type = data.get('payment_type', 'cash')
due_date = data.get('due_date')
notes = data.get('notes', '')
customer = None
if customer_id:
customer = Customer.objects.get(id=customer_id)
sale = Sale.objects.create(
customer=customer,
invoice_number=invoice_number,
total_amount=total_amount,
paid_amount=paid_amount,
balance_due=float(total_amount) - float(paid_amount),
discount=discount,
payment_type=payment_type,
due_date=due_date if due_date else None,
notes=notes
)
# Set status based on payments
if float(paid_amount) >= float(total_amount):
sale.status = 'paid'
elif float(paid_amount) > 0:
sale.status = 'partial'
else:
sale.status = 'unpaid'
sale.save()
# Record initial payment if any
if float(paid_amount) > 0:
SalePayment.objects.create(
sale=sale,
amount=paid_amount,
payment_method=payment_type.capitalize(),
notes=_("Initial payment")
)
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 -= int(item['quantity'])
product.save()
settings = SystemSetting.objects.first()
if not settings:
settings = SystemSetting.objects.create()
return JsonResponse({
'success': True,
'sale_id': sale.id,
'business': {
'name': settings.business_name,
'address': settings.address,
'phone': settings.phone,
'email': settings.email,
'currency': settings.currency_symbol,
'vat_number': settings.vat_number,
'registration_number': settings.registration_number,
'logo_url': settings.logo.url if settings.logo else None
},
'sale': {
'id': sale.id,
'invoice_number': sale.invoice_number,
'created_at': sale.created_at.strftime("%Y-%m-%d %H:%M"),
'total': float(sale.total_amount),
'paid': float(sale.paid_amount),
'balance': float(sale.balance_due),
'items': [
{
'name_en': si.product.name_en,
'name_ar': si.product.name_ar,
'qty': si.quantity,
'price': float(si.unit_price),
'total': float(si.line_total)
} for si 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_sale_payment(request, pk):
sale = get_object_or_404(Sale, pk=pk)
if request.method == 'POST':
amount = request.POST.get('amount')
payment_date = request.POST.get('payment_date', timezone.now().date())
payment_method = request.POST.get('payment_method', 'Cash')
notes = request.POST.get('notes', '')
SalePayment.objects.create(
sale=sale,
amount=amount,
payment_date=payment_date,
payment_method=payment_method,
notes=notes
)
sale.update_balance()
messages.success(request, _("Payment added successfully!"))
return redirect('invoices')
def delete_sale(request, pk):
sale = get_object_or_404(Sale, pk=pk)
for item in sale.items.all():
item.product.stock_quantity += item.quantity
item.product.save()
sale.delete()
messages.success(request, _("Sale deleted successfully!"))
return redirect('invoices')
# --- Other Management Views ---
def reports(request):
"""
Smart Reports View
@ -120,73 +386,18 @@ def settings_view(request):
settings.email = request.POST.get('email')
settings.currency_symbol = request.POST.get('currency_symbol')
settings.tax_rate = request.POST.get('tax_rate')
settings.vat_number = request.POST.get('vat_number')
settings.registration_number = request.POST.get('registration_number')
if 'logo' in request.FILES:
settings.logo = request.FILES['logo']
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')
@ -197,6 +408,23 @@ def add_customer(request):
messages.success(request, "Customer added successfully!")
return redirect('customers')
def edit_customer(request, pk):
customer = get_object_or_404(Customer, pk=pk)
if request.method == 'POST':
customer.name = request.POST.get('name')
customer.phone = request.POST.get('phone')
customer.email = request.POST.get('email')
customer.address = request.POST.get('address')
customer.save()
messages.success(request, "Customer updated successfully!")
return redirect('customers')
def delete_customer(request, pk):
customer = get_object_or_404(Customer, pk=pk)
customer.delete()
messages.success(request, "Customer deleted successfully!")
return redirect('customers')
def add_supplier(request):
if request.method == 'POST':
name = request.POST.get('name')
@ -206,31 +434,144 @@ def add_supplier(request):
messages.success(request, "Supplier added successfully!")
return redirect('suppliers')
def add_purchase(request):
def edit_supplier(request, pk):
supplier = get_object_or_404(Supplier, pk=pk)
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')
supplier.name = request.POST.get('name')
supplier.contact_person = request.POST.get('contact_person')
supplier.phone = request.POST.get('phone')
supplier.save()
messages.success(request, "Supplier updated successfully!")
return redirect('suppliers')
def delete_supplier(request, pk):
supplier = get_object_or_404(Supplier, pk=pk)
supplier.delete()
messages.success(request, "Supplier deleted successfully!")
return redirect('suppliers')
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')
unit_id = request.POST.get('unit')
supplier_id = request.POST.get('supplier')
sku = request.POST.get('sku')
price = request.POST.get('price')
stock = request.POST.get('stock')
cost_price = request.POST.get('cost_price', 0)
price = request.POST.get('price', 0)
vat = request.POST.get('vat', 0)
opening_stock = request.POST.get('opening_stock', 0)
stock_quantity = request.POST.get('stock_quantity', 0)
is_active = request.POST.get('is_active') == 'on'
category = get_object_or_404(Category, id=category_id)
Product.objects.create(
unit = get_object_or_404(Unit, id=unit_id) if unit_id else None
supplier = get_object_or_404(Supplier, id=supplier_id) if supplier_id else None
product = Product.objects.create(
name_en=name_en,
name_ar=name_ar,
category=category,
unit=unit,
supplier=supplier,
sku=sku,
cost_price=cost_price,
price=price,
stock_quantity=stock
vat=vat,
opening_stock=opening_stock,
stock_quantity=stock_quantity,
is_active=is_active
)
if 'image' in request.FILES:
product.image = request.FILES['image']
product.save()
messages.success(request, "Product added successfully!")
return redirect('inventory')
def edit_product(request, pk):
product = get_object_or_404(Product, pk=pk)
if request.method == 'POST':
product.name_en = request.POST.get('name_en')
product.name_ar = request.POST.get('name_ar')
product.sku = request.POST.get('sku')
product.category = get_object_or_404(Category, id=request.POST.get('category'))
unit_id = request.POST.get('unit')
product.unit = get_object_or_404(Unit, id=unit_id) if unit_id else None
supplier_id = request.POST.get('supplier')
product.supplier = get_object_or_404(Supplier, id=supplier_id) if supplier_id else None
product.cost_price = request.POST.get('cost_price', 0)
product.price = request.POST.get('price', 0)
product.vat = request.POST.get('vat', 0)
product.opening_stock = request.POST.get('opening_stock', 0)
product.stock_quantity = request.POST.get('stock_quantity', 0)
product.is_active = request.POST.get('is_active') == 'on'
if 'image' in request.FILES:
product.image = request.FILES['image']
product.save()
messages.success(request, "Product updated successfully!")
return redirect('inventory')
return redirect('inventory')
def delete_product(request, pk):
product = get_object_or_404(Product, pk=pk)
product.delete()
messages.success(request, "Product deleted successfully!")
return redirect('inventory')
def add_category(request):
if request.method == 'POST':
name_en = request.POST.get('name_en')
name_ar = request.POST.get('name_ar')
slug = slugify(name_en)
Category.objects.create(name_en=name_en, name_ar=name_ar, slug=slug)
messages.success(request, "Category added successfully!")
return redirect('inventory')
def edit_category(request, pk):
category = get_object_or_404(Category, pk=pk)
if request.method == 'POST':
category.name_en = request.POST.get('name_en')
category.name_ar = request.POST.get('name_ar')
category.slug = slugify(category.name_en)
category.save()
messages.success(request, "Category updated successfully!")
return redirect('inventory')
def delete_category(request, pk):
category = get_object_or_404(Category, pk=pk)
category.delete()
messages.success(request, "Category deleted successfully!")
return redirect('inventory')
def add_unit(request):
if request.method == 'POST':
name_en = request.POST.get('name_en')
name_ar = request.POST.get('name_ar')
short_name = request.POST.get('short_name')
Unit.objects.create(name_en=name_en, name_ar=name_ar, short_name=short_name)
messages.success(request, "Unit added successfully!")
return redirect('inventory')
def edit_unit(request, pk):
unit = get_object_or_404(Unit, pk=pk)
if request.method == 'POST':
unit.name_en = request.POST.get('name_en')
unit.name_ar = request.POST.get('name_ar')
unit.short_name = request.POST.get('short_name')
unit.save()
messages.success(request, "Unit updated successfully!")
return redirect('inventory')
def delete_unit(request, pk):
unit = get_object_or_404(Unit, pk=pk)
unit.delete()
messages.success(request, "Unit deleted successfully!")
return redirect('inventory')

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB