From f80934e391d8c60bf0d0022284617996be570c44 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 2 Feb 2026 10:11:13 +0000 Subject: [PATCH] improving the system --- core/__pycache__/models.cpython-311.pyc | Bin 16831 -> 23883 bytes core/__pycache__/urls.cpython-311.pyc | Bin 4025 -> 6150 bytes core/__pycache__/views.cpython-311.pyc | Bin 34935 -> 49572 bytes ..._quotation_sale_quotation_quotationitem.py | 45 ++ ..._purchasereturnitem_salereturn_and_more.py | 60 +++ ...le_quotation_quotationitem.cpython-311.pyc | Bin 0 -> 3482 bytes ...rnitem_salereturn_and_more.cpython-311.pyc | Bin 0 -> 4230 bytes core/models.py | 77 +++- core/templates/base.html | 42 +- core/templates/core/barcode_labels.html | 429 ++++++++++++++++++ core/templates/core/invoice_detail.html | 149 ++++-- core/templates/core/purchase_detail.html | 131 ++++-- .../core/purchase_return_create.html | 255 +++++++++++ .../core/purchase_return_detail.html | 183 ++++++++ core/templates/core/purchase_returns.html | 105 +++++ core/templates/core/quotation_create.html | 272 +++++++++++ core/templates/core/quotation_detail.html | 253 +++++++++++ core/templates/core/quotations.html | 135 ++++++ core/templates/core/sale_return_create.html | 255 +++++++++++ core/templates/core/sale_return_detail.html | 183 ++++++++ core/templates/core/sales_returns.html | 105 +++++ core/urls.py | 27 +- core/views.py | 266 ++++++++++- 23 files changed, 2889 insertions(+), 83 deletions(-) create mode 100644 core/migrations/0008_quotation_sale_quotation_quotationitem.py create mode 100644 core/migrations/0009_purchasereturn_purchasereturnitem_salereturn_and_more.py create mode 100644 core/migrations/__pycache__/0008_quotation_sale_quotation_quotationitem.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0009_purchasereturn_purchasereturnitem_salereturn_and_more.cpython-311.pyc create mode 100644 core/templates/core/barcode_labels.html create mode 100644 core/templates/core/purchase_return_create.html create mode 100644 core/templates/core/purchase_return_detail.html create mode 100644 core/templates/core/purchase_returns.html create mode 100644 core/templates/core/quotation_create.html create mode 100644 core/templates/core/quotation_detail.html create mode 100644 core/templates/core/quotations.html create mode 100644 core/templates/core/sale_return_create.html create mode 100644 core/templates/core/sale_return_detail.html create mode 100644 core/templates/core/sales_returns.html diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 040e7fa2f52f297d33163dc5d09f2a87c282e0c8..a5eaed3d2e5cf1fac8c9d309b7114ff4ed6280cc 100644 GIT binary patch delta 5362 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 diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 21ff9b595d47a04935052867802a3e6a3cbbfe7f..87e44739974384d82911de13b51c01fa1389eeb1 100644 GIT binary patch literal 6150 zcmaLaOHdR^8UWyK_)*>D{>P@q*ro;o0)A_@Y64vw@%e5cm)OAwMU)gJ1u7G7$K4zz7(@cwi^E9t;MI0;4cquv55R7z}I$ z3Ib-(0JHEV$Wx7?pHBR>FcA0${&zor3JP@+Rgo++iZ!YPm8?BrlxkENDr}kiG0HWn z0#&h6l^Rurszj-3jXH%&j2D+ijYid?DpS|0)2Mn>Kv+CrOs zgHjhY>Jq9(rTR3gA61i50~!@V)vVNIjkl!tTs$HoO zjT%MOq12d0jic&RYC@xKpgOJ8O^x~n)fuHGHEIggS*31i)HJGdO3i50EUNQL-PWi( zsJfK8t5I{PE+}Z($k8Wlw~sFa~m zCaP;neXmhlsD_k!p;0kZ*Ohviuhxf+?Ug`p{1=XTgIP#M(l34qx<873h?%cb*+8}^ zwriMgcz8FmV_K!L-5<=|bb`DMWlPkt1<6Dzlr6+>t2CNPr4u_Q!GaQXY?Y=m$z(j{ z7OP{cG?^jM7m<|7wK}$niJ450G!~xpj;xZDnNG)cx3H{O9$LH!iCC1MT`Ui+YIzij z5;KxEL)l8NBpO@;D0Q|)y|8LK{$jCn2YSEZqBojcUND$rS#e9%%MVx}{M zk3fa^9rk`(^+&@peKlgJT_i`M@hHs=_Yv$3N5@f|*Q**|ioC{HjlO>I)BD}}L)iwe zXZ}X{FNYfk0Qy*fVkBvA9OMV5ReXH{PBB(ZAbkuMX~ zvP>nTS(fCKOh|(ad02MO*KnEQTw?N0@_Kt#UMyEnUV<8qbwMVn)t4lB8I`COFHtfe z8_Sbb?K_btGL)_Ir`WowULl+#M}oC{%gLfLYjBUxrO$)i-rG-Cc6v4a23 z!Im7%8v&U_N$^%u57bY<%UES zS!ce&jy2TFhj$U{+*i)XYqCm=Jj>VS%r2%|G^Lo26#r?p=PoM3$C^+g#!AS}V=}L5 z`DWpIp?4%(X1t8-ZY2gXB%UICS55c^nee?d;hSK>cdvwRP6^+8627%0eCJ5`#t
  • KL+c3g+S%hz*2;U?=^4iheU(k8x% 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 delta 6887 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 OMqaUoH%GMcG5iR5Q(fBt literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 4569aea..2c98bf5 100644 --- a/core/models.py +++ b/core/models.py @@ -71,6 +71,7 @@ class Sale(models.Model): ] customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True, related_name="sales") + quotation = models.ForeignKey('Quotation', on_delete=models.SET_NULL, null=True, blank=True, related_name="converted_sale") 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) @@ -117,6 +118,38 @@ class SalePayment(models.Model): def __str__(self): return f"Payment of {self.amount} for Sale #{self.sale.id}" +class Quotation(models.Model): + STATUS_CHOICES = [ + ('draft', _('Draft')), + ('sent', _('Sent')), + ('accepted', _('Accepted')), + ('rejected', _('Rejected')), + ('converted', _('Converted to Invoice')), + ] + + customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True, related_name="quotations") + quotation_number = models.CharField(_("Quotation Number"), max_length=50, blank=True) + total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3) + discount = models.DecimalField(_("Discount"), max_digits=15, decimal_places=3, default=0) + status = models.CharField(_("Status"), max_length=20, choices=STATUS_CHOICES, default='draft') + valid_until = models.DateField(_("Valid Until"), null=True, blank=True) + terms_and_conditions = models.TextField(_("Terms and Conditions"), blank=True) + notes = models.TextField(_("Notes"), blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Quotation #{self.id} - {self.customer.name if self.customer else 'Guest'}" + +class QuotationItem(models.Model): + quotation = models.ForeignKey(Quotation, on_delete=models.CASCADE, related_name="items") + product = models.ForeignKey(Product, on_delete=models.CASCADE) + 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 Purchase(models.Model): PAYMENT_TYPE_CHOICES = [ ('cash', _('Cash')), @@ -175,6 +208,48 @@ class PurchasePayment(models.Model): def __str__(self): return f"Payment of {self.amount} for Purchase #{self.purchase.id}" +class SaleReturn(models.Model): + sale = models.ForeignKey(Sale, on_delete=models.SET_NULL, null=True, blank=True, related_name="returns") + customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True, related_name="sale_returns") + return_number = models.CharField(_("Return Number"), max_length=50, blank=True) + total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3) + notes = models.TextField(_("Notes"), blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Sale Return #{self.id} - {self.customer.name if self.customer else 'Guest'}" + +class SaleReturnItem(models.Model): + sale_return = models.ForeignKey(SaleReturn, on_delete=models.CASCADE, related_name="items") + product = models.ForeignKey(Product, on_delete=models.CASCADE) + 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 PurchaseReturn(models.Model): + purchase = models.ForeignKey(Purchase, on_delete=models.SET_NULL, null=True, blank=True, related_name="returns") + supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase_returns") + return_number = models.CharField(_("Return Number"), max_length=50, blank=True) + total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3) + notes = models.TextField(_("Notes"), blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Purchase Return #{self.id} - {self.supplier.name if self.supplier else 'N/A'}" + +class PurchaseReturnItem(models.Model): + purchase_return = models.ForeignKey(PurchaseReturn, 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 SystemSetting(models.Model): business_name = models.CharField(_("Business Name"), max_length=200, default="Meezan Accounting") address = models.TextField(_("Address"), blank=True) @@ -187,4 +262,4 @@ class SystemSetting(models.Model): registration_number = models.CharField(_("Registration Number"), max_length=50, blank=True) def __str__(self): - return self.business_name + return self.business_name \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 0602d98..9b79024 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -47,19 +47,31 @@ {% trans "Dashboard" %}
  • + +
  • {% trans "Sales" %}
  • {% trans "POS System" %}
  • - - {% trans "Invoices" %} + + {% trans "New Sales" %}
  • - - {% trans "Reports" %} + + {% trans "Sales Invoices" %} + +
  • +
  • + + {% trans "Quotation" %} + +
  • +
  • + + {% trans "Sales Return" %}
  • @@ -70,10 +82,25 @@
  • - + {% trans "Purchases" %}
  • +
  • + +
  • + + {% trans "Barcode Printing" %} + +
  • + {% trans "Purchase Return" %} + + +
  • + + {% trans "Reports" %} + +
  • {% trans "Contacts" %}
  • @@ -93,6 +120,11 @@ {% trans "Settings" %}
  • +
  • + + {% trans "Django Admin" %} + +
  • diff --git a/core/templates/core/barcode_labels.html b/core/templates/core/barcode_labels.html new file mode 100644 index 0000000..72c7dcc --- /dev/null +++ b/core/templates/core/barcode_labels.html @@ -0,0 +1,429 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} +
    +
    +

    Barcode Label Printing

    + +
    + +
    + +
    +
    +
    +
    1. Select Products
    +
    +
    +
    + + +
    +
    + + + + + + + + + + {% for product in products %} + + + + + + {% endfor %} + +
    ProductSKUAction
    +
    {{ product.name_en }}
    + {{ product.name_ar }} +
    {{ product.sku }} + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    2. Label Queue & Settings
    +
    +
    +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + +
    + + + + + + + + + + + +
    ProductQty of Labels
    +
    + + Queue is empty. Select products to start. +
    +
    +
    +
    + + +
    +
    +
    3. Live Preview (Single Label)
    +
    +
    +
    +
    +
    Product Name
    + + +
    +
    +
    + Note: This is a preview of the layout. Actual print layout depends on settings above. +
    +
    +
    +
    +
    +
    + + + + + + + + +{% endblock %} diff --git a/core/templates/core/invoice_detail.html b/core/templates/core/invoice_detail.html index a169ab7..33fed51 100644 --- a/core/templates/core/invoice_detail.html +++ b/core/templates/core/invoice_detail.html @@ -8,15 +8,20 @@
    - {% trans "Back to Invoices" %} + {% trans "Back to Invoices" %} / العودة إلى الفواتير - +
    + + +
    -
    +
    @@ -32,23 +37,23 @@

    {{ settings.phone }}

    {{ settings.email }}

    {% if settings.vat_number %} -

    {% trans "VAT" %}: {{ settings.vat_number }}

    +

    {% trans "VAT" %} / الضريبة: {{ settings.vat_number }}

    {% endif %}
    -
    -

    {% trans "Tax Invoice" %}

    -
    -
    {% trans "Invoice Number" %}
    +
    +

    {% trans "Tax Invoice" %} / فاتورة ضريبية

    +
    +
    {% trans "Invoice Number" %} / رقم الفاتورة
    {{ sale.invoice_number|default:sale.id }}
    -
    +
    -
    {% trans "Issue Date" %}
    +
    {% trans "Issue Date" %} / تاريخ الإصدار
    {{ sale.created_at|date:"Y-m-d" }}
    -
    {% trans "Due Date" %}
    +
    {% trans "Due Date" %} / تاريخ الاستحقاق
    {{ sale.due_date|date:"Y-m-d"|default:"-" }}
    @@ -57,7 +62,7 @@
    -
    {% trans "Customer Information" %}
    +
    {% trans "Customer Information" %} / معلومات العميل
    {{ sale.customer.name|default:_("Guest Customer") }}
    {% if sale.customer.phone %}
    {{ sale.customer.phone }}
    @@ -67,14 +72,14 @@ {% endif %}
    -
    {% trans "Payment Status" %}
    +
    {% trans "Payment Status" %} / حالة الدفع
    {% if sale.status == 'paid' %} - {% trans "Fully Paid" %} + {% trans "Fully Paid" %} / مدفوع بالكامل {% elif sale.status == 'partial' %} - {% trans "Partially Paid" %} + {% trans "Partially Paid" %} / مدفوع جزئياً {% else %} - {% trans "Unpaid" %} + {% trans "Unpaid" %} / غير مدفوع {% endif %}
    @@ -85,10 +90,22 @@ - - - - + + + + @@ -107,29 +124,44 @@ - + {% if sale.discount > 0 %} - + {% endif %} - + - + - + @@ -139,15 +171,15 @@ {% if sale.payments.exists %}
    -
    {% trans "Payment Records" %}
    +
    {% trans "Payment Records" %} / سجلات الدفع
    {% trans "Item Description" %}{% trans "Unit Price" %}{% trans "Quantity" %}{% trans "Total" %} +
    {% trans "Item Description" %}
    +
    وصف العنصر
    +
    +
    {% trans "Unit Price" %}
    +
    سعر الوحدة
    +
    +
    {% trans "Quantity" %}
    +
    الكمية
    +
    +
    {% trans "Total" %}
    +
    المجموع
    +
    {% trans "Subtotal" %} +
    {% trans "Subtotal" %}
    +
    المجموع الفرعي
    +
    {{ settings.currency_symbol }}{{ sale.total_amount|add:sale.discount|floatformat:3 }}
    {% trans "Discount" %} +
    {% trans "Discount" %}
    +
    الخصم
    +
    -{{ settings.currency_symbol }}{{ sale.discount|floatformat:3 }}
    {% trans "Grand Total" %} +
    {% trans "Grand Total" %}
    +
    المجموع الكلي
    +
    {{ settings.currency_symbol }}{{ sale.total_amount|floatformat:3 }}
    {% trans "Total Paid" %} +
    {% trans "Total Paid" %}
    +
    إجمالي المدفوع
    +
    {{ settings.currency_symbol }}{{ sale.paid_amount|floatformat:3 }}
    {% trans "Balance Due" %} +
    {% trans "Balance Due" %}
    +
    الرصيد المستحق
    +
    {{ settings.currency_symbol }}{{ sale.balance_due|floatformat:3 }}
    - - - - + + + + @@ -168,28 +200,65 @@ {% if sale.notes %}
    -
    {% trans "Internal Notes" %}
    +
    {% trans "Internal Notes" %} / ملاحظات داخلية

    {{ sale.notes }}

    {% endif %}
    -

    {% trans "Thank you for your business!" %}

    -

    {% trans "Software by Meezan" %}

    +

    {% trans "Thank you for your business!" %} / شكراً لتعاملكم معنا!

    +

    {% trans "Software by Meezan" %} / برمجة ميزان

    + + + {% endblock %} \ No newline at end of file diff --git a/core/templates/core/purchase_detail.html b/core/templates/core/purchase_detail.html index d042075..d515b0e 100644 --- a/core/templates/core/purchase_detail.html +++ b/core/templates/core/purchase_detail.html @@ -8,15 +8,20 @@
    - {% trans "Back to List" %} + {% trans "Back to List" %} / العودة للقائمة - +
    + + +
    -
    +
    @@ -32,23 +37,23 @@

    {{ settings.phone }}

    {{ settings.email }}

    {% if settings.vat_number %} -

    {% trans "VAT" %}: {{ settings.vat_number }}

    +

    {% trans "VAT" %} / الضريبة: {{ settings.vat_number }}

    {% endif %}
    -

    {% trans "Purchase Invoice" %}

    +

    {% trans "Purchase Invoice" %} / فاتورة مشتريات

    -
    {% trans "Invoice Number" %}
    +
    {% trans "Invoice Number" %} / رقم الفاتورة
    {{ purchase.invoice_number|default:purchase.id }}
    -
    {% trans "Issue Date" %}
    +
    {% trans "Issue Date" %} / تاريخ الإصدار
    {{ purchase.created_at|date:"Y-m-d" }}
    -
    {% trans "Due Date" %}
    +
    {% trans "Due Date" %} / تاريخ الاستحقاق
    {{ purchase.due_date|date:"Y-m-d"|default:"-" }}
    @@ -57,7 +62,7 @@
    -
    {% trans "Supplier Information" %}
    +
    {% trans "Supplier Information" %} / معلومات المورد
    {{ purchase.supplier.name }}
    {% if purchase.supplier.phone %}
    {{ purchase.supplier.phone }}
    @@ -67,14 +72,14 @@ {% endif %}
    -
    {% trans "Payment Status" %}
    +
    {% trans "Payment Status" %} / حالة الدفع
    {% if purchase.status == 'paid' %} - {% trans "Fully Paid" %} + {% trans "Fully Paid" %} / مدفوع بالكامل {% elif purchase.status == 'partial' %} - {% trans "Partially Paid" %} + {% trans "Partially Paid" %} / مدفوع جزئياً {% else %} - {% trans "Unpaid" %} + {% trans "Unpaid" %} / غير مدفوع {% endif %}
    @@ -85,10 +90,22 @@
    {% trans "Date" %}{% trans "Method" %}{% trans "Amount" %}{% trans "Notes" %}{% trans "Date" %} / التاريخ{% trans "Method" %} / الطريقة{% trans "Amount" %} / المبلغ{% trans "Notes" %} / ملاحظات
    - - - - + + + + @@ -107,17 +124,26 @@ - + - + - + @@ -127,15 +153,15 @@ {% if purchase.payments.exists %}
    -
    {% trans "Payment History" %}
    +
    {% trans "Payment History" %} / سجل الدفعات
    {% trans "Item Description" %}{% trans "Cost Price" %}{% trans "Quantity" %}{% trans "Total" %} +
    {% trans "Item Description" %}
    +
    وصف العنصر
    +
    +
    {% trans "Cost Price" %}
    +
    سعر التكلفة
    +
    +
    {% trans "Quantity" %}
    +
    الكمية
    +
    +
    {% trans "Total" %}
    +
    المجموع
    +
    {% trans "Grand Total" %} +
    {% trans "Grand Total" %}
    +
    المجموع الكلي
    +
    {{ settings.currency_symbol }}{{ purchase.total_amount|floatformat:3 }}
    {% trans "Total Paid" %} +
    {% trans "Total Paid" %}
    +
    إجمالي المدفوع
    +
    {{ settings.currency_symbol }}{{ purchase.paid_amount|floatformat:3 }}
    {% trans "Balance Due" %} +
    {% trans "Balance Due" %}
    +
    الرصيد المستحق
    +
    {{ settings.currency_symbol }}{{ purchase.balance_due|floatformat:3 }}
    - - - - + + + + @@ -156,27 +182,64 @@ {% if purchase.notes %}
    -
    {% trans "Notes" %}
    +
    {% trans "Notes" %} / ملاحظات

    {{ purchase.notes }}

    {% endif %}
    -

    {% trans "Thank you for your business!" %}

    +

    {% trans "Thank you for your business!" %} / شكراً لتعاملكم معنا!

    + + + {% endblock %} \ No newline at end of file diff --git a/core/templates/core/purchase_return_create.html b/core/templates/core/purchase_return_create.html new file mode 100644 index 0000000..2dd5559 --- /dev/null +++ b/core/templates/core/purchase_return_create.html @@ -0,0 +1,255 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "New Purchase Return" %} | {{ site_settings.business_name }}{% endblock %} + +{% block content %} +
    +
    + +
    +
    +
    +
    {% trans "Create Purchase Return" %}
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    + + +
    + +
    +
    + +
    +
    +
    + + +
    +
    {% trans "Date" %}{% trans "Method" %}{% trans "Amount" %}{% trans "Notes" %}{% trans "Date" %} / التاريخ{% trans "Method" %} / الطريقة{% trans "Amount" %} / المبلغ{% trans "Notes" %} / ملاحظات
    + + + + + + + + + + + + + + + + + + + + + +
    {% trans "Product" %}{% trans "Cost Price" %}{% trans "Quantity" %}{% trans "Total" %}
    +
    [[ item.name_en ]]
    +
    [[ item.sku ]]
    +
    + + + + [[ currencySymbol ]][[ (item.price * item.quantity).toFixed(3) ]] + +
    + {% trans "Search and add products to this return." %} +
    +
    +
    +
    +
    + + +
    +
    +
    +
    {% trans "Return Summary" %}
    + +
    + {% trans "Total Amount" %} +

    [[ currencySymbol ]][[ subtotal.toFixed(3) ]]

    +
    + +
    + +
    + + +
    + +
    + + {% trans "Completing this return will automatically decrease the stock quantity for the selected items." %} +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/purchase_return_detail.html b/core/templates/core/purchase_return_detail.html new file mode 100644 index 0000000..6eb9c6a --- /dev/null +++ b/core/templates/core/purchase_return_detail.html @@ -0,0 +1,183 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Purchase Return" %} #{{ purchase_return.return_number|default:purchase_return.id }} | {{ site_settings.business_name }}{% endblock %} + +{% block content %} +
    + +
    + + {% trans "Back to Returns" %} / العودة إلى المرتجعات + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + {% if settings.logo %} + Logo + {% else %} +

    {{ settings.business_name }}

    + {% endif %} +
    +

    {{ settings.address }}

    +

    {{ settings.phone }}

    +

    {{ settings.email }}

    +
    +
    +
    +

    {% trans "Purchase Return" %} / مرتجع مشتريات

    +
    +
    {% trans "Return Number" %} / رقم المرتجع
    +
    {{ purchase_return.return_number|default:purchase_return.id }}
    +
    +
    +
    +
    {% trans "Return Date" %} / تاريخ المرتجع
    +
    {{ purchase_return.created_at|date:"Y-m-d" }}
    +
    +
    +
    {% trans "Original Purchase" %} / الشراء الأصلي
    +
    {% if purchase_return.purchase %}#{{ purchase_return.purchase.invoice_number|default:purchase_return.purchase.id }}{% else %}-{% endif %}
    +
    +
    +
    +
    + +
    +
    +
    {% trans "Supplier Information" %} / معلومات المورد
    +
    {{ purchase_return.supplier.name|default:"N/A" }}
    + {% if purchase_return.supplier.phone %} +
    {{ purchase_return.supplier.phone }}
    + {% endif %} +
    +
    + + +
    + + + + + + + + + + + {% for item in purchase_return.items.all %} + + + + + + + {% endfor %} + + + + + + + + +
    +
    {% trans "Item Description" %}
    +
    وصف العنصر
    +
    +
    {% trans "Cost Price" %}
    +
    سعر التكلفة
    +
    +
    {% trans "Quantity" %}
    +
    الكمية
    +
    +
    {% trans "Total" %}
    +
    المجموع
    +
    +
    {{ item.product.name_en }}
    +
    {{ item.product.name_ar }}
    +
    {{ settings.currency_symbol }}{{ item.cost_price|floatformat:3 }}{{ item.quantity }}{{ settings.currency_symbol }}{{ item.line_total|floatformat:3 }}
    +
    {% trans "Total Credit" %}
    +
    إجمالي الرصيد المسترد
    +
    {{ settings.currency_symbol }}{{ purchase_return.total_amount|floatformat:3 }}
    +
    + + + {% if purchase_return.notes %} +
    +
    {% trans "Notes" %} / ملاحظات
    +

    {{ purchase_return.notes }}

    +
    + {% endif %} + +
    +

    {% trans "Purchase Return Confirmation" %} / تأكيد مرتجع مشتريات

    +

    {% trans "Software by Meezan" %} / برمجة ميزان

    +
    +
    +
    +
    +
    + + + + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/purchase_returns.html b/core/templates/core/purchase_returns.html new file mode 100644 index 0000000..5376828 --- /dev/null +++ b/core/templates/core/purchase_returns.html @@ -0,0 +1,105 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Purchase Returns" %} | {{ site_settings.business_name }}{% endblock %} + +{% block content %} +
    +
    +
    +

    {% trans "Purchase Returns" %}

    +

    {% trans "Manage returns to suppliers" %}

    +
    + + {% trans "New Purchase Return" %} + +
    + + {% if messages %} +
    + {% for message in messages %} + + {% endfor %} +
    + {% endif %} + +
    +
    +
    + + + + + + + + + + + + + {% for return in returns %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
    {% trans "Return #" %}{% trans "Date" %}{% trans "Supplier" %}{% trans "Original Purchase" %}{% trans "Total Amount" %}{% trans "Actions" %}
    + {{ return.return_number|default:return.id }} + {{ return.created_at|date:"Y-m-d" }}{{ return.supplier.name|default:"N/A" }} + {% if return.purchase %} + + #{{ return.purchase.invoice_number|default:return.purchase.id }} + + {% else %} + N/A + {% endif %} + {{ site_settings.currency_symbol }}{{ return.total_amount|floatformat:3 }} +
    + + + + +
    + + + +
    + Empty +

    {% trans "No purchase returns found." %}

    +
    +
    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/quotation_create.html b/core/templates/core/quotation_create.html new file mode 100644 index 0000000..2635ca0 --- /dev/null +++ b/core/templates/core/quotation_create.html @@ -0,0 +1,272 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "New Quotation" %} | {{ site_settings.business_name }}{% endblock %} + +{% block content %} +
    +
    + +
    +
    +
    +
    {% trans "Create New Quotation" %}
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    + + +
    + +
    +
    + +
    +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    {% trans "Product" %}{% trans "Unit Price" %}{% trans "Quantity" %}{% trans "Total" %}
    +
    [[ item.name_en ]]
    +
    [[ item.sku ]]
    +
    + + + + [[ currencySymbol ]][[ (item.price * item.quantity).toFixed(3) ]] + +
    + {% trans "Search and add products to this quotation." %} +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    {% trans "Quotation Summary" %}
    + +
    + {% trans "Subtotal" %} + [[ currencySymbol ]][[ subtotal.toFixed(3) ]] +
    + +
    + {% trans "Discount" %} +
    + +
    +
    + +
    + +
    +

    {% trans "Grand Total" %}

    +

    [[ currencySymbol ]][[ grandTotal.toFixed(3) ]]

    +
    + +
    + + +
    + +
    + + +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/quotation_detail.html b/core/templates/core/quotation_detail.html new file mode 100644 index 0000000..9cc9ca9 --- /dev/null +++ b/core/templates/core/quotation_detail.html @@ -0,0 +1,253 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Quotation" %} #{{ quotation.quotation_number|default:quotation.id }} | {{ site_settings.business_name }}{% endblock %} + +{% block content %} +
    + +
    + + {% trans "Back to Quotations" %} / العودة لعروض الأسعار + +
    + {% if quotation.status != 'converted' %} + + {% endif %} + + +
    +
    + + + + + +
    +
    + +
    +
    +
    + {% if settings.logo %} + Logo + {% else %} +

    {{ settings.business_name }}

    + {% endif %} +
    +

    {{ settings.address }}

    +

    {{ settings.phone }}

    +

    {{ settings.email }}

    + {% if settings.vat_number %} +

    {% trans "VAT" %} / الضريبة: {{ settings.vat_number }}

    + {% endif %} +
    +
    +
    +

    {% trans "Quotation" %} / عرض سعر

    +
    +
    {% trans "Quotation Number" %} / رقم العرض
    +
    {{ quotation.quotation_number|default:quotation.id }}
    +
    +
    +
    +
    {% trans "Date" %} / التاريخ
    +
    {{ quotation.created_at|date:"Y-m-d" }}
    +
    +
    +
    {% trans "Valid Until" %} / صالح لغاية
    +
    {{ quotation.valid_until|date:"Y-m-d"|default:"-" }}
    +
    +
    +
    +
    + +
    +
    +
    {% trans "Quote For" %} / عرض مقدم إلى
    +
    {{ quotation.customer.name|default:_("Guest Customer") }}
    + {% if quotation.customer.phone %} +
    {{ quotation.customer.phone }}
    + {% endif %} + {% if quotation.customer.address %} +
    {{ quotation.customer.address }}
    + {% endif %} +
    +
    +
    {% trans "Status" %} / الحالة
    +
    + {% if quotation.status == 'converted' %} + {% trans "Converted" %} / محول + {% elif quotation.status == 'accepted' %} + {% trans "Accepted" %} / مقبول + {% elif quotation.status == 'rejected' %} + {% trans "Rejected" %} / مرفوض + {% else %} + {% trans "Open" %} / مفتوح + {% endif %} +
    +
    +
    + + +
    + + + + + + + + + + + {% for item in quotation.items.all %} + + + + + + + {% endfor %} + + + + + + + + {% if quotation.discount > 0 %} + + + + + + {% endif %} + + + + + + +
    +
    {% trans "Item Description" %}
    +
    وصف العنصر
    +
    +
    {% trans "Unit Price" %}
    +
    سعر الوحدة
    +
    +
    {% trans "Quantity" %}
    +
    الكمية
    +
    +
    {% trans "Total" %}
    +
    المجموع
    +
    +
    {{ item.product.name_en }}
    +
    {{ item.product.name_ar }}
    +
    {{ settings.currency_symbol }}{{ item.unit_price|floatformat:3 }}{{ item.quantity }}{{ settings.currency_symbol }}{{ item.line_total|floatformat:3 }}
    +
    {% trans "Subtotal" %}
    +
    المجموع الفرعي
    +
    {{ settings.currency_symbol }}{{ quotation.total_amount|add:quotation.discount|floatformat:3 }}
    +
    {% trans "Discount" %}
    +
    الخصم
    +
    -{{ settings.currency_symbol }}{{ quotation.discount|floatformat:3 }}
    +
    {% trans "Grand Total" %}
    +
    المجموع الكلي
    +
    {{ settings.currency_symbol }}{{ quotation.total_amount|floatformat:3 }}
    +
    + + +
    +
    {% trans "Terms and Conditions" %} / الشروط والأحكام
    +
    + {{ quotation.terms_and_conditions|default:_("No specific terms provided.") }} +
    +
    + + + {% if quotation.notes %} +
    +
    {% trans "Internal Notes" %} / ملاحظات داخلية
    +

    {{ quotation.notes }}

    +
    + {% endif %} + +
    +

    {% trans "This is a computer generated quotation." %} / هذا عرض سعر تم إنشاؤه بواسطة الكمبيوتر.

    +

    {% trans "Software by Meezan" %} / برمجة ميزان

    +
    +
    +
    +
    +
    + + + + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/quotations.html b/core/templates/core/quotations.html new file mode 100644 index 0000000..ce04f89 --- /dev/null +++ b/core/templates/core/quotations.html @@ -0,0 +1,135 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Quotations" %} | {{ site_settings.business_name }}{% endblock %} + +{% block content %} +
    +
    +
    +

    {% trans "Quotations" %}

    +

    {% trans "Manage and track your price proposals" %}

    +
    + + {% trans "New Quotation" %} + +
    + + {% if messages %} +
    + {% for message in messages %} + + {% endfor %} +
    + {% endif %} + +
    +
    +
    + + + + + + + + + + + + + + {% for q in quotations %} + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
    {% trans "Quotation #" %}{% trans "Date" %}{% trans "Customer" %}{% trans "Total" %}{% trans "Status" %}{% trans "Valid Until" %}{% trans "Actions" %}
    + {{ q.quotation_number|default:q.id }} + {{ q.created_at|date:"Y-m-d" }}{{ q.customer.name|default:_("Guest") }}{{ site_settings.currency_symbol }}{{ q.total_amount|floatformat:3 }} + {% if q.status == 'draft' %} + {% trans "Draft" %} + {% elif q.status == 'sent' %} + {% trans "Sent" %} + {% elif q.status == 'accepted' %} + {% trans "Accepted" %} + {% elif q.status == 'converted' %} + {% trans "Converted" %} + {% elif q.status == 'rejected' %} + {% trans "Rejected" %} + {% endif %} + {{ q.valid_until|date:"Y-m-d"|default:"-" }} +
    + + + + {% if q.status != 'converted' %} + + {% endif %} + +
    + + + + + + +
    + Empty +

    {% trans "No quotations found." %}

    +
    +
    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/sale_return_create.html b/core/templates/core/sale_return_create.html new file mode 100644 index 0000000..132a434 --- /dev/null +++ b/core/templates/core/sale_return_create.html @@ -0,0 +1,255 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "New Sales Return" %} | {{ site_settings.business_name }}{% endblock %} + +{% block content %} +
    +
    + +
    +
    +
    +
    {% trans "Create Sales Return" %}
    +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    + + +
    + +
    +
    + +
    +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    {% trans "Product" %}{% trans "Unit Price" %}{% trans "Quantity" %}{% trans "Total" %}
    +
    [[ item.name_en ]]
    +
    [[ item.sku ]]
    +
    + + + + [[ currencySymbol ]][[ (item.price * item.quantity).toFixed(3) ]] + +
    + {% trans "Search and add products to this return." %} +
    +
    +
    +
    +
    + + +
    +
    +
    +
    {% trans "Return Summary" %}
    + +
    + {% trans "Total Amount" %} +

    [[ currencySymbol ]][[ subtotal.toFixed(3) ]]

    +
    + +
    + +
    + + +
    + +
    + + {% trans "Completing this return will automatically increase the stock quantity for the selected items." %} +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/sale_return_detail.html b/core/templates/core/sale_return_detail.html new file mode 100644 index 0000000..fb61b03 --- /dev/null +++ b/core/templates/core/sale_return_detail.html @@ -0,0 +1,183 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Sales Return" %} #{{ sale_return.return_number|default:sale_return.id }} | {{ site_settings.business_name }}{% endblock %} + +{% block content %} +
    + +
    + + {% trans "Back to Returns" %} / العودة إلى المرتجعات + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + {% if settings.logo %} + Logo + {% else %} +

    {{ settings.business_name }}

    + {% endif %} +
    +

    {{ settings.address }}

    +

    {{ settings.phone }}

    +

    {{ settings.email }}

    +
    +
    +
    +

    {% trans "Sales Return" %} / مرتجع مبيعات

    +
    +
    {% trans "Return Number" %} / رقم المرتجع
    +
    {{ sale_return.return_number|default:sale_return.id }}
    +
    +
    +
    +
    {% trans "Return Date" %} / تاريخ المرتجع
    +
    {{ sale_return.created_at|date:"Y-m-d" }}
    +
    +
    +
    {% trans "Original Sale" %} / البيع الأصلي
    +
    {% if sale_return.sale %}#{{ sale_return.sale.invoice_number|default:sale_return.sale.id }}{% else %}-{% endif %}
    +
    +
    +
    +
    + +
    +
    +
    {% trans "Customer Information" %} / معلومات العميل
    +
    {{ sale_return.customer.name|default:_("Guest Customer") }}
    + {% if sale_return.customer.phone %} +
    {{ sale_return.customer.phone }}
    + {% endif %} +
    +
    + + +
    + + + + + + + + + + + {% for item in sale_return.items.all %} + + + + + + + {% endfor %} + + + + + + + + +
    +
    {% trans "Item Description" %}
    +
    وصف العنصر
    +
    +
    {% trans "Unit Price" %}
    +
    سعر الوحدة
    +
    +
    {% trans "Quantity" %}
    +
    الكمية
    +
    +
    {% trans "Total" %}
    +
    المجموع
    +
    +
    {{ item.product.name_en }}
    +
    {{ item.product.name_ar }}
    +
    {{ settings.currency_symbol }}{{ item.unit_price|floatformat:3 }}{{ item.quantity }}{{ settings.currency_symbol }}{{ item.line_total|floatformat:3 }}
    +
    {% trans "Total Refund" %}
    +
    إجمالي المبلغ المرتجع
    +
    {{ settings.currency_symbol }}{{ sale_return.total_amount|floatformat:3 }}
    +
    + + + {% if sale_return.notes %} +
    +
    {% trans "Notes" %} / ملاحظات
    +

    {{ sale_return.notes }}

    +
    + {% endif %} + +
    +

    {% trans "Sales Return Confirmation" %} / تأكيد مرتجع مبيعات

    +

    {% trans "Software by Meezan" %} / برمجة ميزان

    +
    +
    +
    +
    +
    + + + + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/sales_returns.html b/core/templates/core/sales_returns.html new file mode 100644 index 0000000..838249d --- /dev/null +++ b/core/templates/core/sales_returns.html @@ -0,0 +1,105 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block title %}{% trans "Sales Returns" %} | {{ site_settings.business_name }}{% endblock %} + +{% block content %} +
    +
    +
    +

    {% trans "Sales Returns" %}

    +

    {% trans "Manage customer product returns" %}

    +
    + + {% trans "New Sales Return" %} + +
    + + {% if messages %} +
    + {% for message in messages %} + + {% endfor %} +
    + {% endif %} + +
    +
    +
    + + + + + + + + + + + + + {% for return in returns %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
    {% trans "Return #" %}{% trans "Date" %}{% trans "Customer" %}{% trans "Original Sale" %}{% trans "Total Amount" %}{% trans "Actions" %}
    + {{ return.return_number|default:return.id }} + {{ return.created_at|date:"Y-m-d" }}{{ return.customer.name|default:_("Guest") }} + {% if return.sale %} + + #{{ return.sale.invoice_number|default:return.sale.id }} + + {% else %} + N/A + {% endif %} + {{ site_settings.currency_symbol }}{{ return.total_amount|floatformat:3 }} +
    + + + + +
    + + + +
    + Empty +

    {% trans "No sales returns found." %}

    +
    +
    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index d0213eb..a8f3132 100644 --- a/core/urls.py +++ b/core/urls.py @@ -12,18 +12,40 @@ urlpatterns = [ 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/', views.invoice_list, name='invoices'), path('invoices/create/', views.invoice_create, name='invoice_create'), path('invoices//', views.invoice_detail, name='invoice_detail'), path('invoices/payment//', views.add_sale_payment, name='add_sale_payment'), path('invoices/delete//', views.delete_sale, name='delete_sale'), + # Quotations + path('quotations/', views.quotations, name='quotations'), + path('quotations/create/', views.quotation_create, name='quotation_create'), + path('quotations//', views.quotation_detail, name='quotation_detail'), + path('quotations/convert//', views.convert_quotation_to_invoice, name='convert_quotation_to_invoice'), + path('quotations/delete//', views.delete_quotation, name='delete_quotation'), + path('api/create-quotation/', views.create_quotation_api, name='create_quotation_api'), + + # Sales Returns + path('sales/returns/', views.sales_returns, name='sales_returns'), + path('sales/returns/create/', views.sale_return_create, name='sale_return_create'), + path('sales/returns//', views.sale_return_detail, name='sale_return_detail'), + path('sales/returns/delete//', views.delete_sale_return, name='delete_sale_return'), + path('api/create-sale-return/', views.create_sale_return_api, name='create_sale_return_api'), + # Purchases (Invoices) path('purchases/create/', views.purchase_create, name='purchase_create'), path('purchases//', views.purchase_detail, name='purchase_detail'), path('purchases/payment//', views.add_purchase_payment, name='add_purchase_payment'), path('purchases/delete//', views.delete_purchase, name='delete_purchase'), + # Purchase Returns + path('purchases/returns/', views.purchase_returns, name='purchase_returns'), + path('purchases/returns/create/', views.purchase_return_create, name='purchase_return_create'), + path('purchases/returns//', views.purchase_return_detail, name='purchase_return_detail'), + path('purchases/returns/delete//', views.delete_purchase_return, name='delete_purchase_return'), + path('api/create-purchase-return/', views.create_purchase_return_api, name='create_purchase_return_api'), + # 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'), @@ -42,6 +64,7 @@ urlpatterns = [ path('inventory/add/', views.add_product, name='add_product'), path('inventory/edit//', views.edit_product, name='edit_product'), path('inventory/delete//', views.delete_product, name='delete_product'), + path('inventory/barcodes/', views.barcode_labels, name='barcode_labels'), # Categories path('inventory/category/add/', views.add_category, name='add_category'), @@ -52,4 +75,4 @@ urlpatterns = [ path('inventory/unit/add/', views.add_unit, name='add_unit'), path('inventory/unit/edit//', views.edit_unit, name='edit_unit'), path('inventory/unit/delete//', views.delete_unit, name='delete_unit'), -] \ No newline at end of file +] diff --git a/core/views.py b/core/views.py index bb5c1a2..ca90f7f 100644 --- a/core/views.py +++ b/core/views.py @@ -6,7 +6,9 @@ from django.views.decorators.csrf import csrf_exempt from .models import ( Product, Sale, Category, Unit, Customer, Supplier, Purchase, PurchaseItem, PurchasePayment, - SaleItem, SalePayment, SystemSetting + SaleItem, SalePayment, SystemSetting, + Quotation, QuotationItem, + SaleReturn, SaleReturnItem, PurchaseReturn, PurchaseReturnItem ) import json from datetime import timedelta @@ -348,6 +350,263 @@ def delete_sale(request, pk): messages.success(request, _("Sale deleted successfully!")) return redirect('invoices') +# --- Quotation Views --- + +def quotations(request): + quotations_list = Quotation.objects.all().order_by('-created_at') + customers = Customer.objects.all() + return render(request, 'core/quotations.html', {'quotations': quotations_list, 'customers': customers}) + +def quotation_create(request): + products = Product.objects.filter(is_active=True) + customers = Customer.objects.all() + return render(request, 'core/quotation_create.html', {'products': products, 'customers': customers}) + +def quotation_detail(request, pk): + quotation = get_object_or_404(Quotation, pk=pk) + settings = SystemSetting.objects.first() + return render(request, 'core/quotation_detail.html', {'quotation': quotation, 'settings': settings}) + +@csrf_exempt +def create_quotation_api(request): + if request.method == 'POST': + try: + data = json.loads(request.body) + customer_id = data.get('customer_id') + quotation_number = data.get('quotation_number', '') + items = data.get('items', []) + total_amount = data.get('total_amount', 0) + discount = data.get('discount', 0) + valid_until = data.get('valid_until') + terms_and_conditions = data.get('terms_and_conditions', '') + notes = data.get('notes', '') + + customer = None + if customer_id: + customer = Customer.objects.get(id=customer_id) + + quotation = Quotation.objects.create( + customer=customer, + quotation_number=quotation_number, + total_amount=total_amount, + discount=discount, + valid_until=valid_until if valid_until else None, + terms_and_conditions=terms_and_conditions, + notes=notes + ) + + for item in items: + product = Product.objects.get(id=item['id']) + QuotationItem.objects.create( + quotation=quotation, + product=product, + quantity=item['quantity'], + unit_price=item['price'], + line_total=item['line_total'] + ) + + return JsonResponse({'success': True, 'quotation_id': quotation.id}) + except Exception as e: + return JsonResponse({'success': False, 'error': str(e)}, status=400) + return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405) + +def convert_quotation_to_invoice(request, pk): + quotation = get_object_or_404(Quotation, pk=pk) + if quotation.status == 'converted': + messages.warning(request, _("This quotation has already been converted to an invoice.")) + return redirect('invoices') + + # Create Sale from Quotation + sale = Sale.objects.create( + customer=quotation.customer, + quotation=quotation, + total_amount=quotation.total_amount, + discount=quotation.discount, + balance_due=quotation.total_amount, + payment_type='cash', + status='unpaid', + notes=quotation.notes + ) + + # Create SaleItems and Update Stock + for item in quotation.items.all(): + SaleItem.objects.create( + sale=sale, + product=item.product, + quantity=item.quantity, + unit_price=item.unit_price, + line_total=item.line_total + ) + # Deduct Stock + item.product.stock_quantity -= item.quantity + item.product.save() + + # Update Quotation Status + quotation.status = 'converted' + quotation.save() + + messages.success(request, _("Quotation converted to Invoice successfully!")) + return redirect('invoice_detail', pk=sale.pk) + +def delete_quotation(request, pk): + quotation = get_object_or_404(Quotation, pk=pk) + quotation.delete() + messages.success(request, _("Quotation deleted successfully!")) + return redirect('quotations') + +# --- Sale Return Views --- + +def sales_returns(request): + returns = SaleReturn.objects.all().order_by('-created_at') + return render(request, 'core/sales_returns.html', {'returns': returns}) + +def sale_return_create(request): + products = Product.objects.filter(is_active=True) + customers = Customer.objects.all() + sales = Sale.objects.all().order_by('-created_at') + return render(request, 'core/sale_return_create.html', { + 'products': products, + 'customers': customers, + 'sales': sales + }) + +def sale_return_detail(request, pk): + sale_return = get_object_or_404(SaleReturn, pk=pk) + settings = SystemSetting.objects.first() + return render(request, 'core/sale_return_detail.html', {'sale_return': sale_return, 'settings': settings}) + +@csrf_exempt +def create_sale_return_api(request): + if request.method == 'POST': + try: + data = json.loads(request.body) + sale_id = data.get('sale_id') + customer_id = data.get('customer_id') + return_number = data.get('return_number', '') + items = data.get('items', []) + total_amount = data.get('total_amount', 0) + notes = data.get('notes', '') + + customer = None + if customer_id: + customer = Customer.objects.get(id=customer_id) + + sale = None + if sale_id: + sale = Sale.objects.get(id=sale_id) + + sale_return = SaleReturn.objects.create( + sale=sale, + customer=customer, + return_number=return_number, + total_amount=total_amount, + notes=notes + ) + + for item in items: + product = Product.objects.get(id=item['id']) + SaleReturnItem.objects.create( + sale_return=sale_return, + product=product, + quantity=item['quantity'], + unit_price=item['price'], + line_total=item['line_total'] + ) + # Increase Stock for Sales Return + product.stock_quantity += int(item['quantity']) + product.save() + + return JsonResponse({'success': True, 'return_id': sale_return.id}) + except Exception as e: + return JsonResponse({'success': False, 'error': str(e)}, status=400) + return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405) + +def delete_sale_return(request, pk): + sale_return = get_object_or_404(SaleReturn, pk=pk) + for item in sale_return.items.all(): + item.product.stock_quantity -= item.quantity + item.product.save() + sale_return.delete() + messages.success(request, _("Sale return deleted successfully!")) + return redirect('sales_returns') + + +# --- Purchase Return Views --- + +def purchase_returns(request): + returns = PurchaseReturn.objects.all().order_by('-created_at') + return render(request, 'core/purchase_returns.html', {'returns': returns}) + +def purchase_return_create(request): + products = Product.objects.filter(is_active=True) + suppliers = Supplier.objects.all() + purchases = Purchase.objects.all().order_by('-created_at') + return render(request, 'core/purchase_return_create.html', { + 'products': products, + 'suppliers': suppliers, + 'purchases': purchases + }) + +def purchase_return_detail(request, pk): + purchase_return = get_object_or_404(PurchaseReturn, pk=pk) + settings = SystemSetting.objects.first() + return render(request, 'core/purchase_return_detail.html', {'purchase_return': purchase_return, 'settings': settings}) + +@csrf_exempt +def create_purchase_return_api(request): + if request.method == 'POST': + try: + data = json.loads(request.body) + purchase_id = data.get('purchase_id') + supplier_id = data.get('supplier_id') + return_number = data.get('return_number', '') + items = data.get('items', []) + total_amount = data.get('total_amount', 0) + notes = data.get('notes', '') + + supplier = None + if supplier_id: + supplier = Supplier.objects.get(id=supplier_id) + + purchase = None + if purchase_id: + purchase = Purchase.objects.get(id=purchase_id) + + purchase_return = PurchaseReturn.objects.create( + purchase=purchase, + supplier=supplier, + return_number=return_number, + total_amount=total_amount, + notes=notes + ) + + for item in items: + product = Product.objects.get(id=item['id']) + PurchaseReturnItem.objects.create( + purchase_return=purchase_return, + product=product, + quantity=item['quantity'], + cost_price=item['price'], + line_total=item['line_total'] + ) + # Decrease Stock for Purchase Return + product.stock_quantity -= int(item['quantity']) + product.save() + + return JsonResponse({'success': True, 'return_id': purchase_return.id}) + except Exception as e: + return JsonResponse({'success': False, 'error': str(e)}, status=400) + return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405) + +def delete_purchase_return(request, pk): + purchase_return = get_object_or_404(PurchaseReturn, pk=pk) + for item in purchase_return.items.all(): + item.product.stock_quantity += item.quantity + item.product.save() + purchase_return.delete() + messages.success(request, _("Purchase return deleted successfully!")) + return redirect('purchase_returns') + # --- Other Management Views --- def reports(request): @@ -575,3 +834,8 @@ def delete_unit(request, pk): unit.delete() messages.success(request, "Unit deleted successfully!") return redirect('inventory') + +def barcode_labels(request): + products = Product.objects.filter(is_active=True).order_by('name_en') + context = {'products': products} + return render(request, 'core/barcode_labels.html', context)