Andrew Wallo 4 months ago
parent
commit
59f1405fae

+ 1
- 1
app/Filament/Company/Resources/Purchases/BillResource.php View File

261
                                                 return;
261
                                                 return;
262
                                             }
262
                                             }
263
 
263
 
264
-                                            $unitPrice = CurrencyConverter::convertToFloat($offeringRecord->price, $get('../../currency_code') ?? CurrencyAccessor::getDefaultCurrency());
264
+                                            $unitPrice = CurrencyConverter::convertToFloat($offeringRecord->price, CurrencyAccessor::getDefaultCurrency());
265
 
265
 
266
                                             $set('description', $offeringRecord->description);
266
                                             $set('description', $offeringRecord->description);
267
                                             $set('unit_price', $unitPrice);
267
                                             $set('unit_price', $unitPrice);

+ 1
- 1
app/Filament/Company/Resources/Sales/EstimateResource.php View File

259
                                                 return;
259
                                                 return;
260
                                             }
260
                                             }
261
 
261
 
262
-                                            $unitPrice = CurrencyConverter::convertToFloat($offeringRecord->price, $get('../../currency_code') ?? CurrencyAccessor::getDefaultCurrency());
262
+                                            $unitPrice = CurrencyConverter::convertToFloat($offeringRecord->price, CurrencyAccessor::getDefaultCurrency());
263
 
263
 
264
                                             $set('description', $offeringRecord->description);
264
                                             $set('description', $offeringRecord->description);
265
                                             $set('unit_price', $unitPrice);
265
                                             $set('unit_price', $unitPrice);

+ 1
- 1
app/Filament/Company/Resources/Sales/RecurringInvoiceResource.php View File

185
                                                 return;
185
                                                 return;
186
                                             }
186
                                             }
187
 
187
 
188
-                                            $unitPrice = CurrencyConverter::convertToFloat($offeringRecord->price, $get('../../currency_code') ?? CurrencyAccessor::getDefaultCurrency());
188
+                                            $unitPrice = CurrencyConverter::convertToFloat($offeringRecord->price, CurrencyAccessor::getDefaultCurrency());
189
 
189
 
190
                                             $set('description', $offeringRecord->description);
190
                                             $set('description', $offeringRecord->description);
191
                                             $set('unit_price', $unitPrice);
191
                                             $set('unit_price', $unitPrice);

+ 85
- 39
app/Models/Accounting/Invoice.php View File

382
 
382
 
383
     public function createApprovalTransaction(): void
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
         $baseDescription = "{$this->client->name}: Invoice #{$this->invoice_number}";
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
             'type' => JournalEntryType::Debit,
391
             'type' => JournalEntryType::Debit,
400
             'account_id' => Account::getAccountsReceivableAccount($this->company_id)->id,
392
             'account_id' => Account::getAccountsReceivableAccount($this->company_id)->id,
401
-            'amount' => $total,
393
+            'amount_in_invoice_currency' => $totalInInvoiceCurrency,
402
             'description' => $baseDescription,
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
         foreach ($this->lineItems as $index => $lineItem) {
401
         foreach ($this->lineItems as $index => $lineItem) {
410
             $lineItemDescription = "{$baseDescription} › {$lineItem->offering->name}";
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
                 'type' => JournalEntryType::Credit,
406
                 'type' => JournalEntryType::Credit,
417
                 'account_id' => $lineItem->offering->income_account_id,
407
                 'account_id' => $lineItem->offering->income_account_id,
418
-                'amount' => $lineItemSubtotal,
408
+                'amount_in_invoice_currency' => $lineItemSubtotalInInvoiceCurrency,
419
                 'description' => $lineItemDescription,
409
                 'description' => $lineItemDescription,
420
-            ]);
410
+            ];
421
 
411
 
