A tests, I product action
This commit is contained in:
parent
c6c159a4ff
commit
a4fedcdb58
14
README.md
14
README.md
|
|
@ -60,13 +60,21 @@ $product = Product::create([
|
|||
'slug' => 'amazing-t-shirt',
|
||||
'sku' => 'TSH-001',
|
||||
'type' => 'simple',
|
||||
'price' => 29.99,
|
||||
'regular_price' => 29.99,
|
||||
'manage_stock' => true,
|
||||
'stock_quantity' => 100,
|
||||
'status' => 'published',
|
||||
]);
|
||||
|
||||
$product->prices()->create([
|
||||
'currency' => 'USD',
|
||||
'unit_amount' => 1999, // $19.99
|
||||
'sale_unit_amount' => 1499, // $14.99
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$product->stocks()->create([
|
||||
'quantity' => 100,
|
||||
]);
|
||||
|
||||
// Add translated name
|
||||
$product->setLocalized('name', 'Amazing T-Shirt', 'en');
|
||||
$product->setLocalized('description', 'A comfortable cotton t-shirt', 'en');
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@
|
|||
return [
|
||||
// Table names (customizable for multi-tenancy)
|
||||
'tables' => [
|
||||
'products' => 'products',
|
||||
'product_prices' => 'product_prices',
|
||||
'product_categories' => 'product_categories',
|
||||
'cart_items' => 'cart_items',
|
||||
'carts' => 'carts',
|
||||
'payment_methods' => 'payment_methods',
|
||||
'payment_provider_identities' => 'payment_provider_identities',
|
||||
'product_action_runs' => 'product_action_runs',
|
||||
'product_attributes' => 'product_attributes',
|
||||
'product_categories' => 'product_categories',
|
||||
'product_prices' => 'product_prices',
|
||||
'product_purchases' => 'product_purchases',
|
||||
'product_stocks' => 'product_stocks',
|
||||
'carts' => 'carts',
|
||||
'cart_items' => 'cart_items',
|
||||
'payment_provider_identities' => 'payment_provider_identities',
|
||||
'payment_methods' => 'payment_methods',
|
||||
'products' => 'products',
|
||||
],
|
||||
|
||||
// Model classes (allow overriding in main instance)
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ return new class extends Migration
|
|||
// Product category pivot table
|
||||
if (!Schema::hasTable(config('shop.tables.product_category_product', 'product_category_product'))) {
|
||||
Schema::create(config('shop.tables.product_category_product', 'product_category_product'), function (Blueprint $table) {
|
||||
$table->uuid('product_id');
|
||||
$table->uuid('product_id')->nullable();
|
||||
$table->uuid('product_category_id');
|
||||
$table->integer('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
|
@ -210,15 +210,20 @@ return new class extends Migration
|
|||
Schema::create(config('shop.tables.product_actions', 'product_actions'), function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->uuid('product_id');
|
||||
$table->string('action_type');
|
||||
$table->string('event')->default('purchased'); // purchased, refunded, etc.
|
||||
$table->json('events')->default('[]'); // e.g. ["purchased","paid","refunded","viewed"]
|
||||
$table->string('class'); // e.g. \App\...
|
||||
$table->string('method')->nullable(); // null means __invoke via constructor params
|
||||
$table->boolean('defer')->default(true); // queued when true, sync otherwise
|
||||
$table->json('parameters')->nullable();
|
||||
$table->boolean('active')->default(true);
|
||||
$table->integer('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['product_id', 'event', 'active']);
|
||||
$table->foreign('product_id')->references('id')->on(config('shop.tables.products', 'products'))->onDelete('cascade');
|
||||
$table->index(['product_id', 'active']);
|
||||
$table->foreign('product_id')
|
||||
->references('id')
|
||||
->on(config('shop.tables.products', 'products'))
|
||||
->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -338,6 +343,18 @@ return new class extends Migration
|
|||
->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
// ProductActionRuns table
|
||||
if (!Schema::hasTable(config('shop.tables.product_action_runs', 'product_action_runs'))) {
|
||||
Schema::create(config('shop.tables.product_action_runs', 'product_action_runs'), function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('action');
|
||||
$table->unsignedBigInteger('product_purchase_id')->nullable();
|
||||
$table->boolean('success')->default(false);
|
||||
$table->timestamps();
|
||||
$table->foreign('product_purchase_id')->references('id')->on(config('shop.tables.product_purchases', 'product_purchases'))->onDelete('set null');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -350,6 +367,7 @@ return new class extends Migration
|
|||
Schema::dropIfExists(config('shop.tables.cart_discounts', 'cart_discounts'));
|
||||
Schema::dropIfExists(config('shop.tables.cart_items', 'cart_items'));
|
||||
Schema::dropIfExists(config('shop.tables.carts', 'carts'));
|
||||
Schema::dropIfExists(config('shop.tables.product_action_runs', 'product_action_runs'));
|
||||
Schema::dropIfExists(config('shop.tables.product_purchases', 'product_purchases'));
|
||||
Schema::dropIfExists(config('shop.tables.product_actions', 'product_actions'));
|
||||
Schema::dropIfExists(config('shop.tables.product_category_product', 'product_category_product'));
|
||||
|
|
@ -358,7 +376,7 @@ return new class extends Migration
|
|||
Schema::dropIfExists(config('shop.tables.product_stocks', 'product_stocks'));
|
||||
Schema::dropIfExists(config('shop.tables.product_attributes', 'product_attributes'));
|
||||
Schema::dropIfExists(config('shop.tables.product_prices', 'product_prices'));
|
||||
Schema::dropIfExists('product_category_product');
|
||||
Schema::dropIfExists(config('shop.tables.product_category_product', 'product_category_product'));
|
||||
Schema::dropIfExists(config('shop.tables.product_categories', 'product_categories'));
|
||||
Schema::dropIfExists(config('shop.tables.products', 'products'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,22 +316,19 @@ class Product extends Model implements Purchasable, Cartable
|
|||
]);
|
||||
}
|
||||
|
||||
public function syncPricesDown()
|
||||
{
|
||||
if (config('shop.stripe.enabled') && config('shop.stripe.sync_prices')) {
|
||||
StripeService::syncProductPricesDown($this);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function getAvailableActions(): array
|
||||
{
|
||||
return ProductAction::getAvailableActions();
|
||||
}
|
||||
|
||||
public function callActions(string $event = 'purchased', ?ProductPurchase $productPurchase = null, array $additionalData = []): void
|
||||
public function callActions(string $event = 'purchased', ?ProductPurchase $productPurchase = null, array $additionalData = [])
|
||||
{
|
||||
ProductAction::callForProduct($this, $event, $productPurchase, $additionalData);
|
||||
return ProductAction::callForProduct(
|
||||
$this,
|
||||
$event,
|
||||
$productPurchase,
|
||||
$additionalData
|
||||
);
|
||||
}
|
||||
|
||||
public function relatedProducts(): BelongsToMany
|
||||
|
|
@ -529,7 +526,7 @@ class Product extends Model implements Purchasable, Cartable
|
|||
->where('product_id', $this->id);
|
||||
}
|
||||
|
||||
public function hasPrice() : bool
|
||||
public function hasPrice(): bool
|
||||
{
|
||||
return $this->prices()->exists();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,16 +13,20 @@ class ProductAction extends Model
|
|||
|
||||
protected $fillable = [
|
||||
'product_id',
|
||||
'event',
|
||||
'action_type',
|
||||
'events',
|
||||
'class',
|
||||
'method',
|
||||
'defer',
|
||||
'parameters',
|
||||
'active',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'events' => 'array',
|
||||
'parameters' => 'array',
|
||||
'active' => 'boolean',
|
||||
'defer' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
|
|
@ -37,6 +41,43 @@ class ProductAction extends Model
|
|||
return $this->belongsTo(config('shop.models.product', Product::class));
|
||||
}
|
||||
|
||||
public function runs()
|
||||
{
|
||||
return $this->morphMany(ProductActionRun::class, 'action');
|
||||
}
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
// Backward compatibility accessor: expose first event as 'event'
|
||||
public function getEventAttribute(): ?string
|
||||
{
|
||||
$events = $this->events ?? [];
|
||||
return is_array($events) ? ($events[0] ?? null) : null;
|
||||
}
|
||||
|
||||
// Backward compatibility mutators for legacy fields used in tests/factories
|
||||
public function setEventAttribute($value): void
|
||||
{
|
||||
// Ensure events array reflects provided single event
|
||||
$this->events = is_null($value) ? [] : [(string) $value];
|
||||
}
|
||||
|
||||
public function setActionTypeAttribute($value): void
|
||||
{
|
||||
if (is_string($value)) {
|
||||
// If a fully-qualified class is passed, use it; otherwise prefix with namespace config
|
||||
if (str_starts_with($value, '\\') || str_contains($value, '\\')) {
|
||||
$this->class = $value;
|
||||
} else {
|
||||
$namespace = config('shop.actions.namespace', 'App\\Jobs\\ProductAction');
|
||||
$this->class = $namespace . '\\' . $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function callForProduct(
|
||||
Product $product,
|
||||
string $event,
|
||||
|
|
@ -44,7 +85,7 @@ class ProductAction extends Model
|
|||
array $additionalData = []
|
||||
): void {
|
||||
$actions = $product->actions()
|
||||
->where('event', $event)
|
||||
->whereJsonContains('events', $event)
|
||||
->where('active', true)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
|
|
@ -53,21 +94,12 @@ class ProductAction extends Model
|
|||
return;
|
||||
}
|
||||
|
||||
$available_actions = self::getAvailableActions();
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$success = false;
|
||||
try {
|
||||
if (!isset($available_actions[$action->action_type])) {
|
||||
Log::warning('Product action not found', [
|
||||
'product_id' => $product->id,
|
||||
'event' => $event,
|
||||
'action_type' => $action->action_type,
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$namespace = config('shop.actions.namespace', 'App\\Jobs\\ProductAction');
|
||||
$action_job = $namespace . '\\' . $action->action_type;
|
||||
$class = $action->class;
|
||||
$method = $action->method;
|
||||
$defer = (bool) $action->defer;
|
||||
|
||||
$params = [
|
||||
'product' => $product,
|
||||
|
|
@ -77,46 +109,140 @@ class ProductAction extends Model
|
|||
...$additionalData,
|
||||
];
|
||||
|
||||
dispatch(new $action_job(...$params));
|
||||
} catch (\Exception $e) {
|
||||
// Skip if class is not defined
|
||||
if (empty($class) || !is_string($class)) {
|
||||
Log::warning('Product action class missing', [
|
||||
'product_id' => $product->id,
|
||||
'event' => $event,
|
||||
'action_id' => $action->id ?? null,
|
||||
]);
|
||||
|
||||
ProductActionRun::create([
|
||||
'action_id' => $action->id,
|
||||
'action_type' => ProductAction::class,
|
||||
'product_purchase_id' => $productPurchase?->id,
|
||||
'success' => false,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Defer via queue or call synchronously
|
||||
if ($defer) {
|
||||
// If a method is provided, dispatch a closure job calling the static method
|
||||
if ($method) {
|
||||
defer(
|
||||
fn() =>
|
||||
dispatch(function () use ($class, $method, $params, $action, $productPurchase) {
|
||||
if (!class_exists($class)) {
|
||||
Log::warning('Product action class not found for deferred static call', ['class' => $class, 'method' => $method]);
|
||||
|
||||
ProductActionRun::create([
|
||||
'action_id' => $action->id,
|
||||
'action_type' => ProductAction::class,
|
||||
'product_purchase_id' => $productPurchase?->id,
|
||||
'success' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
$class::$method(...$params);
|
||||
})
|
||||
);
|
||||
|
||||
continue;
|
||||
} else {
|
||||
// Assume class is a Job or invokable and can be dispatched directly
|
||||
if (class_exists($class)) {
|
||||
dispatch(new $class(...$params));
|
||||
} else {
|
||||
defer(
|
||||
fn() =>
|
||||
dispatch(function () use ($class, $action, $productPurchase) {
|
||||
Log::warning('Product action class not found for deferred job', ['class' => $class]);
|
||||
|
||||
ProductActionRun::create([
|
||||
'action_id' => $action->id,
|
||||
'action_type' => ProductAction::class,
|
||||
'product_purchase_id' => $productPurchase?->id,
|
||||
'success' => false,
|
||||
]);
|
||||
})
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// For deferred jobs, we assume success since they were dispatched
|
||||
$success = true;
|
||||
} else {
|
||||
if ($method) {
|
||||
// Call static method directly
|
||||
if (class_exists($class)) {
|
||||
$class::$method(...$params);
|
||||
$success = true;
|
||||
} else {
|
||||
Log::warning('Product action class not found for static call', ['class' => $class, 'method' => $method]);
|
||||
|
||||
ProductActionRun::create([
|
||||
'action_id' => $action->id,
|
||||
'action_type' => ProductAction::class,
|
||||
'product_purchase_id' => $productPurchase?->id,
|
||||
'success' => false,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Instantiate and invoke if invokable
|
||||
if (class_exists($class)) {
|
||||
$instance = new $class(...$params);
|
||||
if (is_callable($instance)) {
|
||||
$instance();
|
||||
$success = true;
|
||||
}
|
||||
} else {
|
||||
Log::warning('Product action class not found for direct instantiation', ['class' => $class]);
|
||||
|
||||
ProductActionRun::create([
|
||||
'action_id' => $action->id,
|
||||
'action_type' => ProductAction::class,
|
||||
'product_purchase_id' => $productPurchase?->id,
|
||||
'success' => false,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log successful action run
|
||||
ProductActionRun::create([
|
||||
'action_id' => $action->id,
|
||||
'action_type' => ProductAction::class,
|
||||
'product_purchase_id' => $productPurchase?->id,
|
||||
'success' => $success,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Error calling product action', [
|
||||
'product_id' => $product->id,
|
||||
'event' => $event,
|
||||
'action_type' => $action->action_type ?? 'unknown',
|
||||
'class' => $action->class ?? 'unknown',
|
||||
'method' => $action->method ?? null,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
// Log failed action run
|
||||
ProductActionRun::create([
|
||||
'action_id' => $action->id,
|
||||
'action_type' => ProductAction::class,
|
||||
'product_purchase_id' => $productPurchase?->id,
|
||||
'success' => false,
|
||||
]);
|
||||
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function execute(
|
||||
Product $product,
|
||||
?ProductPurchase $productPurchase = null,
|
||||
array $additionalData = []
|
||||
): void {
|
||||
$namespace = config('shop.actions.namespace', 'App\\Jobs\\ProductAction');
|
||||
$action_job = $namespace . '\\' . $this->action_type;
|
||||
|
||||
if (!class_exists($action_job)) {
|
||||
throw new \Exception("Action class {$action_job} not found");
|
||||
}
|
||||
|
||||
$params = [
|
||||
'product' => $product,
|
||||
'productPurchase' => $productPurchase,
|
||||
'event' => $this->event,
|
||||
...($this->parameters ?? []),
|
||||
...$additionalData,
|
||||
];
|
||||
|
||||
dispatch(new $action_job(...$params));
|
||||
}
|
||||
|
||||
public static function getAvailableActions(): array
|
||||
{
|
||||
return config('shop.actions.available', []);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Blax\Shop\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class ProductActionRun extends Model
|
||||
{
|
||||
protected $table = 'product_action_runs';
|
||||
protected $fillable = [
|
||||
'action_id',
|
||||
'action_type',
|
||||
'product_purchase_id',
|
||||
'success',
|
||||
];
|
||||
protected $casts = [
|
||||
'success' => 'boolean',
|
||||
];
|
||||
|
||||
public function action(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function productPurchase()
|
||||
{
|
||||
return $this->belongsTo(ProductPurchase::class, 'product_purchase_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -78,9 +78,45 @@ class ProductPurchase extends Model
|
|||
protected static function booted()
|
||||
{
|
||||
static::created(function ($productPurchase) {
|
||||
if ($productPurchase->status === 'completed' && $product = $productPurchase->product) {
|
||||
$product = ($productPurchase->purchasable instanceof Product)
|
||||
? $productPurchase->purchasable
|
||||
: null;
|
||||
|
||||
$product ??= ($productPurchase->purchasable instanceof ProductPrice)
|
||||
? $productPurchase->purchasable?->product
|
||||
: $product;
|
||||
|
||||
if ($productPurchase->status === 'completed' && $product) {
|
||||
$product->callActions('purchased', $productPurchase);
|
||||
}
|
||||
});
|
||||
|
||||
// updated purchase from unpaid to paid
|
||||
static::updated(function ($productPurchase) {
|
||||
$product = ($productPurchase->purchasable instanceof Product)
|
||||
? $productPurchase->purchasable
|
||||
: null;
|
||||
|
||||
$product ??= ($productPurchase->purchasable instanceof ProductPrice)
|
||||
? $productPurchase->purchasable?->product
|
||||
: $product;
|
||||
|
||||
|
||||
if ($productPurchase->status === 'completed' && $product) {
|
||||
$product->callActions('purchased', $productPurchase);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function actionRuns()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
ProductActionRun::class,
|
||||
ProductAction::class,
|
||||
'product_id', // Foreign key on ProductAction table...
|
||||
'action_id', // Foreign key on ProductActionRun table...
|
||||
'purchasable_id', // Local key on ProductPurchase table...
|
||||
'id' // Local key on ProductAction table...
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Blax\Shop\Traits;
|
||||
|
||||
use Blax\Shop\Contracts\Purchasable;
|
||||
use Blax\Shop\Exceptions\MultiplePurchaseOptions;
|
||||
use Blax\Shop\Exceptions\NotEnoughStockException;
|
||||
use Blax\Shop\Exceptions\NotPurchasable;
|
||||
|
|
|
|||
|
|
@ -264,8 +264,8 @@ class HasShoppingCapabilitiesTest extends TestCase
|
|||
|
||||
// Create a product action
|
||||
$product->actions()->create([
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'TestAction',
|
||||
'events' => ['purchased'],
|
||||
'class' => 'TestAction',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,19 +18,18 @@ class ProductActionTest extends TestCase
|
|||
{
|
||||
$product = Product::factory()->create();
|
||||
|
||||
$action = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\SendWelcomeEmail',
|
||||
'active' => true,
|
||||
'sort_order' => 10,
|
||||
$action = $product->actions()->create([
|
||||
'events' => ['purchased', 'refunded'],
|
||||
'class' => 'App\\Actions\\SendWelcomeEmail',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('product_actions', [
|
||||
'id' => $action->id,
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
]);
|
||||
|
||||
$this->assertContains('purchased', $action->events ?? []);
|
||||
$this->assertContains('refunded', $action->events ?? []);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
|
@ -40,16 +39,14 @@ class ProductActionTest extends TestCase
|
|||
|
||||
ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\SendWelcomeEmail',
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\SendWelcomeEmail',
|
||||
]);
|
||||
|
||||
ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\GrantAccess',
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\GrantAccess',
|
||||
]);
|
||||
|
||||
$this->assertCount(2, $product->fresh()->actions);
|
||||
|
|
@ -62,9 +59,8 @@ class ProductActionTest extends TestCase
|
|||
|
||||
$action = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\TestAction',
|
||||
'active' => true,
|
||||
'event' => ['purchased'],
|
||||
'class' => 'App\\Actions\\TestAction',
|
||||
]);
|
||||
|
||||
$this->assertEquals($product->id, $action->product->id);
|
||||
|
|
@ -77,11 +73,12 @@ class ProductActionTest extends TestCase
|
|||
|
||||
$action = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\TestAction',
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\TestAction',
|
||||
]);
|
||||
|
||||
$action->refresh();
|
||||
|
||||
$this->assertTrue($action->active);
|
||||
|
||||
$action->update(['active' => false]);
|
||||
|
|
@ -96,14 +93,13 @@ class ProductActionTest extends TestCase
|
|||
|
||||
$action = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\SendEmail',
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\SendEmail',
|
||||
'parameters' => [
|
||||
'template' => 'welcome',
|
||||
'delay' => 60,
|
||||
'subject' => 'Welcome to our service',
|
||||
],
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$action = $action->fresh();
|
||||
|
|
@ -120,18 +116,14 @@ class ProductActionTest extends TestCase
|
|||
|
||||
$action1 = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\FirstAction',
|
||||
'sort_order' => 1,
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\FirstAction',
|
||||
]);
|
||||
|
||||
$action2 = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\SecondAction',
|
||||
'sort_order' => 2,
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\SecondAction',
|
||||
]);
|
||||
|
||||
$sorted = ProductAction::where('product_id', $product->id)
|
||||
|
|
@ -149,20 +141,20 @@ class ProductActionTest extends TestCase
|
|||
|
||||
$purchasedAction = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\OnPurchase',
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\OnPurchase',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$refundedAction = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'refunded',
|
||||
'action_type' => 'App\\Actions\\OnRefund',
|
||||
'events' => ['refunded'],
|
||||
'class' => 'App\\Actions\\OnRefund',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals('purchased', $purchasedAction->event);
|
||||
$this->assertEquals('refunded', $refundedAction->event);
|
||||
$this->assertContains('purchased', $purchasedAction->events);
|
||||
$this->assertContains('refunded', $refundedAction->events);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
|
@ -172,27 +164,24 @@ class ProductActionTest extends TestCase
|
|||
|
||||
ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\OnPurchase',
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\OnPurchase',
|
||||
]);
|
||||
|
||||
ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\AnotherPurchase',
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\OnPurchase',
|
||||
]);
|
||||
|
||||
ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'refunded',
|
||||
'action_type' => 'App\\Actions\\OnRefund',
|
||||
'active' => true,
|
||||
'events' => ['refunded'],
|
||||
'class' => 'App\\Actions\\OnPurchase',
|
||||
]);
|
||||
|
||||
$purchaseActions = ProductAction::where('product_id', $product->id)
|
||||
->where('event', 'purchased')
|
||||
->whereJsonContains('events', 'purchased')
|
||||
->get();
|
||||
|
||||
$this->assertCount(2, $purchaseActions);
|
||||
|
|
@ -205,15 +194,14 @@ class ProductActionTest extends TestCase
|
|||
|
||||
ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\EnabledAction',
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\EnabledAction',
|
||||
]);
|
||||
|
||||
ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\DisabledAction',
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\DisabledAction',
|
||||
'active' => false,
|
||||
]);
|
||||
|
||||
|
|
@ -232,16 +220,14 @@ class ProductActionTest extends TestCase
|
|||
|
||||
ProductAction::create([
|
||||
'product_id' => $product1->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\CommonAction',
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\CommonAction',
|
||||
]);
|
||||
|
||||
ProductAction::create([
|
||||
'product_id' => $product2->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\CommonAction',
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\CommonAction',
|
||||
]);
|
||||
|
||||
$this->assertCount(1, $product1->actions);
|
||||
|
|
@ -255,12 +241,11 @@ class ProductActionTest extends TestCase
|
|||
|
||||
$action = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\TestAction',
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\TestAction',
|
||||
'parameters' => [
|
||||
'key' => 'old_value'
|
||||
],
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$action->update([
|
||||
|
|
@ -284,8 +269,8 @@ class ProductActionTest extends TestCase
|
|||
|
||||
$action = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\TestAction',
|
||||
'event' => ['purchased'],
|
||||
'class' => 'App\\Actions\\TestAction',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
|
|
@ -303,9 +288,8 @@ class ProductActionTest extends TestCase
|
|||
|
||||
$action = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\SimpleAction',
|
||||
'active' => true,
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\SimpleAction',
|
||||
]);
|
||||
|
||||
$this->assertNull($action->parameters);
|
||||
|
|
@ -318,24 +302,24 @@ class ProductActionTest extends TestCase
|
|||
|
||||
$high = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\HighPriority',
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\HighPriority',
|
||||
'sort_order' => 100,
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$medium = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\MediumPriority',
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\MediumPriority',
|
||||
'sort_order' => 50,
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$low = ProductAction::create([
|
||||
'product_id' => $product->id,
|
||||
'event' => 'purchased',
|
||||
'action_type' => 'App\\Actions\\LowPriority',
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\LowPriority',
|
||||
'sort_order' => 10,
|
||||
'active' => true,
|
||||
]);
|
||||
|
|
@ -348,4 +332,30 @@ class ProductActionTest extends TestCase
|
|||
$this->assertEquals($medium->id, $ordered[1]->id);
|
||||
$this->assertEquals($high->id, $ordered[2]->id);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_be_triggered_on_purchase()
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$product = Product::factory()
|
||||
->withStocks()
|
||||
->withPrices(1, 5000)
|
||||
->create();
|
||||
|
||||
$product->actions()->create([
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\SendThankYouEmail',
|
||||
'defer' => true,
|
||||
]);
|
||||
|
||||
$product->actions()->create([
|
||||
'events' => ['purchased'],
|
||||
'class' => 'App\\Actions\\SendThankYouEmail',
|
||||
'defer' => false,
|
||||
]);
|
||||
|
||||
$purchase = $user->purchase($product, 1);
|
||||
|
||||
$this->assertEquals(1, $purchase->actionRuns()->count());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue