diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 4ea5329..d5408c1 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index cc90925..2513855 100644 Binary files a/config/__pycache__/wsgi.cpython-311.pyc and b/config/__pycache__/wsgi.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index bc8b083..f3194d3 100644 --- a/config/settings.py +++ b/config/settings.py @@ -155,6 +155,8 @@ SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SESSION_COOKIE_SAMESITE = "None" CSRF_COOKIE_SAMESITE = "None" +SECURE_CROSS_ORIGIN_OPENER_POLICY = None +SECURE_REFERRER_POLICY = 'no-referrer-when-downgrade' # X_FRAME_OPTIONS = 'SAMEORIGIN' # Email Settings diff --git a/config/wsgi.py b/config/wsgi.py index 28f3157..325a2b3 100644 --- a/config/wsgi.py +++ b/config/wsgi.py @@ -20,12 +20,11 @@ try: except ImportError: pass -# --- FIX: Preload libraries for WeasyPrint/Pango --- -# Manually load libraries using absolute paths -import ctypes -import ctypes.util +# --- WeasyPrint Library Preloading --- import traceback import sys +import ctypes +import ctypes.util lib_paths = [ '/usr/lib/x86_64-linux-gnu/libglib-2.0.so.0', @@ -42,31 +41,13 @@ for path in lib_paths: except OSError: pass -# Try to import weasyprint try: import weasyprint except Exception as e: - # Log error to file and stderr error_msg = f"WeasyPrint Import Error: {str(e)}\n{traceback.format_exc()}" - sys.stderr.write(error_msg) - try: - with open("weasyprint_wsgi_error.log", "w") as f: - f.write(error_msg) - except Exception: - pass - - # Fallback to mock - import types - class MockHTML: - def __init__(self, string=None, base_url=None): - pass - def write_pdf(self, target=None): - raise OSError("WeasyPrint system dependencies are missing. PDF generation is disabled.") - - mock_wp = types.ModuleType("weasyprint") - mock_wp.HTML = MockHTML - sys.modules["weasyprint"] = mock_wp -# --------------------------------------------------- + with open("/tmp/weasyprint_wsgi_error.log", "w") as f: + f.write(error_msg) +# ------------------------------------- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 50c3078..78c0708 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/templates/base.html b/core/templates/base.html index d72fcb3..e8c5dcc 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -34,7 +34,6 @@ {% block head %}{% endblock %} -
{% if user.is_authenticated %} diff --git a/core/views.py b/core/views.py index 0641668..601c940 100644 --- a/core/views.py +++ b/core/views.py @@ -636,7 +636,12 @@ def sale_receipt(request, pk): @login_required def quotations(request): - quotations = Quotation.objects.all().order_by('-created_at') + quotations_qs = Quotation.objects.all().order_by('-created_at') + + paginator = Paginator(quotations_qs, 20) + page_number = request.GET.get('page') + quotations = paginator.get_page(page_number) + return render(request, 'core/quotations.html', {'quotations': quotations}) @login_required @@ -690,11 +695,60 @@ def delete_quotation(request, pk): messages.success(request, _("Quotation deleted.")) return redirect('quotations') -@csrf_exempt @login_required def create_quotation_api(request): - # Simplified API stub - return JsonResponse({'success': True}) + if request.method != 'POST': + return JsonResponse({'success': False, 'error': 'Method not allowed'}) + + 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', '') + + if not items: + return JsonResponse({'success': False, 'error': _('Cannot save an empty quotation.')}) + + with transaction.atomic(): + 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, + created_by=request.user + ) + + 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'] + ) + + messages.success(request, _("Quotation created successfully.")) + return JsonResponse({'success': True, 'quotation_id': quotation.id}) + except Customer.DoesNotExist: + return JsonResponse({'success': False, 'error': _('Customer not found.')}) + except Product.DoesNotExist: + return JsonResponse({'success': False, 'error': _('One or more products not found.')}) + except Exception as e: + logger.error(f"Error creating quotation: {str(e)}") + return JsonResponse({'success': False, 'error': str(e)}) # --- Sales Returns --- diff --git a/manage.py b/manage.py index fcd4440..f3a0730 100755 --- a/manage.py +++ b/manage.py @@ -14,9 +14,7 @@ def main(): except ImportError: pass - # --- FIX: Preload libraries for WeasyPrint/Pango --- - # Manually load libraries using absolute paths since LD_LIBRARY_PATH - # changes don't affect dlopen() in the running process. + # --- WeasyPrint Library Preloading --- import ctypes import ctypes.util import traceback @@ -34,34 +32,15 @@ def main(): try: ctypes.CDLL(path) except OSError: - # Continue even if one fails pass - # Try to import weasyprint to see if it works try: import weasyprint except Exception as e: - # Log the specific error for debugging error_msg = f"WeasyPrint Import Error: {str(e)}\n{traceback.format_exc()}" - sys.stderr.write(error_msg) - try: - with open("weasyprint_error.log", "w") as f: - f.write(error_msg) - except Exception: - pass - - # Fallback to mock if system dependencies are missing or import fails - import types - class MockHTML: - def __init__(self, string=None, base_url=None): - pass - def write_pdf(self, target=None): - raise OSError("WeasyPrint system dependencies are missing. PDF generation is disabled.") - - mock_wp = types.ModuleType("weasyprint") - mock_wp.HTML = MockHTML - sys.modules["weasyprint"] = mock_wp - # --------------------------------------------------- + with open("/tmp/weasyprint_error.log", "w") as f: + f.write(error_msg) + # ------------------------------------- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') try: diff --git a/test_pos_render.py b/test_pos_render.py new file mode 100644 index 0000000..c1d7964 --- /dev/null +++ b/test_pos_render.py @@ -0,0 +1,52 @@ +import os +import django +from django.conf import settings + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') +django.setup() + +from django.template.loader import render_to_string +from django.test import RequestFactory +from core.models import Product, SystemSetting, Category, PaymentMethod + +def test_pos_render(): + factory = RequestFactory() + request = factory.get('/pos/') + + s_settings = SystemSetting.objects.first() + products = Product.objects.filter(is_active=True) + categories = Category.objects.all() + payment_methods = PaymentMethod.objects.all() + + context = { + 'products': products, + 'customers': [], + 'categories': categories, + 'payment_methods': payment_methods, + 'settings': s_settings, + 'site_settings': s_settings, + 'active_session': None, + 'LANGUAGE_CODE': 'en' + } + + rendered = render_to_string('core/pos.html', context, request=request) + + print(f"Total Products Checked: {products.count()}") + # Check for image URLs + for product in products: + if product.image: + url = product.image.url + if url in rendered: + print(f"Product {product.name_en} image URL FOUND: {url}") + else: + # Check for escaped URL + from django.utils.html import escape + if escape(url) in rendered: + print(f"Product {product.name_en} image URL FOUND (escaped): {escape(url)}") + else: + print(f"Product {product.name_en} image URL MISSING: {url}") + else: + print(f"Product {product.name_en} has no image in DB") + +if __name__ == "__main__": + test_pos_render() \ No newline at end of file diff --git a/test_render.py b/test_render.py new file mode 100644 index 0000000..f444fd7 --- /dev/null +++ b/test_render.py @@ -0,0 +1,57 @@ +import os +import django +from django.conf import settings +from django.template.loader import render_to_string +from django.test import RequestFactory + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') +django.setup() + +from core.models import SystemSetting, Product, Customer, Sale, Category, PaymentMethod +from django.utils import timezone +import datetime + +def test_render(): + factory = RequestFactory() + request = factory.get('/') + # Simulate a user + from django.contrib.auth.models import User + user = User.objects.first() + request.user = user + + settings_obj = SystemSetting.objects.first() + context = { + 'site_settings': settings_obj, + 'settings': settings_obj, + 'total_sales_amount': 0, + 'total_receivables': 0, + 'total_payables': 0, + 'total_sales_count': 0, + 'total_products': 0, + 'total_customers': 0, + 'monthly_labels': [], + 'monthly_data': [], + 'chart_labels': [], + 'chart_data': [], + 'category_labels': [], + 'category_data': [], + 'payment_labels': [], + 'payment_data': [], + 'top_products': [], + 'low_stock_count': 0, + 'low_stock_products': [], + 'expired_count': 0, + 'recent_sales': [], + } + + try: + html = render_to_string('core/index.html', context, request=request) + print(f"Render successful, length: {len(html)}") + if len(html) < 100: + print("HTML is too short!") + print(html) + except Exception as e: + print(f"Render failed: {e}") + +if __name__ == "__main__": + test_render()