|
@@ -382,78 +382,124 @@ class Invoice extends Document
|
382
|
382
|
|
383
|
383
|
public function createApprovalTransaction(): void
|
384
|
384
|
{
|
385
|
|
- $total = $this->formatAmountToDefaultCurrency($this->getRawOriginal('total'));
|
386
|
|
-
|
387
|
|
- $transaction = $this->transactions()->create([
|
388
|
|
- 'company_id' => $this->company_id,
|
389
|
|
- 'type' => TransactionType::Journal,
|
390
|
|
- 'posted_at' => $this->date,
|
391
|
|
- 'amount' => $total,
|
392
|
|
- 'description' => 'Invoice Approval for Invoice #' . $this->invoice_number,
|
393
|
|
- ]);
|
394
|
|
-
|
395
|
385
|
$baseDescription = "{$this->client->name}: Invoice #{$this->invoice_number}";
|
396
|
386
|
|
397
|
|
- $transaction->journalEntries()->create([
|
398
|
|
- 'company_id' => $this->company_id,
|
|
387
|
+ $journalEntryData = [];
|
|
388
|
+
|
|
389
|
+ $totalInInvoiceCurrency = $this->getRawOriginal('total');
|
|
390
|
+ $journalEntryData[] = [
|
399
|
391
|
'type' => JournalEntryType::Debit,
|
400
|
392
|
'account_id' => Account::getAccountsReceivableAccount($this->company_id)->id,
|
401
|
|
- 'amount' => $total,
|
|
393
|
+ 'amount_in_invoice_currency' => $totalInInvoiceCurrency,
|
402
|
394
|
'description' => $baseDescription,
|
403
|
|
- ]);
|
|
395
|
+ ];
|
404
|
396
|
|
405
|
|
- $totalLineItemSubtotalCents = $this->convertAmountToDefaultCurrency((int) $this->lineItems()->sum('subtotal'));
|
406
|
|
- $invoiceDiscountTotalCents = $this->convertAmountToDefaultCurrency((int) $this->getRawOriginal('discount_total'));
|
407
|
|
- $remainingDiscountCents = $invoiceDiscountTotalCents;
|
|
397
|
+ $totalLineItemSubtotalInInvoiceCurrency = (int) $this->lineItems()->sum('subtotal');
|
|
398
|
+ $invoiceDiscountTotalInInvoiceCurrency = (int) $this->getRawOriginal('discount_total');
|
|
399
|
+ $remainingDiscountInInvoiceCurrency = $invoiceDiscountTotalInInvoiceCurrency;
|
408
|
400
|
|
409
|
401
|
foreach ($this->lineItems as $index => $lineItem) {
|
410
|
402
|
$lineItemDescription = "{$baseDescription} › {$lineItem->offering->name}";
|
|
403
|
+ $lineItemSubtotalInInvoiceCurrency = $lineItem->getRawOriginal('subtotal');
|
411
|
404
|
|
412
|
|
- $lineItemSubtotal = $this->formatAmountToDefaultCurrency($lineItem->getRawOriginal('subtotal'));
|
413
|
|
-
|
414
|
|
- $transaction->journalEntries()->create([
|
415
|
|
- 'company_id' => $this->company_id,
|
|
405
|
+ $journalEntryData[] = [
|
416
|
406
|
'type' => JournalEntryType::Credit,
|
417
|
407
|
'account_id' => $lineItem->offering->income_account_id,
|
418
|
|
- 'amount' => $lineItemSubtotal,
|
|
408
|
+ 'amount_in_invoice_currency' => $lineItemSubtotalInInvoiceCurrency,
|
419
|
409
|
'description' => $lineItemDescription,
|
420
|
|
- ]);
|
|
410
|
+ ];
|
421
|
411
|
|
422
|
412
|
foreach ($lineItem->adjustments as $adjustment) {
|
423
|
|
- $adjustmentAmount = $this->formatAmountToDefaultCurrency($lineItem->calculateAdjustmentTotalAmount($adjustment));
|
|
413
|
+ $adjustmentAmountInInvoiceCurrency = $lineItem->calculateAdjustmentTotalAmount($adjustment);
|
424
|
414
|
|
425
|
|
- $transaction->journalEntries()->create([
|
426
|
|
- 'company_id' => $this->company_id,
|
|
415
|
+ $journalEntryData[] = [
|
427
|
416
|
'type' => $adjustment->category->isDiscount() ? JournalEntryType::Debit : JournalEntryType::Credit,
|
428
|
417
|
'account_id' => $adjustment->account_id,
|
429
|
|
- 'amount' => $adjustmentAmount,
|
|
418
|
+ 'amount_in_invoice_currency' => $adjustmentAmountInInvoiceCurrency,
|
430
|
419
|
'description' => $lineItemDescription,
|
431
|
|
- ]);
|
|
420
|
+ ];
|
432
|
421
|
}
|
433
|
422
|
|
434
|
|
- if ($this->discount_method->isPerDocument() && $totalLineItemSubtotalCents > 0) {
|
435
|
|
- $lineItemSubtotalCents = $this->convertAmountToDefaultCurrency((int) $lineItem->getRawOriginal('subtotal'));
|
|
423
|
+ // Handle per-document discount allocation
|
|
424
|
+ if ($this->discount_method->isPerDocument() && $totalLineItemSubtotalInInvoiceCurrency > 0) {
|
|
425
|
+ $lineItemSubtotalInInvoiceCurrency = (int) $lineItem->getRawOriginal('subtotal');
|
436
|
426
|
|
437
|
427
|
if ($index === $this->lineItems->count() - 1) {
|
438
|
|
- $lineItemDiscount = $remainingDiscountCents;
|
|
428
|
+ $lineItemDiscountInInvoiceCurrency = $remainingDiscountInInvoiceCurrency;
|
439
|
429
|
} else {
|
440
|
|
- $lineItemDiscount = (int) round(
|
441
|
|
- ($lineItemSubtotalCents / $totalLineItemSubtotalCents) * $invoiceDiscountTotalCents
|
|
430
|
+ $lineItemDiscountInInvoiceCurrency = (int) round(
|
|
431
|
+ ($lineItemSubtotalInInvoiceCurrency / $totalLineItemSubtotalInInvoiceCurrency) * $invoiceDiscountTotalInInvoiceCurrency
|
442
|
432
|
);
|
443
|
|
- $remainingDiscountCents -= $lineItemDiscount;
|
|
433
|
+ $remainingDiscountInInvoiceCurrency -= $lineItemDiscountInInvoiceCurrency;
|
444
|
434
|
}
|
445
|
435
|
|
446
|
|
- if ($lineItemDiscount > 0) {
|
447
|
|
- $transaction->journalEntries()->create([
|
448
|
|
- 'company_id' => $this->company_id,
|
|
436
|
+ if ($lineItemDiscountInInvoiceCurrency > 0) {
|
|
437
|
+ $journalEntryData[] = [
|
449
|
438
|
'type' => JournalEntryType::Debit,
|
450
|
439
|
'account_id' => Account::getSalesDiscountAccount($this->company_id)->id,
|
451
|
|
- 'amount' => $lineItemDiscount,
|
|
440
|
+ 'amount_in_invoice_currency' => $lineItemDiscountInInvoiceCurrency,
|
452
|
441
|
'description' => "{$lineItemDescription} (Proportional Discount)",
|
453
|
|
- ]);
|
|
442
|
+ ];
|
454
|
443
|
}
|
455
|
444
|
}
|
456
|
445
|
}
|
|
446
|
+
|
|
447
|
+ // Convert amounts to default currency
|
|
448
|
+ $totalDebitsInDefaultCurrency = 0;
|
|
449
|
+ $totalCreditsInDefaultCurrency = 0;
|
|
450
|
+
|
|
451
|
+ foreach ($journalEntryData as &$entry) {
|
|
452
|
+ $entry['amount_in_default_currency'] = $this->formatAmountToDefaultCurrency($entry['amount_in_invoice_currency']);
|
|
453
|
+
|
|
454
|
+ if ($entry['type'] === JournalEntryType::Debit) {
|
|
455
|
+ $totalDebitsInDefaultCurrency += $entry['amount_in_default_currency'];
|
|
456
|
+ } else {
|
|
457
|
+ $totalCreditsInDefaultCurrency += $entry['amount_in_default_currency'];
|
|
458
|
+ }
|
|
459
|
+ }
|
|
460
|
+
|
|
461
|
+ unset($entry);
|
|
462
|
+
|
|
463
|
+ // Handle currency conversion imbalance
|
|
464
|
+ $imbalance = $totalDebitsInDefaultCurrency - $totalCreditsInDefaultCurrency;
|
|
465
|
+ if ($imbalance !== 0) {
|
|
466
|
+ $targetType = $imbalance > 0 ? JournalEntryType::Credit : JournalEntryType::Debit;
|
|
467
|
+ $adjustmentAmount = abs($imbalance);
|
|
468
|
+
|
|
469
|
+ // Find last entry of target type and adjust it
|
|
470
|
+ $lastKey = array_key_last(array_filter($journalEntryData, fn ($entry) => $entry['type'] === $targetType, ARRAY_FILTER_USE_BOTH));
|
|
471
|
+ $journalEntryData[$lastKey]['amount_in_default_currency'] += $adjustmentAmount;
|
|
472
|
+
|
|
473
|
+ if ($targetType === JournalEntryType::Debit) {
|
|
474
|
+ $totalDebitsInDefaultCurrency += $adjustmentAmount;
|
|
475
|
+ } else {
|
|
476
|
+ $totalCreditsInDefaultCurrency += $adjustmentAmount;
|
|
477
|
+ }
|
|
478
|
+ }
|
|
479
|
+
|
|
480
|
+ if ($totalDebitsInDefaultCurrency !== $totalCreditsInDefaultCurrency) {
|
|
481
|
+ throw new \Exception('Journal entries do not balance. Debits: ' . $totalDebitsInDefaultCurrency . ', Credits: ' . $totalCreditsInDefaultCurrency);
|
|
482
|
+ }
|
|
483
|
+
|
|
484
|
+ // Create the transaction using the sum of debits
|
|
485
|
+ $transaction = $this->transactions()->create([
|
|
486
|
+ 'company_id' => $this->company_id,
|
|
487
|
+ 'type' => TransactionType::Journal,
|
|
488
|
+ 'posted_at' => $this->date,
|
|
489
|
+ 'amount' => $totalDebitsInDefaultCurrency,
|
|
490
|
+ 'description' => 'Invoice Approval for Invoice #' . $this->invoice_number,
|
|
491
|
+ ]);
|
|
492
|
+
|
|
493
|
+ // Create all journal entries
|
|
494
|
+ foreach ($journalEntryData as $entry) {
|
|
495
|
+ $transaction->journalEntries()->create([
|
|
496
|
+ 'company_id' => $this->company_id,
|
|
497
|
+ 'type' => $entry['type'],
|
|
498
|
+ 'account_id' => $entry['account_id'],
|
|
499
|
+ 'amount' => $entry['amount_in_default_currency'],
|
|
500
|
+ 'description' => $entry['description'],
|
|
501
|
+ ]);
|
|
502
|
+ }
|
457
|
503
|
}
|
458
|
504
|
|
459
|
505
|
public function updateApprovalTransaction(): void
|