Andrew Wallo 4 个月前
父节点
当前提交
6e99ffd979
共有 2 个文件被更改,包括 91 次插入49 次删除
  1. 90
    48
      app/Models/Accounting/Bill.php
  2. 1
    1
      app/Models/Accounting/Invoice.php

+ 90
- 48
app/Models/Accounting/Bill.php 查看文件

266
     public function createInitialTransaction(?Carbon $postedAt = null): void
266
     public function createInitialTransaction(?Carbon $postedAt = null): void
267
     {
267
     {
268
         $postedAt ??= $this->date;
268
         $postedAt ??= $this->date;
269
-
270
-        $total = $this->formatAmountToDefaultCurrency($this->getRawOriginal('total'));
271
-
272
-        $transaction = $this->transactions()->create([
273
-            'company_id' => $this->company_id,
274
-            'type' => TransactionType::Journal,
275
-            'posted_at' => $postedAt,
276
-            'amount' => $total,
277
-            'description' => 'Bill Creation for Bill #' . $this->bill_number,
278
-        ]);
279
-
280
         $baseDescription = "{$this->vendor->name}: Bill #{$this->bill_number}";
269
         $baseDescription = "{$this->vendor->name}: Bill #{$this->bill_number}";
281
 
270
 
282
-        $transaction->journalEntries()->create([
283
-            'company_id' => $this->company_id,
271
+        $journalEntryData = [];
272
+
273
+        $totalInBillCurrency = $this->getRawOriginal('total');
274
+        $journalEntryData[] = [
284
             'type' => JournalEntryType::Credit,
275
             'type' => JournalEntryType::Credit,
285
             'account_id' => Account::getAccountsPayableAccount($this->company_id)->id,
276
             'account_id' => Account::getAccountsPayableAccount($this->company_id)->id,
286
-            'amount' => $total,
277
+            'amount_in_bill_currency' => $totalInBillCurrency,
287
             'description' => $baseDescription,
278
             'description' => $baseDescription,
288
-        ]);
279
+        ];
289
 
280
 
290
-        $totalLineItemSubtotalCents = $this->convertAmountToDefaultCurrency((int) $this->lineItems()->sum('subtotal'));
291
-        $billDiscountTotalCents = $this->convertAmountToDefaultCurrency((int) $this->getRawOriginal('discount_total'));
292
-        $remainingDiscountCents = $billDiscountTotalCents;
281
+        $totalLineItemSubtotalInBillCurrency = (int) $this->lineItems()->sum('subtotal');
282
+        $billDiscountTotalInBillCurrency = (int) $this->getRawOriginal('discount_total');
283
+        $remainingDiscountInBillCurrency = $billDiscountTotalInBillCurrency;
293
 
284
 
294
         foreach ($this->lineItems as $index => $lineItem) {
285
         foreach ($this->lineItems as $index => $lineItem) {
295
             $lineItemDescription = "{$baseDescription} › {$lineItem->offering->name}";
286
             $lineItemDescription = "{$baseDescription} › {$lineItem->offering->name}";
287
+            $lineItemSubtotalInBillCurrency = $lineItem->getRawOriginal('subtotal');
296
 
288
 
297
-            $lineItemSubtotal = $this->formatAmountToDefaultCurrency($lineItem->getRawOriginal('subtotal'));
298
-
299
-            $transaction->journalEntries()->create([
300
-                'company_id' => $this->company_id,
289
+            $journalEntryData[] = [
301
                 'type' => JournalEntryType::Debit,
290
                 'type' => JournalEntryType::Debit,
302
                 'account_id' => $lineItem->offering->expense_account_id,
291
                 'account_id' => $lineItem->offering->expense_account_id,
303
-                'amount' => $lineItemSubtotal,
292
+                'amount_in_bill_currency' => $lineItemSubtotalInBillCurrency,
304
                 'description' => $lineItemDescription,
293
                 'description' => $lineItemDescription,
305
-            ]);
294
+            ];
306
 
295
 
307
             foreach ($lineItem->adjustments as $adjustment) {
296
             foreach ($lineItem->adjustments as $adjustment) {
308
-                $adjustmentAmount = $this->formatAmountToDefaultCurrency($lineItem->calculateAdjustmentTotalAmount($adjustment));
297
+                $adjustmentAmountInBillCurrency = $lineItem->calculateAdjustmentTotalAmount($adjustment);
309
 
298
 
310
                 if ($adjustment->isNonRecoverablePurchaseTax()) {
299
                 if ($adjustment->isNonRecoverablePurchaseTax()) {
311
-                    $transaction->journalEntries()->create([
312
-                        'company_id' => $this->company_id,
300
+                    $journalEntryData[] = [
313
                         'type' => JournalEntryType::Debit,
301
                         'type' => JournalEntryType::Debit,
314
                         'account_id' => $lineItem->offering->expense_account_id,
302
                         'account_id' => $lineItem->offering->expense_account_id,
315
-                        'amount' => $adjustmentAmount,
303
+                        'amount_in_bill_currency' => $adjustmentAmountInBillCurrency,
316
                         'description' => "{$lineItemDescription} ({$adjustment->name})",
304
                         'description' => "{$lineItemDescription} ({$adjustment->name})",
317
-                    ]);
305
+                    ];
318
                 } elseif ($adjustment->account_id) {
306
                 } elseif ($adjustment->account_id) {
319
-                    $transaction->journalEntries()->create([
320
-                        'company_id' => $this->company_id,
307
+                    $journalEntryData[] = [
321
                         'type' => $adjustment->category->isDiscount() ? JournalEntryType::Credit : JournalEntryType::Debit,
308
                         'type' => $adjustment->category->isDiscount() ? JournalEntryType::Credit : JournalEntryType::Debit,
322
                         'account_id' => $adjustment->account_id,
309
                         'account_id' => $adjustment->account_id,
323
-                        'amount' => $adjustmentAmount,
310
+                        'amount_in_bill_currency' => $adjustmentAmountInBillCurrency,
324
                         'description' => $lineItemDescription,
311
                         'description' => $lineItemDescription,
325
-                    ]);
312
+                    ];
326
                 }
313
                 }
327
             }
314
             }
328
 
315
 
329
-            if ($this->discount_method->isPerDocument() && $totalLineItemSubtotalCents > 0) {
330
-                $lineItemSubtotalCents = $this->convertAmountToDefaultCurrency((int) $lineItem->getRawOriginal('subtotal'));
316
+            // Handle per-document discount allocation
317
+            if ($this->discount_method->isPerDocument() && $totalLineItemSubtotalInBillCurrency > 0) {
318
+                $lineItemSubtotalInBillCurrency = (int) $lineItem->getRawOriginal('subtotal');
331
 
319
 
332
                 if ($index === $this->lineItems->count() - 1) {
320
                 if ($index === $this->lineItems->count() - 1) {
333
-                    $lineItemDiscount = $remainingDiscountCents;
321
+                    $lineItemDiscountInBillCurrency = $remainingDiscountInBillCurrency;
334
                 } else {
322
                 } else {
335
-                    $lineItemDiscount = (int) round(
336
-                        ($lineItemSubtotalCents / $totalLineItemSubtotalCents) * $billDiscountTotalCents
323
+                    $lineItemDiscountInBillCurrency = (int) round(
324
+                        ($lineItemSubtotalInBillCurrency / $totalLineItemSubtotalInBillCurrency) * $billDiscountTotalInBillCurrency
337
                     );
325
                     );
338
-                    $remainingDiscountCents -= $lineItemDiscount;
326
+                    $remainingDiscountInBillCurrency -= $lineItemDiscountInBillCurrency;
339
                 }
327
                 }
340
 
328
 
341
-                if ($lineItemDiscount > 0) {
342
-                    $transaction->journalEntries()->create([
343
-                        'company_id' => $this->company_id,
329
+                if ($lineItemDiscountInBillCurrency > 0) {
330
+                    $journalEntryData[] = [
344
                         'type' => JournalEntryType::Credit,
331
                         'type' => JournalEntryType::Credit,
345
                         'account_id' => Account::getPurchaseDiscountAccount($this->company_id)->id,
332
                         'account_id' => Account::getPurchaseDiscountAccount($this->company_id)->id,
346
-                        'amount' => CurrencyConverter::convertCentsToFormatSimple($lineItemDiscount),
333
+                        'amount_in_bill_currency' => $lineItemDiscountInBillCurrency,
347
                         'description' => "{$lineItemDescription} (Proportional Discount)",
334
                         'description' => "{$lineItemDescription} (Proportional Discount)",
348
-                    ]);
335
+                    ];
349
                 }
336
                 }
350
             }
337
             }
351
         }
338
         }
339
+
340
+        // Convert amounts to default currency
341
+        $totalDebitsInDefaultCurrency = 0;
342
+        $totalCreditsInDefaultCurrency = 0;
343
+
344
+        foreach ($journalEntryData as &$entry) {
345
+            $entry['amount_in_default_currency'] = $this->formatAmountToDefaultCurrency($entry['amount_in_bill_currency']);
346
+
347
+            if ($entry['type'] === JournalEntryType::Debit) {
348
+                $totalDebitsInDefaultCurrency += $entry['amount_in_default_currency'];
349
+            } else {
350
+                $totalCreditsInDefaultCurrency += $entry['amount_in_default_currency'];
351
+            }
352
+        }
353
+
354
+        unset($entry);
355
+
356
+        // Handle currency conversion imbalance
357
+        $imbalance = $totalDebitsInDefaultCurrency - $totalCreditsInDefaultCurrency;
358
+        if ($imbalance !== 0) {
359
+            $targetType = $imbalance > 0 ? JournalEntryType::Credit : JournalEntryType::Debit;
360
+            $adjustmentAmount = abs($imbalance);
361
+
362
+            // Find last entry of target type and adjust it
363
+            $lastKey = array_key_last(array_filter($journalEntryData, fn ($entry) => $entry['type'] === $targetType, ARRAY_FILTER_USE_BOTH));
364
+            $journalEntryData[$lastKey]['amount_in_default_currency'] += $adjustmentAmount;
365
+
366
+            if ($targetType === JournalEntryType::Debit) {
367
+                $totalDebitsInDefaultCurrency += $adjustmentAmount;
368
+            } else {
369
+                $totalCreditsInDefaultCurrency += $adjustmentAmount;
370
+            }
371
+        }
372
+
373
+        if ($totalDebitsInDefaultCurrency !== $totalCreditsInDefaultCurrency) {
374
+            throw new \Exception('Journal entries do not balance for Bill #' . $this->bill_number . '. Debits: ' . $totalDebitsInDefaultCurrency . ', Credits: ' . $totalCreditsInDefaultCurrency);
375
+        }
376
+
377
+        // Create the transaction using the sum of debits
378
+        $transaction = $this->transactions()->create([
379
+            'company_id' => $this->company_id,
380
+            'type' => TransactionType::Journal,
381
+            'posted_at' => $postedAt,
382
+            'amount' => $totalDebitsInDefaultCurrency,
383
+            'description' => 'Bill Creation for Bill #' . $this->bill_number,
384
+        ]);
385
+
386
+        // Create all journal entries
387
+        foreach ($journalEntryData as $entry) {
388
+            $transaction->journalEntries()->create([
389
+                'company_id' => $this->company_id,
390
+                'type' => $entry['type'],
391
+                'account_id' => $entry['account_id'],
392
+                'amount' => $entry['amount_in_default_currency'],
393
+                'description' => $entry['description'],
394
+            ]);
395
+        }
352
     }
396
     }