422
             foreach ($lineItem->adjustments as $adjustment) {
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
                     'type' => $adjustment->category->isDiscount() ? JournalEntryType::Debit : JournalEntryType::Credit,
416
                     'type' => $adjustment->category->isDiscount() ? JournalEntryType::Debit : JournalEntryType::Credit,
428
                     'account_id' => $adjustment->account_id,
417
                     'account_id' => $adjustment->account_id,
429
-                    'amount' => $adjustmentAmount,
418
+                    'amount_in_invoice_currency' => $adjustmentAmountInInvoiceCurrency,
430
                     'description' => $lineItemDescription,
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
                 if ($index === $this->lineItems->count() - 1) {
427
                 if ($index === $this->lineItems->count() - 1) {
438
-                    $lineItemDiscount = $remainingDiscountCents;
428
+                    $lineItemDiscountInInvoiceCurrency = $remainingDiscountInInvoiceCurrency;
439
                 } else {
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
                         'type' => JournalEntryType::Debit,
438
                         'type' => JournalEntryType::Debit,
450
                         'account_id' => Account::getSalesDiscountAccount($this->company_id)->id,
439
                         'account_id' => Account::getSalesDiscountAccount($this->company_id)->id,
451
-                        'amount' => $lineItemDiscount,
440
+                        'amount_in_invoice_currency' => $lineItemDiscountInInvoiceCurrency,
452
                         'description' => "{$lineItemDescription} (Proportional Discount)",
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
     public function updateApprovalTransaction(): void
505
     public function updateApprovalTransaction(): void

+ 28
- 28
composer.lock View File

6952
         },
6952
         },
6953
         {
6953
         {
6954
             "name": "symfony/deprecation-contracts",
6954
             "name": "symfony/deprecation-contracts",
6955
-            "version": "v3.5.1",
6955
+            "version": "v3.6.0",
6956
             "source": {
6956
             "source": {
6957
                 "type": "git",
6957
                 "type": "git",
6958
                 "url": "https://github.com/symfony/deprecation-contracts.git",
6958
                 "url": "https://github.com/symfony/deprecation-contracts.git",
6959
-                "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
6959
+                "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
6960
             },
6960
             },
6961
             "dist": {
6961
             "dist": {
6962
                 "type": "zip",
6962
                 "type": "zip",
6963
-                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
6964
-                "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
6963
+                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
6964
+                "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
6965
                 "shasum": ""
6965
                 "shasum": ""
6966
             },
6966
             },
6967
             "require": {
6967
             "require": {
6974
                     "name": "symfony/contracts"
6974
                     "name": "symfony/contracts"
6975
                 },
6975
                 },
6976
                 "branch-alias": {
6976
                 "branch-alias": {
6977
-                    "dev-main": "3.5-dev"
6977
+                    "dev-main": "3.6-dev"
6978
                 }
6978
                 }
6979
             },
6979
             },
6980
             "autoload": {
6980
             "autoload": {
6999
             "description": "A generic function and convention to trigger deprecation notices",
6999
             "description": "A generic function and convention to trigger deprecation notices",
7000
             "homepage": "https://symfony.com",
7000
             "homepage": "https://symfony.com",
7001
             "support": {
7001
             "support": {
7002
-                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
7002
+                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
7003
             },
7003
             },
7004
             "funding": [
7004
             "funding": [
7005
                 {
7005
                 {
7015
                     "type": "tidelift"
7015
                     "type": "tidelift"
7016
                 }
7016
                 }
7017
             ],
7017
             ],
7018
-            "time": "2024-09-25T14:20:29+00:00"
7018
+            "time": "2024-09-25T14:21:43+00:00"
7019
         },
7019
         },
7020
         {
7020
         {
7021
             "name": "symfony/error-handler",
7021
             "name": "symfony/error-handler",
7174
         },
7174
         },
7175
         {
7175
         {
7176
             "name": "symfony/event-dispatcher-contracts",
7176
             "name": "symfony/event-dispatcher-contracts",
7177
-            "version": "v3.5.1",
7177
+            "version": "v3.6.0",
7178
             "source": {
7178
             "source": {
7179
                 "type": "git",
7179
                 "type": "git",
7180
                 "url": "https://github.com/symfony/event-dispatcher-contracts.git",
7180
                 "url": "https://github.com/symfony/event-dispatcher-contracts.git",
7181
-                "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f"
7181
+                "reference": "59eb412e93815df44f05f342958efa9f46b1e586"
7182
             },
7182
             },
7183
             "dist": {
7183
             "dist": {
7184
                 "type": "zip",
7184
                 "type": "zip",
7185
-                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f",
7186
-                "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f",
7185
+                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586",
7186
+                "reference": "59eb412e93815df44f05f342958efa9f46b1e586",
7187
                 "shasum": ""
7187
                 "shasum": ""
7188
             },
7188
             },
7189
             "require": {
7189
             "require": {
7197
                     "name": "symfony/contracts"
7197
                     "name": "symfony/contracts"
7198
                 },
7198
                 },
7199
                 "branch-alias": {
7199
                 "branch-alias": {
7200
-                    "dev-main": "3.5-dev"
7200
+                    "dev-main": "3.6-dev"
7201
                 }
7201
                 }
7202
             },
7202
             },
7203
             "autoload": {
7203
             "autoload": {
7230
                 "standards"
7230
                 "standards"
7231
             ],
7231
             ],
7232
             "support": {
7232
             "support": {
7233
-                "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1"
7233
+                "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0"
7234
             },
7234
             },
7235
             "funding": [
7235
             "funding": [
7236
                 {
7236
                 {
7246
                     "type": "tidelift"
7246
                     "type": "tidelift"
7247
                 }
7247
                 }
7248
             ],
7248
             ],
7249
-            "time": "2024-09-25T14:20:29+00:00"
7249
+            "time": "2024-09-25T14:21:43+00:00"
7250
         },
