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.1KB

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