Add catalog sync trigger, metrics, and shipping label actions to integrations.
Deploy Ladill Woo Manager / deploy (push) Successful in 1m5s
Deploy Ladill Woo Manager / deploy (push) Successful in 1m5s
Surfaces actionable controls in Ladill instead of status-only cards: sync catalogs via the plugin proxy, show channel metrics, and link to orders/labels for fulfillment. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Woo;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Controllers\Woo\Concerns\ResolvesWooContext;
|
use App\Http\Controllers\Woo\Concerns\ResolvesWooContext;
|
||||||
use App\Services\Woo\IntegrationStatusService;
|
use App\Services\Woo\IntegrationStatusService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ class IntegrationController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function refresh(Request $request): \Illuminate\Http\RedirectResponse
|
public function refresh(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$store = $this->currentStore($request);
|
$store = $this->currentStore($request);
|
||||||
if ($store) {
|
if ($store) {
|
||||||
@@ -40,4 +41,14 @@ class IntegrationController extends Controller
|
|||||||
? 'Integration status refreshed.'
|
? 'Integration status refreshed.'
|
||||||
: 'Connect a store to refresh integration status.');
|
: 'Connect a store to refresh integration status.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function sync(Request $request, string $integration): RedirectResponse
|
||||||
|
{
|
||||||
|
$store = $this->currentStore($request);
|
||||||
|
$result = $this->integrations->syncCatalog($store, $integration);
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route('woo.integrations.index')
|
||||||
|
->with($result['success'] ? 'success' : 'error', $result['message']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,60 @@ class IntegrationStatusService
|
|||||||
Cache::forget($this->cacheKey($store));
|
Cache::forget($this->cacheKey($store));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{success: bool, message: string}
|
||||||
|
*/
|
||||||
|
public function syncCatalog(?WooStore $store, string $key): array
|
||||||
|
{
|
||||||
|
$catalog = config('woo_integrations', []);
|
||||||
|
|
||||||
|
if (! $store) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Connect a store before syncing catalogs.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($catalog[$key]) || ! ($catalog[$key]['supports_catalog_sync'] ?? false)) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Catalog sync is not available for this integration.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->client->postWithDetails($store, 'integrations/'.$key.'/sync');
|
||||||
|
|
||||||
|
if (! $response['reachable']) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Could not reach your store. Check that the Ladill plugin is active.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = $response['body'] ?? [];
|
||||||
|
|
||||||
|
if (! $response['successful']) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => (string) ($body['message'] ?? 'Catalog sync could not be started.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! ($body['success'] ?? false)) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => (string) ($body['message'] ?? 'Catalog sync could not be started.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->forgetCache($store);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'message' => (string) ($body['message'] ?? 'Catalog sync started on your store.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array<string, mixed>> $catalog
|
* @param array<string, array<string, mixed>> $catalog
|
||||||
* @return array{
|
* @return array{
|
||||||
@@ -113,6 +167,9 @@ class IntegrationStatusService
|
|||||||
'category' => (string) ($meta['category'] ?? 'Integration'),
|
'category' => (string) ($meta['category'] ?? 'Integration'),
|
||||||
'install_url' => $meta['install_url'] ?? null,
|
'install_url' => $meta['install_url'] ?? null,
|
||||||
'docs_url' => $meta['docs_url'] ?? null,
|
'docs_url' => $meta['docs_url'] ?? null,
|
||||||
|
'supports_catalog_sync' => (bool) ($meta['supports_catalog_sync'] ?? false),
|
||||||
|
'supports_metrics' => (bool) ($meta['supports_metrics'] ?? false),
|
||||||
|
'supports_label_actions' => (bool) ($meta['supports_label_actions'] ?? false),
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
'installed' => (bool) ($remoteStatus['installed'] ?? false),
|
'installed' => (bool) ($remoteStatus['installed'] ?? false),
|
||||||
'connected' => (bool) ($remoteStatus['connected'] ?? false),
|
'connected' => (bool) ($remoteStatus['connected'] ?? false),
|
||||||
@@ -121,6 +178,7 @@ class IntegrationStatusService
|
|||||||
: 'Could not reach your store. Check that the Ladill plugin is active.',
|
: 'Could not reach your store. Check that the Ladill plugin is active.',
|
||||||
'manage_url' => is_string($manageUrl) && $manageUrl !== '' ? $manageUrl : null,
|
'manage_url' => is_string($manageUrl) && $manageUrl !== '' ? $manageUrl : null,
|
||||||
'details' => is_array($remoteStatus['details'] ?? null) ? $remoteStatus['details'] : [],
|
'details' => is_array($remoteStatus['details'] ?? null) ? $remoteStatus['details'] : [],
|
||||||
|
'metrics' => is_array($remoteStatus['metrics'] ?? null) ? $remoteStatus['metrics'] : [],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,12 +198,16 @@ class IntegrationStatusService
|
|||||||
'category' => (string) ($meta['category'] ?? 'Integration'),
|
'category' => (string) ($meta['category'] ?? 'Integration'),
|
||||||
'install_url' => $meta['install_url'] ?? null,
|
'install_url' => $meta['install_url'] ?? null,
|
||||||
'docs_url' => $meta['docs_url'] ?? null,
|
'docs_url' => $meta['docs_url'] ?? null,
|
||||||
|
'supports_catalog_sync' => (bool) ($meta['supports_catalog_sync'] ?? false),
|
||||||
|
'supports_metrics' => (bool) ($meta['supports_metrics'] ?? false),
|
||||||
|
'supports_label_actions' => (bool) ($meta['supports_label_actions'] ?? false),
|
||||||
'state' => 'no_store',
|
'state' => 'no_store',
|
||||||
'installed' => false,
|
'installed' => false,
|
||||||
'connected' => false,
|
'connected' => false,
|
||||||
'summary' => 'Connect a WooCommerce store to see status.',
|
'summary' => 'Connect a WooCommerce store to see status.',
|
||||||
'manage_url' => null,
|
'manage_url' => null,
|
||||||
'details' => [],
|
'details' => [],
|
||||||
|
'metrics' => [],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,43 @@ class PluginClient
|
|||||||
return $this->request($store, 'POST', $path, $payload);
|
return $this->request($store, 'POST', $path, $payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $payload
|
||||||
|
* @return array{reachable: bool, successful: bool, body: ?array<string, mixed>}
|
||||||
|
*/
|
||||||
|
public function postWithDetails(WooStore $store, string $path, array $payload = []): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$response = $this->raw($store, 'POST', $path, $payload);
|
||||||
|
} catch (ConnectionException $e) {
|
||||||
|
$this->logConnectionFailure($store, 'POST', $path, $e);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'reachable' => false,
|
||||||
|
'successful' => false,
|
||||||
|
'body' => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array<string, mixed>|null $json */
|
||||||
|
$json = $response->json();
|
||||||
|
|
||||||
|
if (! $response->successful()) {
|
||||||
|
Log::warning('Woo plugin API request failed', [
|
||||||
|
'store_id' => $store->id,
|
||||||
|
'method' => 'POST',
|
||||||
|
'path' => $path,
|
||||||
|
'status' => $response->status(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'reachable' => true,
|
||||||
|
'successful' => $response->successful(),
|
||||||
|
'body' => is_array($json) ? $json : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/** @param array<string, mixed> $payload */
|
/** @param array<string, mixed> $payload */
|
||||||
public function put(WooStore $store, string $path, array $payload = []): ?array
|
public function put(WooStore $store, string $path, array $payload = []): ?array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ return [
|
|||||||
'category' => 'Fulfillment',
|
'category' => 'Fulfillment',
|
||||||
'install_url' => null,
|
'install_url' => null,
|
||||||
'docs_url' => 'https://woocommerce.com/document/setting-up-shipping-zones/',
|
'docs_url' => 'https://woocommerce.com/document/setting-up-shipping-zones/',
|
||||||
|
'supports_metrics' => true,
|
||||||
|
'supports_label_actions' => true,
|
||||||
],
|
],
|
||||||
'google' => [
|
'google' => [
|
||||||
'name' => 'Google for WooCommerce',
|
'name' => 'Google for WooCommerce',
|
||||||
@@ -14,6 +16,8 @@ return [
|
|||||||
'category' => 'Marketing',
|
'category' => 'Marketing',
|
||||||
'install_url' => 'https://wordpress.org/plugins/google-listings-and-ads/',
|
'install_url' => 'https://wordpress.org/plugins/google-listings-and-ads/',
|
||||||
'docs_url' => 'https://woocommerce.com/document/google-for-woocommerce/',
|
'docs_url' => 'https://woocommerce.com/document/google-for-woocommerce/',
|
||||||
|
'supports_catalog_sync' => true,
|
||||||
|
'supports_metrics' => true,
|
||||||
],
|
],
|
||||||
'pinterest' => [
|
'pinterest' => [
|
||||||
'name' => 'Pinterest for WooCommerce',
|
'name' => 'Pinterest for WooCommerce',
|
||||||
@@ -21,6 +25,8 @@ return [
|
|||||||
'category' => 'Marketing',
|
'category' => 'Marketing',
|
||||||
'install_url' => 'https://wordpress.org/plugins/pinterest-for-woocommerce/',
|
'install_url' => 'https://wordpress.org/plugins/pinterest-for-woocommerce/',
|
||||||
'docs_url' => 'https://woocommerce.com/document/pinterest-for-woocommerce/',
|
'docs_url' => 'https://woocommerce.com/document/pinterest-for-woocommerce/',
|
||||||
|
'supports_catalog_sync' => true,
|
||||||
|
'supports_metrics' => true,
|
||||||
],
|
],
|
||||||
'snapchat' => [
|
'snapchat' => [
|
||||||
'name' => 'Snapchat for WooCommerce',
|
'name' => 'Snapchat for WooCommerce',
|
||||||
@@ -28,6 +34,8 @@ return [
|
|||||||
'category' => 'Marketing',
|
'category' => 'Marketing',
|
||||||
'install_url' => 'https://wordpress.org/plugins/snapchat-for-woocommerce/',
|
'install_url' => 'https://wordpress.org/plugins/snapchat-for-woocommerce/',
|
||||||
'docs_url' => 'https://businesshelp.snapchat.com/s/article/woocommerce',
|
'docs_url' => 'https://businesshelp.snapchat.com/s/article/woocommerce',
|
||||||
|
'supports_catalog_sync' => true,
|
||||||
|
'supports_metrics' => true,
|
||||||
],
|
],
|
||||||
'tiktok' => [
|
'tiktok' => [
|
||||||
'name' => 'TikTok for WooCommerce',
|
'name' => 'TikTok for WooCommerce',
|
||||||
@@ -35,6 +43,8 @@ return [
|
|||||||
'category' => 'Marketing',
|
'category' => 'Marketing',
|
||||||
'install_url' => 'https://wordpress.org/plugins/tiktok-for-business/',
|
'install_url' => 'https://wordpress.org/plugins/tiktok-for-business/',
|
||||||
'docs_url' => 'https://woocommerce.com/document/tiktok-for-woocommerce/',
|
'docs_url' => 'https://woocommerce.com/document/tiktok-for-woocommerce/',
|
||||||
|
'supports_catalog_sync' => true,
|
||||||
|
'supports_metrics' => true,
|
||||||
],
|
],
|
||||||
'reddit' => [
|
'reddit' => [
|
||||||
'name' => 'Reddit for WooCommerce',
|
'name' => 'Reddit for WooCommerce',
|
||||||
@@ -42,6 +52,8 @@ return [
|
|||||||
'category' => 'Marketing',
|
'category' => 'Marketing',
|
||||||
'install_url' => 'https://wordpress.org/plugins/reddit-for-woocommerce/',
|
'install_url' => 'https://wordpress.org/plugins/reddit-for-woocommerce/',
|
||||||
'docs_url' => 'https://woocommerce.com/document/reddit-for-woocommerce/',
|
'docs_url' => 'https://woocommerce.com/document/reddit-for-woocommerce/',
|
||||||
|
'supports_catalog_sync' => true,
|
||||||
|
'supports_metrics' => true,
|
||||||
],
|
],
|
||||||
'facebook' => [
|
'facebook' => [
|
||||||
'name' => 'Meta for WooCommerce',
|
'name' => 'Meta for WooCommerce',
|
||||||
@@ -49,5 +61,7 @@ return [
|
|||||||
'category' => 'Marketing',
|
'category' => 'Marketing',
|
||||||
'install_url' => 'https://wordpress.org/plugins/facebook-for-woocommerce/',
|
'install_url' => 'https://wordpress.org/plugins/facebook-for-woocommerce/',
|
||||||
'docs_url' => 'https://woocommerce.com/document/facebook-for-woocommerce/',
|
'docs_url' => 'https://woocommerce.com/document/facebook-for-woocommerce/',
|
||||||
|
'supports_catalog_sync' => true,
|
||||||
|
'supports_metrics' => true,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -29,6 +29,18 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800">
|
||||||
|
{{ session('success') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-800">
|
||||||
|
{{ session('error') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
@if(! $store)
|
@if(! $store)
|
||||||
<div class="rounded-2xl border border-dashed border-slate-200 bg-white px-6 py-10 text-center">
|
<div class="rounded-2xl border border-dashed border-slate-200 bg-white px-6 py-10 text-center">
|
||||||
<p class="text-sm text-slate-600">No active store selected.</p>
|
<p class="text-sm text-slate-600">No active store selected.</p>
|
||||||
@@ -48,6 +60,7 @@
|
|||||||
@foreach($integrations as $integration)
|
@foreach($integrations as $integration)
|
||||||
@php
|
@php
|
||||||
$state = $integration['state'];
|
$state = $integration['state'];
|
||||||
|
$metrics = $integration['metrics'] ?? [];
|
||||||
$badge = match ($state) {
|
$badge = match ($state) {
|
||||||
'connected' => ['Connected', 'bg-emerald-50 text-emerald-700 ring-emerald-200'],
|
'connected' => ['Connected', 'bg-emerald-50 text-emerald-700 ring-emerald-200'],
|
||||||
'available', 'needs_setup' => $integration['installed']
|
'available', 'needs_setup' => $integration['installed']
|
||||||
@@ -73,6 +86,47 @@
|
|||||||
<p class="mt-3 text-sm text-slate-500">{{ $integration['summary'] }}</p>
|
<p class="mt-3 text-sm text-slate-500">{{ $integration['summary'] }}</p>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if(! empty($metrics['available']))
|
||||||
|
<dl class="mt-4 grid grid-cols-2 gap-3 rounded-xl bg-slate-50 p-3 text-xs">
|
||||||
|
@if(isset($metrics['published_products']))
|
||||||
|
<div>
|
||||||
|
<dt class="font-medium text-slate-500">Published products</dt>
|
||||||
|
<dd class="mt-0.5 text-sm font-semibold text-slate-900">{{ number_format((int) $metrics['published_products']) }}</dd>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if(isset($metrics['synced_products']))
|
||||||
|
<div>
|
||||||
|
<dt class="font-medium text-slate-500">Synced to channel</dt>
|
||||||
|
<dd class="mt-0.5 text-sm font-semibold text-slate-900">{{ number_format((int) $metrics['synced_products']) }}</dd>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if(isset($metrics['pending_products']))
|
||||||
|
<div>
|
||||||
|
<dt class="font-medium text-slate-500">Pending sync</dt>
|
||||||
|
<dd class="mt-0.5 text-sm font-semibold text-slate-900">{{ number_format((int) $metrics['pending_products']) }}</dd>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if(isset($metrics['orders_awaiting_fulfillment']))
|
||||||
|
<div>
|
||||||
|
<dt class="font-medium text-slate-500">Awaiting fulfillment</dt>
|
||||||
|
<dd class="mt-0.5 text-sm font-semibold text-slate-900">{{ number_format((int) $metrics['orders_awaiting_fulfillment']) }}</dd>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if(isset($metrics['labeled_orders']))
|
||||||
|
<div>
|
||||||
|
<dt class="font-medium text-slate-500">Labeled orders</dt>
|
||||||
|
<dd class="mt-0.5 text-sm font-semibold text-slate-900">{{ number_format((int) $metrics['labeled_orders']) }}</dd>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if(! empty($metrics['catalog_sync_status']) && $metrics['catalog_sync_status'] !== 'unknown')
|
||||||
|
<div class="col-span-2">
|
||||||
|
<dt class="font-medium text-slate-500">Catalog sync</dt>
|
||||||
|
<dd class="mt-0.5 text-sm font-semibold capitalize text-slate-900">{{ str_replace('_', ' ', $metrics['catalog_sync_status']) }}</dd>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</dl>
|
||||||
|
@endif
|
||||||
|
|
||||||
@if($integration['key'] === 'shipping' && ! empty($integration['details']['woocommerce_shipping']))
|
@if($integration['key'] === 'shipping' && ! empty($integration['details']['woocommerce_shipping']))
|
||||||
<p class="mt-2 text-xs text-slate-500">
|
<p class="mt-2 text-xs text-slate-500">
|
||||||
WooCommerce Shipping
|
WooCommerce Shipping
|
||||||
@@ -85,6 +139,30 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="mt-5 flex flex-wrap items-center gap-2">
|
<div class="mt-5 flex flex-wrap items-center gap-2">
|
||||||
|
@if($integration['supports_catalog_sync'] && $integration['connected'] && $reachable)
|
||||||
|
<form method="post" action="{{ route('woo.integrations.sync', $integration['key']) }}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit"
|
||||||
|
class="inline-flex items-center rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white hover:bg-slate-800">
|
||||||
|
Sync catalog
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($integration['supports_label_actions'] && $store)
|
||||||
|
<a href="{{ route('woo.orders.index') }}"
|
||||||
|
class="inline-flex items-center rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">
|
||||||
|
View orders
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($integration['key'] === 'shipping' && ! empty($integration['details']['labels_manage_url']))
|
||||||
|
<a href="{{ $integration['details']['labels_manage_url'] }}" target="_blank" rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">
|
||||||
|
Manage labels
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
@if($integration['manage_url'])
|
@if($integration['manage_url'])
|
||||||
<a href="{{ $integration['manage_url'] }}" target="_blank" rel="noopener noreferrer"
|
<a href="{{ $integration['manage_url'] }}" target="_blank" rel="noopener noreferrer"
|
||||||
class="inline-flex items-center rounded-xl bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-700">
|
class="inline-flex items-center rounded-xl bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-700">
|
||||||
@@ -109,7 +187,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm text-slate-600">
|
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm text-slate-600">
|
||||||
Ladill surfaces official WooCommerce extensions installed on your store. OAuth, catalog sync, and ad setup still happen in WordPress — open <strong>Manage in WordPress</strong> to finish configuration.
|
Ladill surfaces official WooCommerce extensions on your store. Use <strong>Sync catalog</strong> to trigger a product feed sync from Ladill, or open <strong>Manage in WordPress</strong> for OAuth, ads, and advanced setup.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</x-user-layout>
|
</x-user-layout>
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ Route::middleware(['auth', 'platform.session'])->group(function () {
|
|||||||
Route::delete('/stores/{store}', [StoreController::class, 'destroy'])->name('woo.stores.destroy');
|
Route::delete('/stores/{store}', [StoreController::class, 'destroy'])->name('woo.stores.destroy');
|
||||||
Route::get('/integrations', [IntegrationController::class, 'index'])->name('woo.integrations.index');
|
Route::get('/integrations', [IntegrationController::class, 'index'])->name('woo.integrations.index');
|
||||||
Route::post('/integrations/refresh', [IntegrationController::class, 'refresh'])->name('woo.integrations.refresh');
|
Route::post('/integrations/refresh', [IntegrationController::class, 'refresh'])->name('woo.integrations.refresh');
|
||||||
|
Route::post('/integrations/{integration}/sync', [IntegrationController::class, 'sync'])->name('woo.integrations.sync');
|
||||||
|
Route::post('/integrations/{integration}/sync', [IntegrationController::class, 'sync'])->name('woo.integrations.sync');
|
||||||
Route::get('/settings', [SettingsController::class, 'edit'])->name('woo.settings');
|
Route::get('/settings', [SettingsController::class, 'edit'])->name('woo.settings');
|
||||||
Route::post('/settings/team', [StoreMemberController::class, 'store'])->name('woo.team.store');
|
Route::post('/settings/team', [StoreMemberController::class, 'store'])->name('woo.team.store');
|
||||||
Route::delete('/settings/team/{member}', [StoreMemberController::class, 'destroy'])->name('woo.team.destroy');
|
Route::delete('/settings/team/{member}', [StoreMemberController::class, 'destroy'])->name('woo.team.destroy');
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class WooIntegrationsTest extends TestCase
|
|||||||
'manage_url' => 'https://shop.example.com/wp-admin/admin.php?page=wc-admin&path=/google/start',
|
'manage_url' => 'https://shop.example.com/wp-admin/admin.php?page=wc-admin&path=/google/start',
|
||||||
'summary' => 'Installed — finish setup in WordPress.',
|
'summary' => 'Installed — finish setup in WordPress.',
|
||||||
'details' => [],
|
'details' => [],
|
||||||
|
'metrics' => ['available' => true, 'published_products' => 42],
|
||||||
],
|
],
|
||||||
'pinterest' => [
|
'pinterest' => [
|
||||||
'state' => 'not_installed',
|
'state' => 'not_installed',
|
||||||
@@ -170,4 +171,102 @@ class WooIntegrationsTest extends TestCase
|
|||||||
->assertRedirect(route('woo.integrations.index'))
|
->assertRedirect(route('woo.integrations.index'))
|
||||||
->assertSessionHas('success');
|
->assertSessionHas('success');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_catalog_sync_posts_to_plugin_and_refreshes_cache(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$store = WooStore::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'site_url' => 'https://shop.example.com',
|
||||||
|
'site_name' => 'Example Shop',
|
||||||
|
'status' => WooStore::STATUS_ACTIVE,
|
||||||
|
'connected_at' => now(),
|
||||||
|
'public_id' => 'store-public-id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Cache::put('woo.integrations.'.$store->id, ['integrations' => []], 300);
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
'https://shop.example.com/wp-json/ladill-woo/v1/integrations/google/sync' => Http::response([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Catalog sync started on your store.',
|
||||||
|
], 200),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->from(route('woo.integrations.index'))
|
||||||
|
->post(route('woo.integrations.sync', 'google'))
|
||||||
|
->assertRedirect(route('woo.integrations.index'))
|
||||||
|
->assertSessionHas('success', 'Catalog sync started on your store.');
|
||||||
|
|
||||||
|
$this->assertFalse(Cache::has('woo.integrations.'.$store->id));
|
||||||
|
|
||||||
|
Http::assertSent(function ($request) {
|
||||||
|
return $request->url() === 'https://shop.example.com/wp-json/ladill-woo/v1/integrations/google/sync'
|
||||||
|
&& $request->method() === 'POST'
|
||||||
|
&& $request->hasHeader('X-Ladill-Store', 'store-public-id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_integrations_page_shows_catalog_metrics(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
WooStore::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'site_url' => 'https://shop.example.com',
|
||||||
|
'site_name' => 'Example Shop',
|
||||||
|
'status' => WooStore::STATUS_ACTIVE,
|
||||||
|
'connected_at' => now(),
|
||||||
|
'public_id' => 'store-public-id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
'https://shop.example.com/wp-json/ladill-woo/v1/integrations/status' => Http::response([
|
||||||
|
'integrations' => [
|
||||||
|
'google' => [
|
||||||
|
'state' => 'connected',
|
||||||
|
'installed' => true,
|
||||||
|
'connected' => true,
|
||||||
|
'manage_url' => 'https://shop.example.com/wp-admin/admin.php?page=wc-admin&path=/google/start',
|
||||||
|
'summary' => 'Connected and ready.',
|
||||||
|
'details' => [],
|
||||||
|
'metrics' => [
|
||||||
|
'available' => true,
|
||||||
|
'published_products' => 42,
|
||||||
|
'synced_products' => 40,
|
||||||
|
'pending_products' => 2,
|
||||||
|
'catalog_sync_status' => 'synced',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'shipping' => [
|
||||||
|
'state' => 'connected',
|
||||||
|
'installed' => true,
|
||||||
|
'connected' => true,
|
||||||
|
'manage_url' => 'https://shop.example.com/wp-admin/admin.php?page=wc-settings&tab=shipping',
|
||||||
|
'summary' => '1 zone, 2 methods',
|
||||||
|
'details' => [
|
||||||
|
'woocommerce_shipping' => true,
|
||||||
|
'labels_ready' => true,
|
||||||
|
'labels_manage_url' => 'https://shop.example.com/wp-admin/admin.php?page=wc-settings&tab=shipping§ion=woocommerce-services',
|
||||||
|
],
|
||||||
|
'metrics' => [
|
||||||
|
'available' => true,
|
||||||
|
'orders_awaiting_fulfillment' => 5,
|
||||||
|
'labeled_orders' => 12,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
], 200),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('woo.integrations.index'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Published products')
|
||||||
|
->assertSee('42')
|
||||||
|
->assertSee('Awaiting fulfillment')
|
||||||
|
->assertSee('Sync catalog')
|
||||||
|
->assertSee('View orders')
|
||||||
|
->assertSee('Manage labels');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user