7250
         },
7251
         {
7251
         {
7252
             "name": "symfony/finder",
7252
             "name": "symfony/finder",
8601
         },
8601
         },
8602
         {
8602
         {
8603
             "name": "symfony/service-contracts",
8603
             "name": "symfony/service-contracts",
8604
-            "version": "v3.5.1",
8604
+            "version": "v3.6.0",
8605
             "source": {
8605
             "source": {
8606
                 "type": "git",
8606
                 "type": "git",
8607
                 "url": "https://github.com/symfony/service-contracts.git",
8607
                 "url": "https://github.com/symfony/service-contracts.git",
8608
-                "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
8608
+                "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
8609
             },
8609
             },
8610
             "dist": {
8610
             "dist": {
8611
                 "type": "zip",
8611
                 "type": "zip",
8612
-                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
8613
-                "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
8612
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
8613
+                "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
8614
                 "shasum": ""
8614
                 "shasum": ""
8615
             },
8615
             },
8616
             "require": {
8616
             "require": {
8628
                     "name": "symfony/contracts"
8628
                     "name": "symfony/contracts"
8629
                 },
8629
                 },
8630
                 "branch-alias": {
8630
                 "branch-alias": {
8631
-                    "dev-main": "3.5-dev"
8631
+                    "dev-main": "3.6-dev"
8632
                 }
8632
                 }
8633
             },
8633
             },
8634
             "autoload": {
8634
             "autoload": {
8664
                 "standards"
8664
                 "standards"
8665
             ],
8665
             ],
8666
             "support": {
8666
             "support": {
8667
-                "source": "https://github.com/symfony/service-contracts/tree/v3.5.1"
8667
+                "source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
8668
             },
8668
             },
8669
             "funding": [
8669
             "funding": [
8670
                 {
8670
                 {
8680
                     "type": "tidelift"
8680
                     "type": "tidelift"
8681
                 }
8681
                 }
8682
             ],
8682
             ],
8683
-            "time": "2024-09-25T14:20:29+00:00"
8683
+            "time": "2025-04-25T09:37:31+00:00"
8684
         },
8684
         },
8685
         {
8685
         {
8686
             "name": "symfony/string",
8686
             "name": "symfony/string",
8866
         },
8866
         },
8867
         {
8867
         {
8868
             "name": "symfony/translation-contracts",
8868
             "name": "symfony/translation-contracts",
8869
-            "version": "v3.5.1",
8869
+            "version": "v3.6.0",
8870
             "source": {
8870
             "source": {
8871
                 "type": "git",
8871
                 "type": "git",
8872
                 "url": "https://github.com/symfony/translation-contracts.git",
8872
                 "url": "https://github.com/symfony/translation-contracts.git",
8873
-                "reference": "4667ff3bd513750603a09c8dedbea942487fb07c"
8873
+                "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d"
8874
             },
8874
             },
8875
             "dist": {
8875
             "dist": {
8876
                 "type": "zip",
8876
                 "type": "zip",
8877
-                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c",
8878
-                "reference": "4667ff3bd513750603a09c8dedbea942487fb07c",
8877
+                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
8878
+                "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d",
8879
                 "shasum": ""
8879
                 "shasum": ""
8880
             },
8880
             },
8881
             "require": {
8881
             "require": {
8888
                     "name": "symfony/contracts"
8888
                     "name": "symfony/contracts"
8889
                 },
8889
                 },
8890
                 "branch-alias": {
8890
                 "branch-alias": {
8891
-                    "dev-main": "3.5-dev"
8891
+                    "dev-main": "3.6-dev"
8892
                 }
8892
                 }
8893
             },
8893
             },
8894
             "autoload": {
8894
             "autoload": {
8924
                 "standards"
8924
                 "standards"
8925
             ],
8925
             ],
8926
             "support": {
8926
             "support": {
8927
-                "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1"
8927
+                "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0"
8928
             },
8928
             },
8929
             "funding": [
8929
             "funding": [
8930
                 {
8930
                 {
8940
                     "type": "tidelift"
8940
                     "type": "tidelift"
8941
                 }
8941
                 }
8942
             ],
8942
             ],
8943
-            "time": "2024-09-25T14:20:29+00:00"
8943
+            "time": "2024-09-27T08:32:26+00:00"
8944
         },
8944
         },
8945
         {
8945
         {
8946
             "name": "symfony/uid",
8946
             "name": "symfony/uid",

Loading…
Cancel
Save