adding vat
This commit is contained in:
parent
a123d9bb27
commit
9dfa03d69c
58
apply_patch.py
Normal file
58
apply_patch.py
Normal file
@ -0,0 +1,58 @@
|
||||
|
||||
import re
|
||||
|
||||
with open('core/views.py', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
with open('core/patch_views_vat.py', 'r') as f:
|
||||
new_func = f.read()
|
||||
|
||||
# Regex to find the function definition
|
||||
# It starts with @csrf_exempt\ndef create_sale_api(request):
|
||||
# And ends before the next function definition (which likely starts with @ or def)
|
||||
pattern = r"@csrf_exempt\s+def create_sale_api(request):.*?return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)"
|
||||
|
||||
# Note: The pattern needs to match the indentation and multiline content.
|
||||
# Since regex for code blocks is tricky, I will use a simpler approach:
|
||||
# 1. Read the file lines.
|
||||
# 2. Find start line of create_sale_api.
|
||||
# 3. Find the end line (start of next function or end of file).
|
||||
# 4. Replace lines.
|
||||
|
||||
lines = content.splitlines()
|
||||
start_index = -1
|
||||
end_index = -1
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip() == "def create_sale_api(request):":
|
||||
# Check if previous line is decorator
|
||||
if i > 0 and lines[i-1].strip() == "@csrf_exempt":
|
||||
start_index = i - 1
|
||||
else:
|
||||
start_index = i
|
||||
break
|
||||
|
||||
if start_index != -1:
|
||||
# Find the next function or end
|
||||
# We look for next line starting with 'def ' or '@' at top level
|
||||
for i in range(start_index + 1, len(lines)):
|
||||
if lines[i].startswith("def ") or lines[i].startswith("@"):
|
||||
end_index = i
|
||||
break
|
||||
if end_index == -1:
|
||||
end_index = len(lines)
|
||||
|
||||
# Replace
|
||||
new_lines = new_func.splitlines()
|
||||
# Ensure new lines have correct indentation if needed (but views.py is top level mostly)
|
||||
|
||||
# We need to preserve the imports and structure.
|
||||
# The new_func is complete.
|
||||
|
||||
final_lines = lines[:start_index] + new_lines + lines[end_index:]
|
||||
|
||||
with open('core/views.py', 'w') as f:
|
||||
f.write('\n'.join(final_lines))
|
||||
print("Successfully patched create_sale_api")
|
||||
else:
|
||||
print("Could not find create_sale_api function")
|
||||
Binary file not shown.
Binary file not shown.
23
core/migrations/0025_sale_subtotal_sale_vat_amount.py
Normal file
23
core/migrations/0025_sale_subtotal_sale_vat_amount.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-05 12:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0024_device'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='subtotal',
|
||||
field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Subtotal'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sale',
|
||||
name='vat_amount',
|
||||
field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='VAT Amount'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -147,6 +147,8 @@ 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)
|
||||
subtotal = models.DecimalField(_("Subtotal"), max_digits=15, decimal_places=3, default=0)
|
||||
vat_amount = models.DecimalField(_("VAT Amount"), max_digits=15, decimal_places=3, default=0)
|
||||
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)
|
||||
@ -420,4 +422,4 @@ def create_user_profile(sender, instance, created, **kwargs):
|
||||
def save_user_profile(sender, instance, **kwargs):
|
||||
if not hasattr(instance, 'profile'):
|
||||
UserProfile.objects.create(user=instance)
|
||||
instance.profile.save()
|
||||
instance.profile.save()
|
||||
161
core/patch_views_vat.py
Normal file
161
core/patch_views_vat.py
Normal file
@ -0,0 +1,161 @@
|
||||
@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', [])
|
||||
|
||||
# Retrieve amounts
|
||||
subtotal = data.get('subtotal', 0)
|
||||
vat_amount = data.get('vat_amount', 0)
|
||||
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')
|
||||
payment_method_id = data.get('payment_method_id')
|
||||
due_date = data.get('due_date')
|
||||
notes = data.get('notes', '')
|
||||
|
||||
# Loyalty data
|
||||
points_to_redeem = data.get('loyalty_points_redeemed', 0)
|
||||
|
||||
customer = None
|
||||
if customer_id:
|
||||
customer = Customer.objects.get(id=customer_id)
|
||||
|
||||
if not customer and payment_type != 'cash':
|
||||
return JsonResponse({'success': False, 'error': _('Credit or Partial payments are not allowed for Guest customers.')}, status=400)
|
||||
|
||||
settings = SystemSetting.objects.first()
|
||||
if not settings:
|
||||
settings = SystemSetting.objects.create()
|
||||
|
||||
loyalty_discount = 0
|
||||
if settings.loyalty_enabled and customer and points_to_redeem > 0:
|
||||
if customer.loyalty_points >= points_to_redeem:
|
||||
loyalty_discount = float(points_to_redeem) * float(settings.currency_per_point)
|
||||
|
||||
sale = Sale.objects.create(
|
||||
customer=customer,
|
||||
invoice_number=invoice_number,
|
||||
subtotal=subtotal,
|
||||
vat_amount=vat_amount,
|
||||
total_amount=total_amount,
|
||||
paid_amount=paid_amount,
|
||||
balance_due=float(total_amount) - float(paid_amount),
|
||||
discount=discount,
|
||||
loyalty_points_redeemed=points_to_redeem,
|
||||
loyalty_discount_amount=loyalty_discount,
|
||||
payment_type=payment_type,
|
||||
due_date=due_date if due_date else None,
|
||||
notes=notes,
|
||||
created_by=request.user
|
||||
)
|
||||
|
||||
# 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:
|
||||
pm = None
|
||||
if payment_method_id:
|
||||
pm = PaymentMethod.objects.filter(id=payment_method_id).first()
|
||||
|
||||
SalePayment.objects.create(
|
||||
sale=sale,
|
||||
amount=paid_amount,
|
||||
payment_method=pm,
|
||||
payment_method_name=pm.name_en if pm else payment_type.capitalize(),
|
||||
notes="Initial payment",
|
||||
created_by=request.user
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
# Handle Loyalty Points
|
||||
if settings.loyalty_enabled and customer:
|
||||
# Earn Points
|
||||
points_earned = float(total_amount) * float(settings.points_per_currency)
|
||||
if customer.loyalty_tier:
|
||||
points_earned *= float(customer.loyalty_tier.point_multiplier)
|
||||
|
||||
if points_earned > 0:
|
||||
customer.loyalty_points += decimal.Decimal(str(points_earned))
|
||||
LoyaltyTransaction.objects.create(
|
||||
customer=customer,
|
||||
sale=sale,
|
||||
transaction_type='earned',
|
||||
points=points_earned,
|
||||
notes=f"Points earned from Sale #{sale.id}"
|
||||
)
|
||||
|
||||
# Redeem Points
|
||||
if points_to_redeem > 0:
|
||||
customer.loyalty_points -= decimal.Decimal(str(points_to_redeem))
|
||||
LoyaltyTransaction.objects.create(
|
||||
customer=customer,
|
||||
sale=sale,
|
||||
transaction_type='redeemed',
|
||||
points=-points_to_redeem,
|
||||
notes=f"Points redeemed for Sale #{sale.id}"
|
||||
)
|
||||
|
||||
customer.update_tier()
|
||||
customer.save()
|
||||
|
||||
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"),
|
||||
'subtotal': float(sale.subtotal),
|
||||
'vat_amount': float(sale.vat_amount),
|
||||
'total': float(sale.total_amount),
|
||||
'discount': float(sale.discount),
|
||||
'paid': float(sale.paid_amount),
|
||||
'balance': float(sale.balance_due),
|
||||
'customer_name': sale.customer.name if sale.customer else 'Guest',
|
||||
'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)
|
||||
@ -60,6 +60,7 @@
|
||||
<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">{% trans "VAT %" %}</th>
|
||||
<th class="text-center" style="width: 15%;">{% trans "Quantity" %}</th>
|
||||
<th class="text-end">{% trans "Total" %}</th>
|
||||
<th></th>
|
||||
@ -72,10 +73,13 @@
|
||||
<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">
|
||||
<input type="number" step="0.001" class="form-control form-control-sm text-center border-0 border-bottom rounded-0" v-model="item.price">
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm text-center border-0 border-bottom rounded-0" step="0.01" v-model="item.quantity" @input="calculateTotal">
|
||||
<input type="number" step="0.01" class="form-control form-control-sm text-center border-0 border-bottom rounded-0" v-model="item.vat_rate" readonly disabled>
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm text-center border-0 border-bottom rounded-0" step="0.01" v-model="item.quantity">
|
||||
</td>
|
||||
<td class="text-end fw-bold">[[ currencySymbol ]][[ (item.price * item.quantity).toFixed(decimalPlaces) ]]</td>
|
||||
<td class="text-end">
|
||||
@ -83,7 +87,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="cart.length === 0">
|
||||
<td colspan="5" class="text-center py-5 text-muted">
|
||||
<td colspan="6" class="text-center py-5 text-muted">
|
||||
{% trans "Search and add products to this invoice." %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -105,10 +109,15 @@
|
||||
<span class="fw-bold">[[ currencySymbol ]][[ subtotal.toFixed(decimalPlaces) ]]</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">{% trans "VAT" %}</span>
|
||||
<span class="fw-bold text-danger">[[ currencySymbol ]][[ totalVat.toFixed(decimalPlaces) ]]</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">
|
||||
<input type="number" class="form-control text-end border-0 border-bottom rounded-0" v-model="discount">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -184,6 +193,7 @@
|
||||
name_ar: "{{ p.name_ar|escapejs }}",
|
||||
sku: "{{ p.sku|escapejs }}",
|
||||
price: {{ p.price|default:0 }},
|
||||
vat: {{ p.vat|default:site_settings.tax_rate|default:0 }},
|
||||
stock: {{ p.stock_quantity|default:0 }}
|
||||
},
|
||||
{% endfor %}
|
||||
@ -208,8 +218,11 @@
|
||||
subtotal() {
|
||||
return this.cart.reduce((total, item) => total + (item.price * item.quantity), 0);
|
||||
},
|
||||
totalVat() {
|
||||
return this.cart.reduce((total, item) => total + (item.price * item.quantity * (item.vat_rate / 100)), 0);
|
||||
},
|
||||
grandTotal() {
|
||||
return Math.max(0, this.subtotal - this.discount);
|
||||
return Math.max(0, this.subtotal + this.totalVat - this.discount);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -235,6 +248,7 @@
|
||||
name_en: product.name_en,
|
||||
sku: product.sku,
|
||||
price: product.price,
|
||||
vat_rate: product.vat,
|
||||
quantity: 1
|
||||
});
|
||||
}
|
||||
@ -264,6 +278,8 @@
|
||||
price: item.price,
|
||||
line_total: item.price * item.quantity
|
||||
})),
|
||||
subtotal: this.subtotal,
|
||||
vat_amount: this.totalVat,
|
||||
total_amount: this.grandTotal,
|
||||
discount: this.discount,
|
||||
paid_amount: actualPaidAmount,
|
||||
@ -300,4 +316,4 @@
|
||||
}).mount('#saleApp');
|
||||
</script>
|
||||
{% endlocalize %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -138,7 +138,15 @@
|
||||
<div>{% trans "Subtotal" %}</div>
|
||||
<div class="small fw-normal">المجموع الفرعي</div>
|
||||
</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>
|
||||
<td class="text-end pe-4 py-3 fw-bold border-top">{{ settings.currency_symbol }}{{ sale.subtotal|floatformat:3 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="border-0"></td>
|
||||
<td class="text-center py-2 fw-bold text-muted">
|
||||
<div>{% trans "VAT" %}</div>
|
||||
<div class="small fw-normal">الضريبة</div>
|
||||
</td>
|
||||
<td class="text-end pe-4 py-2 fw-bold text-muted">{{ settings.currency_symbol }}{{ sale.vat_amount|floatformat:3 }}</td>
|
||||
</tr>
|
||||
{% if sale.discount > 0 %}
|
||||
<tr class="text-muted">
|
||||
|
||||
@ -146,7 +146,7 @@
|
||||
<div class="row row-cols-2 row-cols-sm-3 row-cols-md-4 row-cols-lg-5 row-cols-xl-6 g-2" id="productGrid">
|
||||
{% for product in products %}
|
||||
<div class="col product-item" data-category="{{ product.category.id }}" data-name-en="{{ product.name_en|lower }}" data-name-ar="{{ product.name_ar }}">
|
||||
<div class="card h-100 shadow-sm product-card p-1" onclick="addToCart({{ product.id|unlocalize }}, '{{ product.name_en|escapejs }}', '{{ product.name_ar|escapejs }}', {{ product.price|unlocalize }})">
|
||||
<div class="card h-100 shadow-sm product-card p-1" onclick="addToCart({{ product.id|unlocalize }}, '{{ product.name_en|escapejs }}', '{{ product.name_ar|escapejs }}', {{ product.price|unlocalize }}, {{ product.vat|default:site_settings.tax_rate|default:0|unlocalize }})">
|
||||
{% if product.image %}
|
||||
<img src="{{ product.image.url }}" class="card-img-top rounded-3" alt="{{ product.name_en }}" style="height: 80px; object-fit: cover;">
|
||||
{% else %}
|
||||
@ -224,6 +224,10 @@
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="small">{% trans "Subtotal" %}</span>
|
||||
<span id="subtotalAmount" class="small">{{ site_settings.currency_symbol }}0.000</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="small">{% trans "VAT" %}</span>
|
||||
<span id="taxAmount" class="small">{{ site_settings.currency_symbol }}0.000</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="small">{% trans "Discount" %}</span>
|
||||
@ -443,6 +447,9 @@
|
||||
<span>Date: <span id="inv-date"></span></span>
|
||||
<span class="rtl">التاريخ: <span id="inv-date-ar"></span></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Customer: <span id="inv-customer"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
<table class="invoice-table">
|
||||
<thead>
|
||||
@ -457,8 +464,20 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="invoice-total">
|
||||
<div class="d-flex justify-content-between fw-bold">
|
||||
<span>TOTAL / المجموع</span>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Subtotal / المجموع الفرعي</span>
|
||||
<span id="inv-subtotal"></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>VAT / الضريبة</span>
|
||||
<span id="inv-vat"></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Discount / الخصم</span>
|
||||
<span id="inv-discount"></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between fw-bold" style="border-top: 1px solid #000; margin-top: 2px; padding-top: 2px;">
|
||||
<span>TOTAL / المجموع النهائي</span>
|
||||
<span id="inv-total"></span>
|
||||
</div>
|
||||
</div>
|
||||
@ -514,7 +533,7 @@
|
||||
document.body.classList.toggle('cart-open');
|
||||
}
|
||||
|
||||
function addToCart(id, nameEn, nameAr, price) {
|
||||
function addToCart(id, nameEn, nameAr, price, vatRate) {
|
||||
const existing = cart.find(item => item.id === id);
|
||||
if (existing) {
|
||||
existing.quantity += 1;
|
||||
@ -525,6 +544,7 @@
|
||||
name_en: nameEn,
|
||||
name_ar: nameAr,
|
||||
price,
|
||||
vat_rate: vatRate,
|
||||
quantity: 1,
|
||||
line_total: price
|
||||
});
|
||||
@ -556,10 +576,12 @@
|
||||
|
||||
function updateTotals() {
|
||||
const subtotal = cart.reduce((acc, item) => acc + item.line_total, 0);
|
||||
const totalVat = cart.reduce((acc, item) => acc + (item.line_total * (item.vat_rate / 100)), 0);
|
||||
const discount = parseFloat(document.getElementById('discountInput').value) || 0;
|
||||
const total = Math.max(0, subtotal - discount);
|
||||
const total = Math.max(0, subtotal + totalVat - discount);
|
||||
|
||||
document.getElementById('subtotalAmount').innerText = `${currency} ${formatAmount(subtotal)}`;
|
||||
document.getElementById('taxAmount').innerText = `${currency} ${formatAmount(totalVat)}`;
|
||||
document.getElementById('totalAmount').innerText = `${currency} ${formatAmount(total)}`;
|
||||
}
|
||||
|
||||
@ -612,8 +634,9 @@
|
||||
if (cart.length === 0) return;
|
||||
|
||||
const subtotal = cart.reduce((acc, item) => acc + item.line_total, 0);
|
||||
const totalVat = cart.reduce((acc, item) => acc + (item.line_total * (item.vat_rate / 100)), 0);
|
||||
const discount = parseFloat(document.getElementById('discountInput').value) || 0;
|
||||
const totalAmount = Math.max(0, subtotal - discount);
|
||||
const totalAmount = Math.max(0, subtotal + totalVat - discount);
|
||||
|
||||
document.getElementById('modalTotalAmount').innerText = `${currency} ${formatAmount(totalAmount)}`;
|
||||
document.getElementById('cashReceivedInput').value = '';
|
||||
@ -686,8 +709,9 @@
|
||||
|
||||
// Update Total Payable
|
||||
const subtotal = cart.reduce((acc, item) => acc + item.line_total, 0);
|
||||
const totalVat = cart.reduce((acc, item) => acc + (item.line_total * (item.vat_rate / 100)), 0);
|
||||
const discount = parseFloat(document.getElementById('discountInput').value) || 0;
|
||||
const totalAmount = Math.max(0, subtotal - discount - value);
|
||||
const totalAmount = Math.max(0, subtotal + totalVat - discount - value);
|
||||
|
||||
document.getElementById('modalTotalAmount').innerText = `${currency} ${formatAmount(totalAmount)}`;
|
||||
calculateBalance();
|
||||
@ -714,11 +738,12 @@
|
||||
|
||||
function setExactAmount() {
|
||||
const subtotal = cart.reduce((acc, item) => acc + item.line_total, 0);
|
||||
const totalVat = cart.reduce((acc, item) => acc + (item.line_total * (item.vat_rate / 100)), 0);
|
||||
const discount = parseFloat(document.getElementById('discountInput').value) || 0;
|
||||
const loyaltyRedeem = parseFloat(document.getElementById('loyaltyRedeemInput').value) || 0;
|
||||
const loyaltyDiscount = customerLoyalty ? loyaltyRedeem * customerLoyalty.currency_per_point : 0;
|
||||
|
||||
const totalAmount = Math.max(0, subtotal - discount - loyaltyDiscount);
|
||||
const totalAmount = Math.max(0, subtotal + totalVat - discount - loyaltyDiscount);
|
||||
|
||||
document.getElementById('cashReceivedInput').value = totalAmount.toFixed(decimalPlaces);
|
||||
calculateBalance();
|
||||
@ -731,11 +756,12 @@
|
||||
|
||||
function calculateBalance() {
|
||||
const subtotal = cart.reduce((acc, item) => acc + item.line_total, 0);
|
||||
const totalVat = cart.reduce((acc, item) => acc + (item.line_total * (item.vat_rate / 100)), 0);
|
||||
const discount = parseFloat(document.getElementById('discountInput').value) || 0;
|
||||
const loyaltyRedeem = parseFloat(document.getElementById('loyaltyRedeemInput').value) || 0;
|
||||
const loyaltyDiscount = customerLoyalty ? loyaltyRedeem * customerLoyalty.currency_per_point : 0;
|
||||
|
||||
const totalAmount = Math.max(0, subtotal - discount - loyaltyDiscount);
|
||||
const totalAmount = Math.max(0, subtotal + totalVat - discount - loyaltyDiscount);
|
||||
|
||||
const received = parseFloat(document.getElementById('cashReceivedInput').value) || 0;
|
||||
const balance = Math.max(0, received - totalAmount);
|
||||
@ -751,16 +777,19 @@
|
||||
confirmBtn.innerText = '{% trans "Processing..." %}';
|
||||
|
||||
const subtotal = cart.reduce((acc, item) => acc + item.line_total, 0);
|
||||
const totalVat = cart.reduce((acc, item) => acc + (item.line_total * (item.vat_rate / 100)), 0);
|
||||
const discount = parseFloat(document.getElementById('discountInput').value) || 0;
|
||||
const loyaltyRedeem = parseFloat(document.getElementById('loyaltyRedeemInput').value) || 0;
|
||||
const loyaltyDiscount = customerLoyalty ? loyaltyRedeem * customerLoyalty.currency_per_point : 0;
|
||||
|
||||
const totalAmount = Math.max(0, subtotal - discount - loyaltyDiscount);
|
||||
const totalAmount = Math.max(0, subtotal + totalVat - discount - loyaltyDiscount);
|
||||
|
||||
const data = {
|
||||
customer_id: document.getElementById('customerSelect').value,
|
||||
payment_method_id: currentPaymentType === 'cash' ? selectedPaymentMethodId : null,
|
||||
items: cart,
|
||||
subtotal: subtotal,
|
||||
vat_amount: totalVat,
|
||||
total_amount: totalAmount,
|
||||
paid_amount: currentPaymentType === 'cash' ? totalAmount : 0,
|
||||
discount: discount,
|
||||
@ -836,7 +865,8 @@
|
||||
document.getElementById('inv-id-ar').innerText = data.sale.id;
|
||||
document.getElementById('inv-date').innerText = data.sale.created_at;
|
||||
document.getElementById('inv-date-ar').innerText = data.sale.created_at;
|
||||
|
||||
document.getElementById('inv-customer').innerText = data.sale.customer_name || 'Guest';
|
||||
|
||||
let itemsHtml = '';
|
||||
data.sale.items.forEach(item => {
|
||||
itemsHtml += `
|
||||
@ -851,6 +881,10 @@
|
||||
`;
|
||||
});
|
||||
document.getElementById('inv-items').innerHTML = itemsHtml;
|
||||
|
||||
document.getElementById('inv-subtotal').innerText = data.business.currency + ' ' + formatAmount(data.sale.subtotal);
|
||||
document.getElementById('inv-vat').innerText = data.business.currency + ' ' + formatAmount(data.sale.vat_amount);
|
||||
document.getElementById('inv-discount').innerText = data.business.currency + ' ' + formatAmount(data.sale.discount);
|
||||
document.getElementById('inv-total').innerText = data.business.currency + ' ' + formatAmount(data.sale.total);
|
||||
}
|
||||
|
||||
@ -906,8 +940,9 @@
|
||||
if (cart.length === 0) return;
|
||||
|
||||
const subtotal = cart.reduce((acc, item) => acc + item.line_total, 0);
|
||||
const totalVat = cart.reduce((acc, item) => acc + (item.line_total * (item.vat_rate / 100)), 0);
|
||||
const discount = parseFloat(document.getElementById('discountInput').value) || 0;
|
||||
const totalAmount = Math.max(0, subtotal - discount);
|
||||
const totalAmount = Math.max(0, subtotal + totalVat - discount);
|
||||
|
||||
const data = {
|
||||
customer_id: document.getElementById('customerSelect').value,
|
||||
|
||||
@ -426,7 +426,12 @@ def create_sale_api(request):
|
||||
customer_id = data.get('customer_id')
|
||||
invoice_number = data.get('invoice_number', '')
|
||||
items = data.get('items', [])
|
||||
|
||||
# Retrieve amounts
|
||||
subtotal = data.get('subtotal', 0)
|
||||
vat_amount = data.get('vat_amount', 0)
|
||||
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')
|
||||
@ -456,6 +461,8 @@ def create_sale_api(request):
|
||||
sale = Sale.objects.create(
|
||||
customer=customer,
|
||||
invoice_number=invoice_number,
|
||||
subtotal=subtotal,
|
||||
vat_amount=vat_amount,
|
||||
total_amount=total_amount,
|
||||
paid_amount=paid_amount,
|
||||
balance_due=float(total_amount) - float(paid_amount),
|
||||
@ -552,9 +559,13 @@ def create_sale_api(request):
|
||||
'id': sale.id,
|
||||
'invoice_number': sale.invoice_number,
|
||||
'created_at': sale.created_at.strftime("%Y-%m-%d %H:%M"),
|
||||
'subtotal': float(sale.subtotal),
|
||||
'vat_amount': float(sale.vat_amount),
|
||||
'total': float(sale.total_amount),
|
||||
'discount': float(sale.discount),
|
||||
'paid': float(sale.paid_amount),
|
||||
'balance': float(sale.balance_due),
|
||||
'customer_name': sale.customer.name if sale.customer else 'Guest',
|
||||
'items': [
|
||||
{
|
||||
'name_en': si.product.name_en,
|
||||
@ -568,10 +579,6 @@ def create_sale_api(request):
|
||||
})
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
||||
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
|
||||
|
||||
@csrf_exempt
|
||||
@login_required
|
||||
def send_invoice_whatsapp(request):
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user