353
 
397
 
354
     public function updateInitialTransaction(): void
398
     public function updateInitialTransaction(): void
374
         return $amountCents;
418
         return $amountCents;
375
     }
419
     }
376
 
420
 
377
-    public function formatAmountToDefaultCurrency(int $amountCents): string
421
+    public function formatAmountToDefaultCurrency(int $amountCents): int
378
     {
422
     {
379
-        $convertedCents = $this->convertAmountToDefaultCurrency($amountCents);
380
-
381
-        return CurrencyConverter::convertCentsToFormatSimple($convertedCents);
423
+        return $this->convertAmountToDefaultCurrency($amountCents);
382
     }
424
     }
383
 
425
 
384
     public static function getReplicateAction(string $action = ReplicateAction::class): MountableAction
426
     public static function getReplicateAction(string $action = ReplicateAction::class): MountableAction

+ 1
- 1
app/Models/Accounting/Invoice.php 查看文件

478
         }
478
         }
479
 
479
 
480
         if ($totalDebitsInDefaultCurrency !== $totalCreditsInDefaultCurrency) {
480
         if ($totalDebitsInDefaultCurrency !== $totalCreditsInDefaultCurrency) {
481
-            throw new \Exception('Journal entries do not balance. Debits: ' . $totalDebitsInDefaultCurrency . ', Credits: ' . $totalCreditsInDefaultCurrency);
481
+            throw new \Exception('Journal entries do not balance for Invoice #' . $this->invoice_number . '. Debits: ' . $totalDebitsInDefaultCurrency . ', Credits: ' . $totalCreditsInDefaultCurrency);
482
         }
482
         }
483
 
483
 
484
         // Create the transaction using the sum of debits
484
         // Create the transaction using the sum of debits

正在加载...
取消
保存