Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

ManagesLineItems.php 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <?php
  2. namespace App\Concerns;
  3. use App\Enums\Accounting\AdjustmentComputation;
  4. use App\Enums\Accounting\DocumentDiscountMethod;
  5. use App\Models\Accounting\Bill;
  6. use App\Models\Accounting\DocumentLineItem;
  7. use App\Utilities\Currency\CurrencyAccessor;
  8. use App\Utilities\Currency\CurrencyConverter;
  9. use App\Utilities\RateCalculator;
  10. use Illuminate\Database\Eloquent\Model;
  11. use Illuminate\Support\Collection;
  12. trait ManagesLineItems
  13. {
  14. protected function handleLineItems(Model $record, Collection $lineItems): void
  15. {
  16. foreach ($lineItems as $index => $itemData) {
  17. $lineItem = isset($itemData['id'])
  18. ? $record->lineItems->find($itemData['id'])
  19. : $record->lineItems()->make();
  20. $lineItem->fill([
  21. 'offering_id' => $itemData['offering_id'],
  22. 'description' => $itemData['description'],
  23. 'quantity' => $itemData['quantity'],
  24. 'unit_price' => $itemData['unit_price'],
  25. 'line_number' => $index + 1,
  26. ]);
  27. if (! $lineItem->exists) {
  28. $lineItem->documentable()->associate($record);
  29. }
  30. $lineItem->save();
  31. $this->handleLineItemAdjustments($lineItem, $itemData, $record->discount_method);
  32. $this->updateLineItemTotals($lineItem, $record->discount_method);
  33. }
  34. }
  35. protected function deleteRemovedLineItems(Model $record, Collection $lineItems): void
  36. {
  37. $existingLineItemIds = $record->lineItems->pluck('id');
  38. $updatedLineItemIds = $lineItems->pluck('id')->filter();
  39. $lineItemsToDelete = $existingLineItemIds->diff($updatedLineItemIds);
  40. if ($lineItemsToDelete->isNotEmpty()) {
  41. $record
  42. ->lineItems()
  43. ->whereIn('id', $lineItemsToDelete)
  44. ->each(fn (DocumentLineItem $lineItem) => $lineItem->delete());
  45. }
  46. }
  47. protected function handleLineItemAdjustments(DocumentLineItem $lineItem, array $itemData, DocumentDiscountMethod $discountMethod): void
  48. {
  49. $isBill = $lineItem->documentable instanceof Bill;
  50. $taxType = $isBill ? 'purchaseTaxes' : 'salesTaxes';
  51. $discountType = $isBill ? 'purchaseDiscounts' : 'salesDiscounts';
  52. $adjustmentIds = collect($itemData[$taxType] ?? [])
  53. ->merge($discountMethod->isPerLineItem() ? ($itemData[$discountType] ?? []) : [])
  54. ->filter()
  55. ->unique();
  56. $lineItem->adjustments()->sync($adjustmentIds);
  57. $lineItem->refresh();
  58. }
  59. protected function updateLineItemTotals(DocumentLineItem $lineItem, DocumentDiscountMethod $discountMethod): void
  60. {
  61. $lineItem->updateQuietly([
  62. 'tax_total' => $lineItem->calculateTaxTotalAmount(),
  63. 'discount_total' => $discountMethod->isPerLineItem()
  64. ? $lineItem->calculateDiscountTotalAmount()
  65. : 0,
  66. ]);
  67. }
  68. protected function updateDocumentTotals(Model $record, array $data): array
  69. {
  70. $currencyCode = $data['currency_code'] ?? $record->currency_code ?? CurrencyAccessor::getDefaultCurrency();
  71. $subtotalCents = $record->lineItems()->sum('subtotal');
  72. $taxTotalCents = $record->lineItems()->sum('tax_total');
  73. $discountTotalCents = $this->calculateDiscountTotal(
  74. DocumentDiscountMethod::parse($data['discount_method']),
  75. AdjustmentComputation::parse($data['discount_computation']),
  76. $data['discount_rate'] ?? null,
  77. $subtotalCents,
  78. $record,
  79. $currencyCode,
  80. );
  81. $grandTotalCents = $subtotalCents + $taxTotalCents - $discountTotalCents;
  82. return [
  83. 'subtotal' => $subtotalCents,
  84. 'tax_total' => $taxTotalCents,
  85. 'discount_total' => $discountTotalCents,
  86. 'total' => $grandTotalCents,
  87. ];
  88. }
  89. protected function calculateDiscountTotal(
  90. DocumentDiscountMethod $discountMethod,
  91. ?AdjustmentComputation $discountComputation,
  92. ?string $discountRate,
  93. int $subtotalCents,
  94. Model $record,
  95. string $currencyCode
  96. ): int {
  97. if ($discountMethod->isPerLineItem()) {
  98. return $record->lineItems()->sum('discount_total');
  99. }
  100. if ($discountComputation?->isPercentage()) {
  101. $scaledRate = RateCalculator::parseLocalizedRate($discountRate);
  102. return RateCalculator::calculatePercentage($subtotalCents, $scaledRate);
  103. }
  104. return CurrencyConverter::convertToCents($discountRate, $currencyCode);
  105. }
  106. }