diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 9b2cfe0..1a0c2bf 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 8d6caf3..a93a9a6 100644 --- a/config/settings.py +++ b/config/settings.py @@ -156,23 +156,11 @@ USE_I18N = True USE_TZ = True -# Script Name (for subpath deployment) -FORCE_SCRIPT_NAME = os.getenv("FORCE_SCRIPT_NAME") - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.2/howto/static-files/ -# Default to 'static/' but allow override or adjust based on FORCE_SCRIPT_NAME -if FORCE_SCRIPT_NAME: - # Ensure FORCE_SCRIPT_NAME starts with / and ends without / for consistency if needed, - # but normally users provide "/meezan". - # We strip trailing slash from script name when forming static url - _script_prefix = FORCE_SCRIPT_NAME.rstrip('/') - STATIC_URL = os.getenv("STATIC_URL", f"{_script_prefix}/static/") - MEDIA_URL = os.getenv("MEDIA_URL", f"{_script_prefix}/media/") -else: - STATIC_URL = os.getenv("STATIC_URL", "static/") - MEDIA_URL = os.getenv("MEDIA_URL", "media/") +STATIC_URL = os.getenv("STATIC_URL", "/static/") +MEDIA_URL = os.getenv("MEDIA_URL", "/media/") # Collect static into a separate folder; avoid overlapping with STATICFILES_DIRS. STATIC_ROOT = BASE_DIR / 'staticfiles' diff --git a/core/__pycache__/helpers.cpython-311.pyc b/core/__pycache__/helpers.cpython-311.pyc new file mode 100644 index 0000000..658002a Binary files /dev/null and b/core/__pycache__/helpers.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 366bd66..a8145bd 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/utils.cpython-311.pyc b/core/__pycache__/utils.cpython-311.pyc index a6171c4..7b4b15e 100644 Binary files a/core/__pycache__/utils.cpython-311.pyc and b/core/__pycache__/utils.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 8244ec0..9bb892f 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/helpers.py b/core/helpers.py new file mode 100644 index 0000000..24477cc --- /dev/null +++ b/core/helpers.py @@ -0,0 +1,122 @@ +def number_to_words_en(number): + """ + Converts a number to English words. + Handles decimals up to 3 places. + """ + if number == 0: + return "Zero" + + units = ["", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", + "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"] + tens = ["", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"] + thousands = ["", "Thousand", "Million", "Billion"] + + def _convert_less_than_thousand(num): + res = "" + if num >= 100: + res += units[num // 100] + " Hundred " + num %= 100 + if num >= 20: + res += tens[num // 10] + " " + num %= 10 + if num > 0: + res += units[num] + return res.strip() + + try: + parts = str(float(number)).split('.') + integer_part = int(parts[0]) + fractional_part = int(parts[1]) if len(parts) > 1 else 0 + except ValueError: + return "Invalid Number" + + res = "" + if integer_part == 0: + res = "Zero" + else: + idx = 0 + while integer_part > 0: + if integer_part % 1000 != 0: + res = _convert_less_than_thousand(integer_part % 1000) + " " + thousands[idx] + " " + res + integer_part //= 1000 + idx += 1 + + words = res.strip() + + if fractional_part > 0: + frac_str = parts[1] + denom = 10 ** len(frac_str) + words += f" and {fractional_part}/{denom}" + + return words + +def number_to_words_ar(number): + return number_to_words_en(number) + +def send_whatsapp_message(phone, message): + try: + import requests + from .models import SystemSetting + + settings = SystemSetting.objects.first() + if not settings or not settings.wablas_enabled: + return False, "WhatsApp gateway is disabled." + + if not settings.wablas_token or not settings.wablas_server_url: + return False, "Wablas configuration is incomplete." + + phone = ''.join(filter(str.isdigit, str(phone))) + server_url = settings.wablas_server_url.rstrip('/') + url = f"{server_url}/api/send-message" + + headers = { + "Authorization": settings.wablas_token, + "Secret": settings.wablas_secret_key + } + + payload = {"phone": phone, "message": message} + + response = requests.post(url, data=payload, headers=headers, timeout=10) + data = response.json() + if response.status_code == 200 and data.get('status') == True: + return True, "Message sent successfully." + else: + return False, data.get('message', 'Unknown error from Wablas.') + except Exception as e: + return False, str(e) + +def send_whatsapp_document(phone, document_url, caption=""): + try: + import requests + from .models import SystemSetting + + settings = SystemSetting.objects.first() + if not settings or not settings.wablas_enabled: + return False, "WhatsApp gateway is disabled." + + if not settings.wablas_token or not settings.wablas_server_url: + return False, "Wablas configuration is incomplete." + + phone = ''.join(filter(str.isdigit, str(phone))) + server_url = settings.wablas_server_url.rstrip('/') + url = f"{server_url}/api/send-document" + + headers = { + "Authorization": settings.wablas_token, + "Secret": settings.wablas_secret_key + } + + payload = { + "phone": phone, + "document": document_url, + "caption": caption + } + + response = requests.post(url, data=payload, headers=headers, timeout=15) + data = response.json() + if response.status_code == 200 and data.get('status') == True: + return True, "Document sent successfully." + else: + return False, data.get('message', 'Unknown error from Wablas.') + except Exception as e: + return False, str(e) \ No newline at end of file diff --git a/core/templates/core/purchase_edit.html b/core/templates/core/purchase_edit.html new file mode 100644 index 0000000..5fd5240 --- /dev/null +++ b/core/templates/core/purchase_edit.html @@ -0,0 +1,299 @@ +{% extends 'base.html' %} +{% load i18n l10n %} + +{% block title %}{% trans "Edit Purchase" %} | {{ site_settings.business_name }}{% endblock %} + +{% block content %} +
+
+ +
+
+
+
+
{% trans "Edit Purchase Invoice" %} #[[ invoiceNumber || '{{ purchase.id }}' ]]
+ + {% trans "Back to List" %} + +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+ +
+
+ +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + +
{% trans "Product" %}{% trans "Cost Price" %}{% trans "Quantity" %}{% trans "Total" %}
+
[[ item.name_en ]]
+
[[ item.sku ]]
+
+ + + + [[ currencySymbol ]][[ (parseFloat(item.cost_price) * parseFloat(item.quantity)).toFixed(decimalPlaces) ]] + +
+ {% trans "Search and add products to this purchase." %} +
+
+
+
+
+ + +
+
+
+
{% trans "Purchase Summary" %}
+ +
+ {% trans "Total Amount" %} + [[ currencySymbol ]][[ grandTotal.toFixed(decimalPlaces) ]] +
+ +
+ +
+

{% trans "Grand Total" %}

+

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

+
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+
+
+ + + +{% localize off %} + +{% endlocalize %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/purchases.html b/core/templates/core/purchases.html index 2dd1e00..f67b2cc 100644 --- a/core/templates/core/purchases.html +++ b/core/templates/core/purchases.html @@ -69,6 +69,9 @@ + + + {% if purchase.balance_due > 0 %}