From a4cd4385309246ac13588c0a760f5c4294baf804 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 13 Mar 2026 09:51:51 +0000 Subject: [PATCH] Add re-import action to view page --- resources/lang/en/messages.php | 1 + src/Actions/ReimportProduct.php | 48 +++++++ src/ServiceProvider.php | 4 + src/Support/StoreConfig.php | 13 ++ tests/Unit/ReimportProductActionTest.php | 163 +++++++++++++++++++++++ 5 files changed, 229 insertions(+) create mode 100644 src/Actions/ReimportProduct.php create mode 100644 tests/Unit/ReimportProductActionTest.php diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php index 8012e0c..58ac186 100644 --- a/resources/lang/en/messages.php +++ b/resources/lang/en/messages.php @@ -8,4 +8,5 @@ 'option_title_price_nostock' => ':title - :price (Out of Stock)', 'option_title_nostock' => ':title (Out of Stock)', 'out_of_stock' => 'Out of Stock', + 'reimport_product' => 'Reimport from Shopify', ]; diff --git a/src/Actions/ReimportProduct.php b/src/Actions/ReimportProduct.php new file mode 100644 index 0000000..933fd8d --- /dev/null +++ b/src/Actions/ReimportProduct.php @@ -0,0 +1,48 @@ +context['view'] === 'form' + && $item instanceof Entry + && $item->collectionHandle() === config('shopify.collection_handle', 'products'); + } + + public function visibleToBulk($items) + { + return false; + } + + public function authorize($user, $item) + { + return $user->can('access shopify'); + } + + public function run($entries, $values) + { + $entries->each(function (Entry $entry) { + $storeHandle = null; + + if (StoreConfig::isMultiStore() && StoreConfig::getMode() === 'localized') { + $storeHandle = StoreConfig::findBySite($entry->locale())['handle'] ?? null; + } + + ImportSingleProductJob::dispatch((int) $entry->get('product_id'), [], $storeHandle); + }); + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 2ea316d..68c0078 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -26,6 +26,10 @@ class ServiceProvider extends AddonServiceProvider Commands\ShopifyWebhooksRegister::class, ]; + protected $actions = [ + Actions\ReimportProduct::class, + ]; + protected $fieldtypes = [ Fieldtypes\Variants::class, Fieldtypes\DisabledText::class, diff --git a/src/Support/StoreConfig.php b/src/Support/StoreConfig.php index bf0ecee..d3568b0 100644 --- a/src/Support/StoreConfig.php +++ b/src/Support/StoreConfig.php @@ -50,6 +50,19 @@ public static function findByHandle(string $handle): ?array return array_merge(['handle' => $handle], $stores[$handle]); } + public static function findBySite(string $siteHandle): ?array + { + $stores = config('shopify.multi_store.stores', []); + + foreach ($stores as $handle => $store) { + if (($store['site'] ?? null) === $siteHandle) { + return array_merge(['handle' => $handle], $store); + } + } + + return null; + } + public static function findByDomain(string $domain): ?array { $stores = config('shopify.multi_store.stores', []); diff --git a/tests/Unit/ReimportProductActionTest.php b/tests/Unit/ReimportProductActionTest.php new file mode 100644 index 0000000..41a555c --- /dev/null +++ b/tests/Unit/ReimportProductActionTest.php @@ -0,0 +1,163 @@ +collection(config('shopify.collection_handle', 'products')) + ->locale($locale) + ->slug('test-product') + ->data(['product_id' => $productId]); + + $entry->save(); + + return $entry; + } + + private function makeVariantEntry() + { + $entry = Facades\Entry::make() + ->collection('variants') + ->slug('test-variant') + ->data(['product_id' => 999]); + + $entry->save(); + + return $entry; + } + + private function actionWithContext(string $view = 'form'): ReimportProduct + { + return (new ReimportProduct)->context(['view' => $view]); + } + + #[Test] + public function is_visible_on_product_entry_form_view() + { + $entry = $this->makeProductEntry(); + + $this->assertTrue($this->actionWithContext('form')->visibleTo($entry)); + } + + #[Test] + public function is_not_visible_on_list_view() + { + $entry = $this->makeProductEntry(); + + $this->assertFalse($this->actionWithContext('list')->visibleTo($entry)); + } + + #[Test] + public function is_not_visible_on_non_product_entry() + { + $entry = $this->makeVariantEntry(); + + $this->assertFalse($this->actionWithContext('form')->visibleTo($entry)); + } + + #[Test] + public function is_not_visible_in_bulk() + { + $this->assertFalse($this->actionWithContext()->visibleToBulk(collect())); + } + + #[Test] + public function dispatches_import_job_with_product_id() + { + Queue::fake(); + + $entry = $this->makeProductEntry(productId: 456); + + $this->actionWithContext()->run(collect([$entry]), []); + + Queue::assertPushed(ImportSingleProductJob::class, function ($job) { + return $job->productId === 456 && $job->storeHandle === null; + }); + } + + #[Test] + public function dispatches_job_without_store_handle_in_single_store_mode() + { + Queue::fake(); + + config(['shopify.multi_store.enabled' => false]); + + $entry = $this->makeProductEntry(productId: 789); + + $this->actionWithContext()->run(collect([$entry]), []); + + Queue::assertPushed(ImportSingleProductJob::class, function ($job) { + return $job->productId === 789 && $job->storeHandle === null; + }); + } + + #[Test] + public function dispatches_job_with_store_handle_in_localized_multi_store_mode() + { + Queue::fake(); + + Facades\Site::setSites([ + 'en' => ['url' => '/', 'locale' => 'en_US'], + 'fr' => ['url' => '/fr/', 'locale' => 'fr_FR'], + ]); + + Facades\Collection::find(config('shopify.collection_handle', 'products'))->sites(['en', 'fr'])->save(); + + config(['shopify.multi_store' => [ + 'enabled' => true, + 'mode' => 'localized', + 'primary_store' => 'uk', + 'stores' => [ + 'uk' => ['url' => 'uk.myshopify.com', 'admin_token' => 'tok', 'site' => 'en'], + 'fr' => ['url' => 'fr.myshopify.com', 'admin_token' => 'tok', 'site' => 'fr'], + ], + ]]); + + $entry = Facades\Entry::make() + ->collection(config('shopify.collection_handle', 'products')) + ->locale('fr') + ->slug('test-product-fr') + ->data(['product_id' => 101]); + + $entry->save(); + + $this->actionWithContext()->run(collect([$entry]), []); + + Queue::assertPushed(ImportSingleProductJob::class, function ($job) { + return $job->productId === 101 && $job->storeHandle === 'fr'; + }); + } + + #[Test] + public function dispatches_job_without_store_handle_in_unified_multi_store_mode() + { + Queue::fake(); + + config(['shopify.multi_store' => [ + 'enabled' => true, + 'mode' => 'unified', + 'primary_store' => 'uk', + 'stores' => [ + 'uk' => ['url' => 'uk.myshopify.com', 'admin_token' => 'tok', 'site' => 'en'], + ], + ]]); + + $entry = $this->makeProductEntry(productId: 202); + + $this->actionWithContext()->run(collect([$entry]), []); + + Queue::assertPushed(ImportSingleProductJob::class, function ($job) { + return $job->productId === 202 && $job->storeHandle === null; + }); + } +}