BFI cart conversion product purchase, R cart dates
This commit is contained in:
parent
c5b78071e7
commit
f6c60f3d79
|
|
@ -263,8 +263,8 @@ return new class extends Migration
|
||||||
$table->timestamp('last_activity_at')->nullable();
|
$table->timestamp('last_activity_at')->nullable();
|
||||||
$table->timestamp('expires_at')->nullable();
|
$table->timestamp('expires_at')->nullable();
|
||||||
$table->timestamp('converted_at')->nullable();
|
$table->timestamp('converted_at')->nullable();
|
||||||
$table->timestamp('from_date')->nullable(); // Default start date for booking items
|
$table->timestamp('from')->nullable(); // Default start date for booking items
|
||||||
$table->timestamp('until_date')->nullable(); // Default end date for booking items
|
$table->timestamp('until')->nullable(); // Default end date for booking items
|
||||||
$table->json('meta')->nullable();
|
$table->json('meta')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->softDeletes();
|
$table->softDeletes();
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ class StripeWebhookController
|
||||||
'converted_at' => now(),
|
'converted_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Update associated purchases
|
// Update associated purchases and claim stocks
|
||||||
$this->updatePurchasesForSession($cart, $session);
|
$this->updatePurchasesForSession($cart, $session);
|
||||||
|
|
||||||
Log::info('Cart converted via Stripe checkout', [
|
Log::info('Cart converted via Stripe checkout', [
|
||||||
|
|
@ -165,15 +165,20 @@ class StripeWebhookController
|
||||||
// Update purchases with this charge ID if they exist
|
// Update purchases with this charge ID if they exist
|
||||||
$purchases = ProductPurchase::where('charge_id', $charge->id)->get();
|
$purchases = ProductPurchase::where('charge_id', $charge->id)->get();
|
||||||
foreach ($purchases as $purchase) {
|
foreach ($purchases as $purchase) {
|
||||||
$updateData = [
|
if ($purchase->status !== PurchaseStatus::COMPLETED) {
|
||||||
'status' => PurchaseStatus::COMPLETED,
|
$updateData = [
|
||||||
];
|
'status' => PurchaseStatus::COMPLETED,
|
||||||
|
];
|
||||||
|
|
||||||
if (in_array('amount_paid', $purchase->getFillable())) {
|
if (in_array('amount_paid', $purchase->getFillable())) {
|
||||||
$updateData['amount_paid'] = $charge->amount / 100;
|
$updateData['amount_paid'] = $charge->amount / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
$purchase->update($updateData);
|
||||||
|
|
||||||
|
// Claim stock if not already claimed
|
||||||
|
$this->claimStockForPurchase($purchase);
|
||||||
}
|
}
|
||||||
|
|
||||||
$purchase->update($updateData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -209,15 +214,20 @@ class StripeWebhookController
|
||||||
// Update purchases with this payment intent
|
// Update purchases with this payment intent
|
||||||
$purchases = ProductPurchase::where('charge_id', $paymentIntent->id)->get();
|
$purchases = ProductPurchase::where('charge_id', $paymentIntent->id)->get();
|
||||||
foreach ($purchases as $purchase) {
|
foreach ($purchases as $purchase) {
|
||||||
$updateData = [
|
if ($purchase->status !== PurchaseStatus::COMPLETED) {
|
||||||
'status' => PurchaseStatus::COMPLETED,
|
$updateData = [
|
||||||
];
|
'status' => PurchaseStatus::COMPLETED,
|
||||||
|
];
|
||||||
|
|
||||||
if (in_array('amount_paid', $purchase->getFillable())) {
|
if (in_array('amount_paid', $purchase->getFillable())) {
|
||||||
$updateData['amount_paid'] = $paymentIntent->amount / 100;
|
$updateData['amount_paid'] = $paymentIntent->amount / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
$purchase->update($updateData);
|
||||||
|
|
||||||
|
// Claim stock if not already claimed
|
||||||
|
$this->claimStockForPurchase($purchase);
|
||||||
}
|
}
|
||||||
|
|
||||||
$purchase->update($updateData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,7 +254,8 @@ class StripeWebhookController
|
||||||
*/
|
*/
|
||||||
protected function updatePurchasesForSession(Cart $cart, $session)
|
protected function updatePurchasesForSession(Cart $cart, $session)
|
||||||
{
|
{
|
||||||
$purchases = $cart->items()->with('purchase')->get()->pluck('purchase')->filter();
|
// Get all purchases for this cart
|
||||||
|
$purchases = ProductPurchase::where('cart_id', $cart->id)->get();
|
||||||
|
|
||||||
foreach ($purchases as $purchase) {
|
foreach ($purchases as $purchase) {
|
||||||
if (!$purchase) {
|
if (!$purchase) {
|
||||||
|
|
@ -255,16 +266,87 @@ class StripeWebhookController
|
||||||
'status' => PurchaseStatus::COMPLETED,
|
'status' => PurchaseStatus::COMPLETED,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Only update columns that exist in the model
|
// Update charge_id if it exists in fillable
|
||||||
if (in_array('charge_id', $purchase->getFillable())) {
|
if (in_array('charge_id', $purchase->getFillable())) {
|
||||||
$updateData['charge_id'] = $session->payment_intent;
|
$updateData['charge_id'] = $session->payment_intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update amount_paid if it exists in fillable
|
||||||
if (in_array('amount_paid', $purchase->getFillable())) {
|
if (in_array('amount_paid', $purchase->getFillable())) {
|
||||||
$updateData['amount_paid'] = $session->amount_total / 100; // Convert from cents
|
// Use the purchase's amount since it was already set correctly
|
||||||
|
$updateData['amount_paid'] = $purchase->amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
$purchase->update($updateData);
|
$purchase->update($updateData);
|
||||||
|
|
||||||
|
// Claim stock after successful payment
|
||||||
|
$this->claimStockForPurchase($purchase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claim stock for a purchase (used after successful payment)
|
||||||
|
*/
|
||||||
|
protected function claimStockForPurchase(ProductPurchase $purchase)
|
||||||
|
{
|
||||||
|
$product = $purchase->purchasable;
|
||||||
|
if (!($product instanceof \Blax\Shop\Models\Product)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if product doesn't manage stock
|
||||||
|
if (!$product->manage_stock && !$product->isPool()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if we need to claim stock with timespan (from/until)
|
||||||
|
$hasTimespan = $purchase->from && $purchase->until;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($product->isPool()) {
|
||||||
|
// For pool products: claim from single items (they manage their own stock)
|
||||||
|
// Only claim if there's a timespan (booking dates)
|
||||||
|
if ($hasTimespan) {
|
||||||
|
$product->claimPoolStock(
|
||||||
|
$purchase->quantity,
|
||||||
|
$purchase,
|
||||||
|
$purchase->from,
|
||||||
|
$purchase->until,
|
||||||
|
"Purchase #{$purchase->id} completed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// If no timespan, pool products don't claim stock
|
||||||
|
// (single items would be simple products that don't need claiming)
|
||||||
|
} elseif ($product->isBooking()) {
|
||||||
|
// For booking products: claim stock for the timespan
|
||||||
|
if ($hasTimespan) {
|
||||||
|
$product->claimStock(
|
||||||
|
$purchase->quantity,
|
||||||
|
$purchase,
|
||||||
|
$purchase->from,
|
||||||
|
$purchase->until,
|
||||||
|
"Purchase #{$purchase->id} completed"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Log::warning('Booking product without timespan', [
|
||||||
|
'purchase_id' => $purchase->id,
|
||||||
|
'product_id' => $product->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For simple/consumable products (like shampoo bottle):
|
||||||
|
// Decrease stock immediately (no timespan needed)
|
||||||
|
if ($product->manage_stock) {
|
||||||
|
$product->decreaseStock($purchase->quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Failed to claim/decrease stock for purchase', [
|
||||||
|
'purchase_id' => $purchase->id,
|
||||||
|
'product_id' => $product->id,
|
||||||
|
'product_type' => $product->type->value ?? 'unknown',
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ namespace Blax\Shop\Models;
|
||||||
use Blax\Shop\Contracts\Cartable;
|
use Blax\Shop\Contracts\Cartable;
|
||||||
use Blax\Shop\Enums\CartStatus;
|
use Blax\Shop\Enums\CartStatus;
|
||||||
use Blax\Shop\Enums\ProductType;
|
use Blax\Shop\Enums\ProductType;
|
||||||
|
use Blax\Shop\Enums\PurchaseStatus;
|
||||||
use Blax\Shop\Exceptions\InvalidDateRangeException;
|
use Blax\Shop\Exceptions\InvalidDateRangeException;
|
||||||
use Blax\Shop\Exceptions\NotEnoughAvailableInTimespanException;
|
use Blax\Shop\Exceptions\NotEnoughAvailableInTimespanException;
|
||||||
use Blax\Shop\Services\CartService;
|
use Blax\Shop\Services\CartService;
|
||||||
|
|
@ -17,6 +18,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class Cart extends Model
|
class Cart extends Model
|
||||||
{
|
{
|
||||||
|
|
@ -32,8 +34,8 @@ class Cart extends Model
|
||||||
'expires_at',
|
'expires_at',
|
||||||
'converted_at',
|
'converted_at',
|
||||||
'meta',
|
'meta',
|
||||||
'from_date',
|
'from',
|
||||||
'until_date',
|
'until',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
|
@ -42,8 +44,8 @@ class Cart extends Model
|
||||||
'converted_at' => 'datetime',
|
'converted_at' => 'datetime',
|
||||||
'last_activity_at' => 'datetime',
|
'last_activity_at' => 'datetime',
|
||||||
'meta' => 'object',
|
'meta' => 'object',
|
||||||
'from_date' => 'datetime',
|
'from' => 'datetime',
|
||||||
'until_date' => 'datetime',
|
'until' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $appends = [
|
protected $appends = [
|
||||||
|
|
@ -236,8 +238,8 @@ class Cart extends Model
|
||||||
|
|
||||||
// Update cart with from/until
|
// Update cart with from/until
|
||||||
$this->update([
|
$this->update([
|
||||||
'from_date' => $from,
|
'from' => $from,
|
||||||
'until_date' => $until,
|
'until' => $until,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Update cart items with from/until
|
// Update cart items with from/until
|
||||||
|
|
@ -264,15 +266,15 @@ class Cart extends Model
|
||||||
$from = Carbon::parse($from);
|
$from = Carbon::parse($from);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->until_date && $from >= $this->until_date) {
|
if ($this->until && $from >= $this->until) {
|
||||||
throw new InvalidDateRangeException();
|
throw new InvalidDateRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($validateAvailability && $this->until_date) {
|
if ($validateAvailability && $this->until) {
|
||||||
$this->validateDateAvailability($from, $this->until_date);
|
$this->validateDateAvailability($from, $this->until);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->update(['from_date' => $from]);
|
$this->update(['from' => $from]);
|
||||||
|
|
||||||
return $this->fresh();
|
return $this->fresh();
|
||||||
}
|
}
|
||||||
|
|
@ -293,15 +295,15 @@ class Cart extends Model
|
||||||
$until = Carbon::parse($until);
|
$until = Carbon::parse($until);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->from_date && $this->from_date >= $until) {
|
if ($this->from && $this->from >= $until) {
|
||||||
throw new InvalidDateRangeException();
|
throw new InvalidDateRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($validateAvailability && $this->from_date) {
|
if ($validateAvailability && $this->from) {
|
||||||
$this->validateDateAvailability($this->from_date, $until);
|
$this->validateDateAvailability($this->from, $until);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->update(['until_date' => $until]);
|
$this->update(['until' => $until]);
|
||||||
|
|
||||||
return $this->fresh();
|
return $this->fresh();
|
||||||
}
|
}
|
||||||
|
|
@ -315,27 +317,27 @@ class Cart extends Model
|
||||||
*/
|
*/
|
||||||
public function applyDatesToItems(bool $validateAvailability = true): self
|
public function applyDatesToItems(bool $validateAvailability = true): self
|
||||||
{
|
{
|
||||||
if (!$this->from_date || !$this->until_date) {
|
if (!$this->from || !$this->until) {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->items as $item) {
|
foreach ($this->items as $item) {
|
||||||
// Only apply to items without dates that are booking products
|
// Only apply to booking items that don't already have dates set
|
||||||
if ($item->is_booking && (!$item->from || !$item->until)) {
|
if ($item->is_booking && (!$item->from || !$item->until)) {
|
||||||
if ($validateAvailability) {
|
if ($validateAvailability) {
|
||||||
$product = $item->purchasable;
|
$product = $item->purchasable;
|
||||||
if ($product && !$product->isAvailableForBooking($this->from_date, $this->until_date, $item->quantity)) {
|
if ($product && !$product->isAvailableForBooking($this->from, $this->until, $item->quantity)) {
|
||||||
throw new NotEnoughAvailableInTimespanException(
|
throw new NotEnoughAvailableInTimespanException(
|
||||||
productName: $product->name ?? 'Product',
|
productName: $product->name ?? 'Product',
|
||||||
requested: $item->quantity,
|
requested: $item->quantity,
|
||||||
available: 0, // Could calculate actual available amount
|
available: 0, // Could calculate actual available amount
|
||||||
from: $this->from_date,
|
from: $this->from,
|
||||||
until: $this->until_date
|
until: $this->until
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$item->updateDates($this->from_date, $this->until_date);
|
$item->updateDates($this->from, $this->until);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -844,7 +846,7 @@ class Cart extends Model
|
||||||
|
|
||||||
public function checkout(): static
|
public function checkout(): static
|
||||||
{
|
{
|
||||||
return \DB::transaction(function () {
|
return DB::transaction(function () {
|
||||||
// Lock the cart to prevent concurrent checkouts
|
// Lock the cart to prevent concurrent checkouts
|
||||||
$this->lockForUpdate();
|
$this->lockForUpdate();
|
||||||
|
|
||||||
|
|
@ -949,6 +951,7 @@ class Cart extends Model
|
||||||
*
|
*
|
||||||
* This method:
|
* This method:
|
||||||
* - Validates the cart (doesn't convert it)
|
* - Validates the cart (doesn't convert it)
|
||||||
|
* - Creates ProductPurchase records for each cart item (with PENDING status)
|
||||||
* - Uses dynamic price_data for each cart item (no pre-created Stripe prices needed)
|
* - Uses dynamic price_data for each cart item (no pre-created Stripe prices needed)
|
||||||
* - Creates line items with descriptions including booking dates
|
* - Creates line items with descriptions including booking dates
|
||||||
* - Returns the Stripe checkout session
|
* - Returns the Stripe checkout session
|
||||||
|
|
@ -971,6 +974,40 @@ class Cart extends Model
|
||||||
// Validate cart before proceeding (doesn't convert it)
|
// Validate cart before proceeding (doesn't convert it)
|
||||||
$this->validateForCheckout();
|
$this->validateForCheckout();
|
||||||
|
|
||||||
|
// Create ProductPurchase records for each cart item
|
||||||
|
DB::transaction(function () {
|
||||||
|
foreach ($this->items as $item) {
|
||||||
|
// Skip if purchase already exists
|
||||||
|
if ($item->purchase_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product = $item->purchasable;
|
||||||
|
$from = $item->from;
|
||||||
|
$until = $item->until;
|
||||||
|
|
||||||
|
// Create purchase record with PENDING status
|
||||||
|
$purchase = ProductPurchase::create([
|
||||||
|
'cart_id' => $this->id,
|
||||||
|
'price_id' => $item->price_id,
|
||||||
|
'purchasable_id' => $product->id,
|
||||||
|
'purchasable_type' => get_class($product),
|
||||||
|
'purchaser_id' => $this->customer_id,
|
||||||
|
'purchaser_type' => $this->customer_type,
|
||||||
|
'quantity' => $item->quantity,
|
||||||
|
'amount' => $item->subtotal,
|
||||||
|
'amount_paid' => 0,
|
||||||
|
'status' => PurchaseStatus::PENDING,
|
||||||
|
'from' => $from,
|
||||||
|
'until' => $until,
|
||||||
|
'meta' => $item->meta,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Link purchase to cart item
|
||||||
|
$item->update(['purchase_id' => $purchase->id]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$lineItems = [];
|
$lineItems = [];
|
||||||
|
|
||||||
foreach ($this->items as $item) {
|
foreach ($this->items as $item) {
|
||||||
|
|
|
||||||
|
|
@ -342,7 +342,7 @@ class CartItem extends Model
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the effective 'from' date for this cart item.
|
* Get the effective 'from' date for this cart item.
|
||||||
* Returns the item's specific date if set, otherwise falls back to the cart's from_date.
|
* Returns the item's specific date if set, otherwise falls back to the cart's from.
|
||||||
*
|
*
|
||||||
* @return \Carbon\Carbon|null
|
* @return \Carbon\Carbon|null
|
||||||
*/
|
*/
|
||||||
|
|
@ -352,12 +352,12 @@ class CartItem extends Model
|
||||||
return $this->from;
|
return $this->from;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->cart?->from_date;
|
return $this->cart?->from;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the effective 'until' date for this cart item.
|
* Get the effective 'until' date for this cart item.
|
||||||
* Returns the item's specific date if set, otherwise falls back to the cart's until_date.
|
* Returns the item's specific date if set, otherwise falls back to the cart's until.
|
||||||
*
|
*
|
||||||
* @return \Carbon\Carbon|null
|
* @return \Carbon\Carbon|null
|
||||||
*/
|
*/
|
||||||
|
|
@ -367,7 +367,7 @@ class CartItem extends Model
|
||||||
return $this->until;
|
return $this->until;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->cart?->until_date;
|
return $this->cart?->until;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ class CartDateManagementTest extends TestCase
|
||||||
$cart->setDates($from, $until, validateAvailability: false);
|
$cart->setDates($from, $until, validateAvailability: false);
|
||||||
|
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
$this->assertEquals($from->toDateTimeString(), $cart->from_date->toDateTimeString());
|
$this->assertEquals($from->toDateTimeString(), $cart->from->toDateTimeString());
|
||||||
$this->assertEquals($until->toDateTimeString(), $cart->until_date->toDateTimeString());
|
$this->assertEquals($until->toDateTimeString(), $cart->until->toDateTimeString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
@ -48,7 +48,7 @@ class CartDateManagementTest extends TestCase
|
||||||
$cart->setFromDate($from, validateAvailability: false);
|
$cart->setFromDate($from, validateAvailability: false);
|
||||||
|
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
$this->assertEquals($from->toDateTimeString(), $cart->from_date->toDateTimeString());
|
$this->assertEquals($from->toDateTimeString(), $cart->from->toDateTimeString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
@ -60,14 +60,14 @@ class CartDateManagementTest extends TestCase
|
||||||
$cart->setUntilDate($until, validateAvailability: false);
|
$cart->setUntilDate($until, validateAvailability: false);
|
||||||
|
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
$this->assertEquals($until->toDateTimeString(), $cart->until_date->toDateTimeString());
|
$this->assertEquals($until->toDateTimeString(), $cart->until->toDateTimeString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function it_throws_exception_when_setting_from_date_after_existing_until_date()
|
public function it_throws_exception_when_setting_from_date_after_existing_until_date()
|
||||||
{
|
{
|
||||||
$cart = Cart::factory()->create([
|
$cart = Cart::factory()->create([
|
||||||
'until_date' => Carbon::now()->addDays(2),
|
'until' => Carbon::now()->addDays(2),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->expectException(InvalidDateRangeException::class);
|
$this->expectException(InvalidDateRangeException::class);
|
||||||
|
|
@ -78,7 +78,7 @@ class CartDateManagementTest extends TestCase
|
||||||
public function it_throws_exception_when_setting_until_date_before_existing_from_date()
|
public function it_throws_exception_when_setting_until_date_before_existing_from_date()
|
||||||
{
|
{
|
||||||
$cart = Cart::factory()->create([
|
$cart = Cart::factory()->create([
|
||||||
'from_date' => Carbon::now()->addDays(3),
|
'from' => Carbon::now()->addDays(3),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->expectException(InvalidDateRangeException::class);
|
$this->expectException(InvalidDateRangeException::class);
|
||||||
|
|
@ -102,8 +102,8 @@ class CartDateManagementTest extends TestCase
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$cart = Cart::factory()->create([
|
$cart = Cart::factory()->create([
|
||||||
'from_date' => Carbon::now()->addDays(1),
|
'from' => Carbon::now()->addDays(1),
|
||||||
'until_date' => Carbon::now()->addDays(3),
|
'until' => Carbon::now()->addDays(3),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$itemFromDate = Carbon::now()->addDays(5);
|
$itemFromDate = Carbon::now()->addDays(5);
|
||||||
|
|
@ -136,8 +136,8 @@ class CartDateManagementTest extends TestCase
|
||||||
$cartUntilDate = Carbon::now()->addDays(3);
|
$cartUntilDate = Carbon::now()->addDays(3);
|
||||||
|
|
||||||
$cart = Cart::factory()->create([
|
$cart = Cart::factory()->create([
|
||||||
'from_date' => $cartFromDate,
|
'from' => $cartFromDate,
|
||||||
'until_date' => $cartUntilDate,
|
'until' => $cartUntilDate,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$item = $cart->addToCart($product, 1);
|
$item = $cart->addToCart($product, 1);
|
||||||
|
|
@ -187,8 +187,8 @@ class CartDateManagementTest extends TestCase
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$cart = Cart::factory()->create([
|
$cart = Cart::factory()->create([
|
||||||
'from_date' => Carbon::now()->addDays(1),
|
'from' => Carbon::now()->addDays(1),
|
||||||
'until_date' => Carbon::now()->addDays(3),
|
'until' => Carbon::now()->addDays(3),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$item = $cart->addToCart($product, 1);
|
$item = $cart->addToCart($product, 1);
|
||||||
|
|
@ -283,8 +283,8 @@ class CartDateManagementTest extends TestCase
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$cart = Cart::factory()->create([
|
$cart = Cart::factory()->create([
|
||||||
'from_date' => Carbon::now()->addDays(1),
|
'from' => Carbon::now()->addDays(1),
|
||||||
'until_date' => Carbon::now()->addDays(3),
|
'until' => Carbon::now()->addDays(3),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$item = $cart->addToCart($product, 1);
|
$item = $cart->addToCart($product, 1);
|
||||||
|
|
@ -389,8 +389,8 @@ class CartDateManagementTest extends TestCase
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$cart = Cart::factory()->create([
|
$cart = Cart::factory()->create([
|
||||||
'from_date' => Carbon::now()->addDays(1),
|
'from' => Carbon::now()->addDays(1),
|
||||||
'until_date' => Carbon::now()->addDays(3),
|
'until' => Carbon::now()->addDays(3),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Add item that would exceed available stock
|
// Add item that would exceed available stock
|
||||||
|
|
@ -428,7 +428,7 @@ class CartDateManagementTest extends TestCase
|
||||||
validateAvailability: false
|
validateAvailability: false
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertNotNull($cart->from_date);
|
$this->assertNotNull($cart->from);
|
||||||
$this->assertNotNull($cart->until_date);
|
$this->assertNotNull($cart->until);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,10 @@ class CartDateStringParsingTest extends TestCase
|
||||||
{
|
{
|
||||||
$cart = $this->cart->setDates('2025-12-20', '2025-12-25', false);
|
$cart = $this->cart->setDates('2025-12-20', '2025-12-25', false);
|
||||||
|
|
||||||
$this->assertNotNull($cart->from_date);
|
$this->assertNotNull($cart->from);
|
||||||
$this->assertNotNull($cart->until_date);
|
$this->assertNotNull($cart->until);
|
||||||
$this->assertEquals('2025-12-20', $cart->from_date->format('Y-m-d'));
|
$this->assertEquals('2025-12-20', $cart->from->format('Y-m-d'));
|
||||||
$this->assertEquals('2025-12-25', $cart->until_date->format('Y-m-d'));
|
$this->assertEquals('2025-12-25', $cart->until->format('Y-m-d'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
@ -65,8 +65,8 @@ class CartDateStringParsingTest extends TestCase
|
||||||
|
|
||||||
$cart = $this->cart->setDates($from, $until, false);
|
$cart = $this->cart->setDates($from, $until, false);
|
||||||
|
|
||||||
$this->assertEquals('2025-12-20', $cart->from_date->format('Y-m-d'));
|
$this->assertEquals('2025-12-20', $cart->from->format('Y-m-d'));
|
||||||
$this->assertEquals('2025-12-25', $cart->until_date->format('Y-m-d'));
|
$this->assertEquals('2025-12-25', $cart->until->format('Y-m-d'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
@ -74,18 +74,18 @@ class CartDateStringParsingTest extends TestCase
|
||||||
{
|
{
|
||||||
$cart = $this->cart->setFromDate('2025-12-20', false);
|
$cart = $this->cart->setFromDate('2025-12-20', false);
|
||||||
|
|
||||||
$this->assertNotNull($cart->from_date);
|
$this->assertNotNull($cart->from);
|
||||||
$this->assertEquals('2025-12-20', $cart->from_date->format('Y-m-d'));
|
$this->assertEquals('2025-12-20', $cart->from->format('Y-m-d'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function cart_set_until_date_accepts_string()
|
public function cart_set_until_date_accepts_string()
|
||||||
{
|
{
|
||||||
$this->cart->update(['from_date' => Carbon::parse('2025-12-20')]);
|
$this->cart->update(['from' => Carbon::parse('2025-12-20')]);
|
||||||
$cart = $this->cart->setUntilDate('2025-12-25', false);
|
$cart = $this->cart->setUntilDate('2025-12-25', false);
|
||||||
|
|
||||||
$this->assertNotNull($cart->until_date);
|
$this->assertNotNull($cart->until);
|
||||||
$this->assertEquals('2025-12-25', $cart->until_date->format('Y-m-d'));
|
$this->assertEquals('2025-12-25', $cart->until->format('Y-m-d'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
@ -107,8 +107,8 @@ class CartDateStringParsingTest extends TestCase
|
||||||
|
|
||||||
$cart = $cart->setDates($from, $until, false);
|
$cart = $cart->setDates($from, $until, false);
|
||||||
|
|
||||||
$this->assertNotNull($cart->from_date, "Failed to parse: $from");
|
$this->assertNotNull($cart->from, "Failed to parse: $from");
|
||||||
$this->assertNotNull($cart->until_date, "Failed to parse: $until");
|
$this->assertNotNull($cart->until, "Failed to parse: $until");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,15 @@ class HtmlDateTimeCastTest extends TestCase
|
||||||
$cart = Cart::factory()->create();
|
$cart = Cart::factory()->create();
|
||||||
$date = Carbon::parse('2025-12-25 14:30:00');
|
$date = Carbon::parse('2025-12-25 14:30:00');
|
||||||
|
|
||||||
$cart->from_date = $date;
|
$cart->from = $date;
|
||||||
$cart->save();
|
$cart->save();
|
||||||
|
|
||||||
// Reload from database
|
// Reload from database
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
|
|
||||||
// Should return Carbon instance
|
// Should return Carbon instance
|
||||||
$this->assertInstanceOf(Carbon::class, $cart->from_date);
|
$this->assertInstanceOf(Carbon::class, $cart->from);
|
||||||
$this->assertEquals('2025-12-25 14:30:00', $cart->from_date->format('Y-m-d H:i:s'));
|
$this->assertEquals('2025-12-25 14:30:00', $cart->from->format('Y-m-d H:i:s'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
@ -33,13 +33,13 @@ class HtmlDateTimeCastTest extends TestCase
|
||||||
$cart = Cart::factory()->create();
|
$cart = Cart::factory()->create();
|
||||||
$date = new \DateTime('2025-12-25 14:30:00');
|
$date = new \DateTime('2025-12-25 14:30:00');
|
||||||
|
|
||||||
$cart->from_date = $date;
|
$cart->from = $date;
|
||||||
$cart->save();
|
$cart->save();
|
||||||
|
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
|
|
||||||
$this->assertInstanceOf(Carbon::class, $cart->from_date);
|
$this->assertInstanceOf(Carbon::class, $cart->from);
|
||||||
$this->assertEquals('2025-12-25 14:30:00', $cart->from_date->format('Y-m-d H:i:s'));
|
$this->assertEquals('2025-12-25 14:30:00', $cart->from->format('Y-m-d H:i:s'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
@ -48,13 +48,13 @@ class HtmlDateTimeCastTest extends TestCase
|
||||||
$cart = Cart::factory()->create();
|
$cart = Cart::factory()->create();
|
||||||
|
|
||||||
// Standard datetime string
|
// Standard datetime string
|
||||||
$cart->from_date = '2025-12-25 14:30:00';
|
$cart->from = '2025-12-25 14:30:00';
|
||||||
$cart->save();
|
$cart->save();
|
||||||
|
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
|
|
||||||
$this->assertInstanceOf(Carbon::class, $cart->from_date);
|
$this->assertInstanceOf(Carbon::class, $cart->from);
|
||||||
$this->assertEquals('2025-12-25 14:30:00', $cart->from_date->format('Y-m-d H:i:s'));
|
$this->assertEquals('2025-12-25 14:30:00', $cart->from->format('Y-m-d H:i:s'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
@ -63,26 +63,26 @@ class HtmlDateTimeCastTest extends TestCase
|
||||||
$cart = Cart::factory()->create();
|
$cart = Cart::factory()->create();
|
||||||
|
|
||||||
// HTML5 datetime-local format (YYYY-MM-DDTHH:MM)
|
// HTML5 datetime-local format (YYYY-MM-DDTHH:MM)
|
||||||
$cart->from_date = '2025-12-25T14:30';
|
$cart->from = '2025-12-25T14:30';
|
||||||
$cart->save();
|
$cart->save();
|
||||||
|
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
|
|
||||||
$this->assertInstanceOf(Carbon::class, $cart->from_date);
|
$this->assertInstanceOf(Carbon::class, $cart->from);
|
||||||
$this->assertEquals('2025-12-25 14:30:00', $cart->from_date->format('Y-m-d H:i:s'));
|
$this->assertEquals('2025-12-25 14:30:00', $cart->from->format('Y-m-d H:i:s'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function it_can_format_for_html5_input()
|
public function it_can_format_for_html5_input()
|
||||||
{
|
{
|
||||||
$cart = Cart::factory()->create();
|
$cart = Cart::factory()->create();
|
||||||
$cart->from_date = Carbon::parse('2025-12-25 14:30:00');
|
$cart->from = Carbon::parse('2025-12-25 14:30:00');
|
||||||
$cart->save();
|
$cart->save();
|
||||||
|
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
|
|
||||||
// Can format for HTML5 datetime-local input
|
// Can format for HTML5 datetime-local input
|
||||||
$htmlFormat = $cart->from_date->format('Y-m-d\TH:i');
|
$htmlFormat = $cart->from->format('Y-m-d\TH:i');
|
||||||
$this->assertEquals('2025-12-25T14:30', $htmlFormat);
|
$this->assertEquals('2025-12-25T14:30', $htmlFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,12 +90,12 @@ class HtmlDateTimeCastTest extends TestCase
|
||||||
public function it_handles_null_values()
|
public function it_handles_null_values()
|
||||||
{
|
{
|
||||||
$cart = Cart::factory()->create();
|
$cart = Cart::factory()->create();
|
||||||
$cart->from_date = null;
|
$cart->from = null;
|
||||||
$cart->save();
|
$cart->save();
|
||||||
|
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
|
|
||||||
$this->assertNull($cart->from_date);
|
$this->assertNull($cart->from);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
@ -128,28 +128,28 @@ class HtmlDateTimeCastTest extends TestCase
|
||||||
$cart = Cart::factory()->create();
|
$cart = Cart::factory()->create();
|
||||||
$timestamp = Carbon::parse('2025-12-25 14:30:00')->timestamp;
|
$timestamp = Carbon::parse('2025-12-25 14:30:00')->timestamp;
|
||||||
|
|
||||||
$cart->from_date = $timestamp;
|
$cart->from = $timestamp;
|
||||||
$cart->save();
|
$cart->save();
|
||||||
|
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
|
|
||||||
$this->assertInstanceOf(Carbon::class, $cart->from_date);
|
$this->assertInstanceOf(Carbon::class, $cart->from);
|
||||||
$this->assertEquals('2025-12-25 14:30:00', $cart->from_date->format('Y-m-d H:i:s'));
|
$this->assertEquals('2025-12-25 14:30:00', $cart->from->format('Y-m-d H:i:s'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @test */
|
/** @test */
|
||||||
public function it_maintains_carbon_methods()
|
public function it_maintains_carbon_methods()
|
||||||
{
|
{
|
||||||
$cart = Cart::factory()->create();
|
$cart = Cart::factory()->create();
|
||||||
$cart->from_date = Carbon::parse('2025-12-25 14:30:00');
|
$cart->from = Carbon::parse('2025-12-25 14:30:00');
|
||||||
$cart->save();
|
$cart->save();
|
||||||
|
|
||||||
$cart->refresh();
|
$cart->refresh();
|
||||||
|
|
||||||
// All Carbon methods should be available
|
// All Carbon methods should be available
|
||||||
$this->assertTrue($cart->from_date->isAfter(Carbon::parse('2025-12-24')));
|
$this->assertTrue($cart->from->isAfter(Carbon::parse('2025-12-24')));
|
||||||
$this->assertTrue($cart->from_date->isBefore(Carbon::parse('2025-12-26')));
|
$this->assertTrue($cart->from->isBefore(Carbon::parse('2025-12-26')));
|
||||||
$this->assertEquals('December', $cart->from_date->format('F'));
|
$this->assertEquals('December', $cart->from->format('F'));
|
||||||
$this->assertEquals('2025-12-25', $cart->from_date->toDateString());
|
$this->assertEquals('2025-12-25', $cart->from->toDateString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue