Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

ManagesLineItems.php 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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 $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. ]);
  26. if (! $lineItem->exists) {
  27. $lineItem->documentable()->associate($record);
  28. }
  29. $lineItem->save();
  30. $this->handleLineItemAdjustments($lineItem, $itemData, $record->discount_method);
  31. $this->updateLineItemTotals($lineItem, $record->discount_method);
  32. }
  33. }
  34. protected function deleteRemovedLineItems(Model $record, Collection $lineItems): void
  35. {
  36. $existingLineItemIds = $record->lineItems->pluck('id');
  37. $updatedLineItemIds = $lineItems->pluck('id')->filter();
  38. $lineItemsToDelete = $existingLineItemIds->diff($updatedLineItemIds);
  39. if ($lineItemsToDelete->isNotEmpty()) {
  40. $record
  41. ->lineItems()
  42. ->whereIn('id', $lineItemsToDelete)
  43. ->each(fn (DocumentLineItem $lineItem) => $lineItem->delete());
  44. }
  45. }
  46. protected function handleLineItemAdjustments(DocumentLineItem $lineItem, array $itemData, DocumentDiscountMethod $discountMethod): void
  47. {
  48. $isBill = $lineItem->documentable instanceof Bill;
  49. $taxType = $isBill ? 'purchaseTaxes' : 'salesTaxes';
  50. $discountType = $isBill ? 'purchaseDiscounts' : 'salesDiscounts';
  51. $adjustmentIds = collect($itemData[$taxType] ?? [])
  52. ->merge($discountMethod->isPerLineItem() ? ($itemData[$discountType] ?? []) : [])
  53. ->filter()
  54. ->unique();
  55. $lineItem->adjustments()->sync($adjustmentIds);
  56. $lineItem->refresh();
  57. }
  58. protected function updateLineItemTotals(DocumentLineItem $lineItem, DocumentDiscountMethod $discountMethod): void
  59. {
  60. $lineItem->updateQuietly([
  61. 'tax_total' => $lineItem->calculateTaxTotal()->getAmount(),
  62. 'discount_total' => $discountMethod->isPerLineItem()
  63. ? $lineItem->calculateDiscountTotal()->getAmount()
  64. : 0,
  65. ]);
  66. }
  67. protected function updateDocumentTotals(Model $record, array $data): array
  68. {
  69. $currencyCode = $data['currency_code'] ?? $record->currency_code ?? CurrencyAccessor::getDefaultCurrency();
  70. $subtotalCents = $record->lineItems()->sum('subtotal');
  71. $taxTotalCents = $record->lineItems()->sum('tax_total');
  72. $discountTotalCents = $this->calculateDiscountTotal(
  73. DocumentDiscountMethod::parse($data['discount_method']),
  74. AdjustmentComputation::parse($data['discount_computation']),
  75. $data['discount_rate'] ?? null,
  76. $subtotalCents,
  77. $record,
  78. $currencyCode,
  79. );
  80. $grandTotalCents = $subtotalCents + $taxTotalCents - $discountTotalCents;
  81. return [
  82. 'subtotal' => CurrencyConverter::convertCentsToFormatSimple($subtotalCents, $currencyCode),
  83. 'tax_total' => CurrencyConverter::convertCentsToFormatSimple($taxTotalCents, $currencyCode),
  84. 'discount_total' => CurrencyConverter::convertCentsToFormatSimple($discountTotalCents, $currencyCode),
  85. 'total' => CurrencyConverter::convertCentsToFormatSimple($grandTotalCents, $currencyCode),
  86. ];
  87. }
  88. protected function calculateDiscountTotal(
  89. DocumentDiscountMethod $discountMethod,
  90. ?AdjustmentComputation $discountComputation,
  91. ?string $discountRate,
  92. int $subtotalCents,
  93. Model $record,
  94. string $currencyCode
  95. ): int {
  96. if ($discountMethod->isPerLineItem()) {
  97. return $record->lineItems()->sum('discount_total');
  98. }
  99. if ($discountComputation?->isPercentage()) {
  100. $scaledRate = RateCalculator::parseLocalizedRate($discountRate);
  101. return RateCalculator::calculatePercentage($subtotalCents, $scaledRate);
  102. }
  103. return CurrencyConverter::convertToCents($discountRate, $currencyCode);
  104. }
  105. }