You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ManagesLineItems.php 4.3KB

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