From dfd2c563061bd8e5c85c2146fcb7ae31da0b608d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=2E=20Nagy=20Gerg=C5=91?= Date: Thu, 26 Sep 2024 13:47:18 +0200 Subject: [PATCH] wip --- ..._07_28_082302_create_bazar_taxes_table.php | 2 +- src/Cart/Driver.php | 14 ++-- src/Interfaces/Checkoutable.php | 21 +++++ src/Models/Cart.php | 4 +- src/Models/Item.php | 12 +-- src/Models/Shipping.php | 2 +- src/Traits/HasProperties.php | 4 +- src/Traits/InteractsWithItems.php | 76 +++++++++---------- src/Traits/InteractsWithTaxes.php | 15 +++- tests/Cart/ManagerTest.php | 11 +-- 10 files changed, 94 insertions(+), 67 deletions(-) diff --git a/database/migrations/2024_07_28_082302_create_bazar_taxes_table.php b/database/migrations/2024_07_28_082302_create_bazar_taxes_table.php index 3e26a248..ffad2f79 100644 --- a/database/migrations/2024_07_28_082302_create_bazar_taxes_table.php +++ b/database/migrations/2024_07_28_082302_create_bazar_taxes_table.php @@ -14,7 +14,7 @@ public function up(): void Schema::create('bazar_taxes', static function (Blueprint $table): void { $table->id(); $table->foreignId('tax_rate_id')->nullable()->constrained('bazar_tax_rates')->nullOnDelete(); - $table->morphs('taxable'); + $table->uuidMorphs('taxable'); $table->float('value')->unsigned(); $table->timestamps(); diff --git a/src/Cart/Driver.php b/src/Cart/Driver.php index 52f35e40..f2b27b2e 100644 --- a/src/Cart/Driver.php +++ b/src/Cart/Driver.php @@ -111,12 +111,12 @@ public function addItem(Buyable $buyable, float $quantity = 1, array $properties public function removeItem(string $id): void { if ($item = $this->getItem($id)) { - $item->delete(); - $key = $this->getItems()->search(static function (Item $item) use ($id) { return $item->getKey() === $id; }); + $item->delete(); + $this->getItems()->forget($key); $this->sync(); @@ -147,7 +147,8 @@ public function removeItems(array $ids): void public function updateItem(string $id, array $properties = []): void { if ($item = $this->getItem($id)) { - $item->fill($properties)->calculateTaxes(); + $item->fill($properties)->save(); + $item->calculateTaxes(); $this->sync(); } @@ -161,7 +162,8 @@ public function updateItems(array $data): void $items = $this->getItems()->whereIn('id', array_keys($data)); $items->each(static function (Item $item) use ($data): void { - $item->fill($data[$item->getKey()])->calculateTaxes(); + $item->fill($data[$item->getKey()])->save(); + $item->calculateTaxes(); }); if ($items->isNotEmpty()) { @@ -174,7 +176,7 @@ public function updateItems(array $data): void */ public function getItems(): Collection { - return $this->getModel()->items; + return $this->getModel()->getItems(); } /** @@ -276,7 +278,7 @@ public function sync(): void { $this->getShipping()->calculateFee(); - $this->getShipping()->calculateTaxes(); + $this->getModel()->calculateTax(); $this->getModel()->calculateDiscount(); } diff --git a/src/Interfaces/Checkoutable.php b/src/Interfaces/Checkoutable.php index b5f09920..d455de0f 100644 --- a/src/Interfaces/Checkoutable.php +++ b/src/Interfaces/Checkoutable.php @@ -5,6 +5,7 @@ use Cone\Bazar\Models\Item; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Support\Collection; interface Checkoutable extends Shippable { @@ -18,6 +19,26 @@ public function user(): BelongsTo; */ public function items(): MorphMany; + /** + * Get the checkoutable items. + */ + public function getItems(): Collection; + + /** + * Get the checkoutable taxable items. + */ + public function getTaxables(): Collection; + + /** + * Get the checkoutable fee items. + */ + public function getFees(): Collection; + + /** + * Get the checkoutable line items. + */ + public function getLineItems(): Collection; + /** * Get the currency. */ diff --git a/src/Models/Cart.php b/src/Models/Cart.php index 10b26715..39f74904 100644 --- a/src/Models/Cart.php +++ b/src/Models/Cart.php @@ -189,7 +189,7 @@ public function scopeExpired(Builder $query): Builder */ public function toOrder(): Order { - $this->lineItems->each(function (Item $item): void { + $this->getLineItems()->each(function (Item $item): void { if (! $item->buyable->buyable($this->order)) { throw new CartException(sprintf('Unable to add [%s] item to the order.', get_class($item->buyable))); } @@ -211,6 +211,8 @@ public function toOrder(): Order $this->order->shipping->address->fill($this->shipping->address->toArray())->save(); } + $this->order->calculateTax(); + return $this->order; } } diff --git a/src/Models/Item.php b/src/Models/Item.php index 7b49abda..648f3a67 100644 --- a/src/Models/Item.php +++ b/src/Models/Item.php @@ -83,16 +83,6 @@ class Item extends Model implements Contract */ protected $table = 'bazar_items'; - /** - * The "booted" method of the model. - */ - protected static function booted(): void - { - static::deleting(static function (self $item): void { - $item->taxes()->delete(); - }); - } - /** * Get the proxied interface. */ @@ -300,6 +290,6 @@ public function calculateTaxes(): float $this->taxes()->sync($taxes); - return $taxes->sum('value'); + return $this->getTaxTotal(); } } diff --git a/src/Models/Shipping.php b/src/Models/Shipping.php index 8f797635..648a03de 100644 --- a/src/Models/Shipping.php +++ b/src/Models/Shipping.php @@ -300,6 +300,6 @@ public function calculateTaxes(): float $this->taxes()->sync($taxes); - return $taxes->sum('value'); + return $this->getTaxTotal(); } } diff --git a/src/Traits/HasProperties.php b/src/Traits/HasProperties.php index bd3c46b3..4d4aeefd 100644 --- a/src/Traits/HasProperties.php +++ b/src/Traits/HasProperties.php @@ -14,7 +14,7 @@ trait HasProperties */ public function propertyValues(): MorphToMany { - return $this->morphToMany(PropertyValue::class, 'buyable', 'bazar_buyable_property_value'); + return $this->morphToMany(PropertyValue::getProxiedClass(), 'buyable', 'bazar_buyable_property_value'); } /** @@ -22,7 +22,7 @@ public function propertyValues(): MorphToMany */ public function properties(): HasManyThrough { - return $this->hasManyThrough(Property::class, PropertyValue::class, 'bazar_buyable_property_value.buyable_id', 'id', 'id', 'property_id') + return $this->hasManyThrough(Property::getProxiedClass(), PropertyValue::getProxiedClass(), 'bazar_buyable_property_value.buyable_id', 'id', 'id', 'property_id') ->join('bazar_buyable_property_value', 'bazar_buyable_property_value.property_value_id', '=', 'bazar_property_values.id') ->where('bazar_buyable_property_value.buyable_type', static::class); } diff --git a/src/Traits/InteractsWithItems.php b/src/Traits/InteractsWithItems.php index 9c9e20d1..10acdb91 100644 --- a/src/Traits/InteractsWithItems.php +++ b/src/Traits/InteractsWithItems.php @@ -5,6 +5,7 @@ use Cone\Bazar\Bazar; use Cone\Bazar\Interfaces\Inventoryable; use Cone\Bazar\Interfaces\LineItem; +use Cone\Bazar\Interfaces\Taxable; use Cone\Bazar\Models\Item; use Cone\Bazar\Models\Shipping; use Cone\Bazar\Support\Currency; @@ -28,8 +29,8 @@ public static function bootInteractsWithItems(): void { static::deleting(static function (self $model): void { if (! in_array(SoftDeletes::class, class_uses_recursive($model)) || $model->forceDeleting) { - $model->items()->delete(); - $model->shipping()->delete(); + $model->items->each->delete(); + $model->shipping->delete(); } }); } @@ -75,47 +76,37 @@ protected function currency(): Attribute } /** - * Get the line items attribute. - * - * @return \Illuminate\Database\Eloquent\Casts\Attribute<\Illuminate\Support\Collection, never> + * Get the items. */ - protected function lineItems(): Attribute + public function getItems(): Collection { - return new Attribute( - get: function (): Collection { - return $this->items->filter->isLineItem(); - } - ); + return $this->items; } /** - * Get the fees attribute. - * - * @return \Illuminate\Database\Eloquent\Casts\Attribute<\Illuminate\Support\Collection, never> + * Get the line items. */ - protected function fees(): Attribute + public function getLineItems(): Collection { - return new Attribute( - get: function (): Collection { - return $this->items->filter->isFee(); - } - ); + return $this->getItems()->filter->isLineItem(); } /** - * Get the taxables attribute. - * - * @return \Illuminate\Database\Eloquent\Casts\Attribute<\Illuminate\Support\Collection, never> + * Get the fees. */ - protected function taxables(): Attribute + public function getFees(): Collection { - return new Attribute( - get: function (): Collection { - return $this->items->when($this->needsShipping(), function (Collection $items): Collection { - return $items->merge([$this->shipping]); - }); - } - ); + return $this->getItems()->filter->isFee(); + } + + /** + * Get the taxables. + */ + public function getTaxables(): Collection + { + return $this->getItems()->when($this->needsShipping(), function (Collection $items): Collection { + return $items->merge([$this->shipping]); + }); } /** @@ -227,10 +218,12 @@ public function getCurrency(): string */ public function getTotal(): float { - $value = $this->taxables->sum(static function (LineItem $item): float { + $value = $this->items->sum(static function (Item $item): float { return $item->getTotal(); }); + $value += $this->needsShipping() ? $this->shipping->getTotal() : 0; + $value -= $this->discount; return round($value < 0 ? 0 : $value, 2); @@ -249,7 +242,7 @@ public function getFormattedTotal(): string */ public function getSubtotal(): float { - $value = $this->lineItems->sum(static function (LineItem $item): float { + $value = $this->getLineItems()->sum(static function (LineItem $item): float { return $item->getSubtotal(); }); @@ -269,7 +262,7 @@ public function getFormattedSubtotal(): string */ public function getFeeTotal(): float { - $value = $this->fees->sum(static function (LineItem $item): float { + $value = $this->getFees()->sum(static function (LineItem $item): float { return $item->getSubtotal(); }); @@ -289,8 +282,8 @@ public function getFormattedFeeTotal(): string */ public function getTax(): float { - $value = $this->taxables->sum(static function (LineItem $item): float { - return $item->getTaxTotal() * $item->getQuantity(); + $value = $this->getTaxables()->sum(static function (Taxable $item): float { + return $item->getTaxTotal(); }); return round($value, 2); @@ -309,9 +302,13 @@ public function getFormattedTax(): string */ public function calculateTax(): float { - return $this->taxables->sum(static function (LineItem $item): float { - return $item->calculateTaxes() * $item->getQuantity(); + $value = $this->getTaxables()->each(static function (Taxable $item): void { + $item->calculateTaxes(); + })->sum(static function (Taxable $item): float { + return $item->getTaxTotal(); }); + + return round($value, 2); } /** @@ -365,7 +362,8 @@ public function syncItems(): void if ($item->isLineItem() && ! is_null($item->checkoutable)) { $data = $item->buyable->toItem($item->checkoutable, $item->only('properties'))->only('price'); - $item->fill($data)->calculateTaxes(); + $item->fill($data)->save(); + $item->calculateTaxes(); } }); } diff --git a/src/Traits/InteractsWithTaxes.php b/src/Traits/InteractsWithTaxes.php index ccfc0633..3d66d05a 100644 --- a/src/Traits/InteractsWithTaxes.php +++ b/src/Traits/InteractsWithTaxes.php @@ -6,9 +6,22 @@ use Cone\Bazar\Models\TaxRate; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Relations\MorphToMany; +use Illuminate\Database\Eloquent\SoftDeletes; trait InteractsWithTaxes { + /** + * Boot the trait. + */ + public static function bootInteractsWithTaxes(): void + { + static::deleting(static function (self $model): void { + if (! in_array(SoftDeletes::class, class_uses_recursive($model)) || $model->forceDeleting) { + $model->taxes()->detach(); + } + }); + } + /** * Get the taxes for the model. */ @@ -50,6 +63,6 @@ protected function formattedTaxTotal(): Attribute */ public function getTaxTotal(): float { - return $this->taxes->sum('tax.value'); + return $this->taxes->sum('tax.value') * $this->getQuantity(); } } diff --git a/tests/Cart/ManagerTest.php b/tests/Cart/ManagerTest.php index 1094e057..05fc2909 100644 --- a/tests/Cart/ManagerTest.php +++ b/tests/Cart/ManagerTest.php @@ -12,6 +12,7 @@ use Cone\Bazar\Models\Property; use Cone\Bazar\Models\PropertyValue; use Cone\Bazar\Models\Shipping; +use Cone\Bazar\Models\TaxRate; use Cone\Bazar\Models\Variant; use Cone\Bazar\Support\Facades\Cart as CartFacade; use Cone\Bazar\Tests\TestCase; @@ -44,6 +45,10 @@ public function setUp(): void $this->product->propertyValues()->attach($property->values); $this->variant->propertyValues()->attach($property->values->where('value', 'S')); + $taxRate = TaxRate::factory()->create(); + + $this->product->taxRates()->attach($taxRate); + $this->manager->addItem($this->product, 2, ['size' => 'L']); $this->manager->addItem($this->product, 1, ['size' => 'S']); } @@ -97,6 +102,7 @@ public function test_cart_can_remove_items(): void $item = $this->manager->getModel()->findItem([ 'properties' => ['size' => 'L'], ]); + $this->manager->removeItem($item->id); $this->assertEquals(1, $this->manager->count()); @@ -186,9 +192,4 @@ public function test_cart_can_checkout(): void Event::assertDispatched(CheckoutProcessed::class); } - - public function test_cart_can_sync_items(): void - { - $this->assertEquals($this->product->price * 2 + $this->variant->price, $this->manager->getTotal()); - } }