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,89 +266,133 @@ class Bill extends Document
266 266
     public function createInitialTransaction(?Carbon $postedAt = null): void
267 267
     {
268 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 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 275
             'type' => JournalEntryType::Credit,
285 276
             'account_id' => Account::getAccountsPayableAccount($this->company_id)->id,
286
-            'amount' => $total,
277
+            'amount_in_bill_currency' => $totalInBillCurrency,
287 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 285
         foreach ($this->lineItems as $index => $lineItem) {
295 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 290
                 'type' => JournalEntryType::Debit,
302 291
                 'account_id' => $lineItem->offering->expense_account_id,
303
-                'amount' => $lineItemSubtotal,
292
+                'amount_in_bill_currency' => $lineItemSubtotalInBillCurrency,
304 293
                 'description' => $lineItemDescription,
305
-            ]);
294
+            ];
306 295
 
307 296
             foreach ($lineItem->adjustments as $adjustment) {
308
-                $adjustmentAmount = $this->formatAmountToDefaultCurrency($lineItem->calculateAdjustmentTotalAmount($adjustment));
297
+                $adjustmentAmountInBillCurrency = $lineItem->calculateAdjustmentTotalAmount($adjustment);
309 298
 
310 299
                 if ($adjustment->isNonRecoverablePurchaseTax()) {
311
-                    $transaction->journalEntries()->create([
312
-                        'company_id' => $this->company_id,
300
+                    $journalEntryData[] = [
313 301
                         'type' => JournalEntryType::Debit,
314 302
                         'account_id' => $lineItem->offering->expense_account_id,
315
-                        'amount' => $adjustmentAmount,
303
+                        'amount_in_bill_currency' => $adjustmentAmountInBillCurrency,
316 304
                         'description' => "{$lineItemDescription} ({$adjustment->name})",
317
-                    ]);
305
+                    ];
318 306
                 } elseif ($adjustment->account_id) {
319
-                    $transaction->journalEntries()->create([
320
-                        'company_id' => $this->company_id,
307
+                    $journalEntryData[] = [
321 308
                         'type' => $adjustment->category->isDiscount() ? JournalEntryType::Credit : JournalEntryType::Debit,
322 309
                         'account_id' => $adjustment->account_id,
323
-                        'amount' => $adjustmentAmount,
310
+                        'amount_in_bill_currency' => $adjustmentAmountInBillCurrency,
324 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 320
                 if ($index === $this->lineItems->count() - 1) {
333
-                    $lineItemDiscount = $remainingDiscountCents;
321
+                    $lineItemDiscountInBillCurrency = $remainingDiscountInBillCurrency;
334 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 331
                         'type' => JournalEntryType::Credit,
345 332
                         'account_id' => Account::getPurchaseDiscountAccount($this->company_id)->id,
346
-                        'amount' => CurrencyConverter::convertCentsToFormatSimple($lineItemDiscount),
333
+                        'amount_in_bill_currency' => $lineItemDiscountInBillCurrency,
347 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 398
     public function updateInitialTransaction(): void
@@ -374,11 +418,9 @@ class Bill extends Document
374 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 426
     public static function getReplicateAction(string $action = ReplicateAction::class): MountableAction

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

@@ -478,7 +478,7 @@ class Invoice extends Document
478 478
         }
479 479
 
480 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 484
         // Create the transaction using the sum of debits

正在加载...
取消
保存