Files
ladill-woo-manager/app/Http/Controllers/Woo/ProductController.php
T
isaaccladandCursor cebf0d2e61
Deploy Ladill Woo Manager / deploy (push) Successful in 51s
Add Woo Manager Pro with multi-store switching and free-tier limits.
Free: 1 store and 20 products. Pro/Business mirrors Invoice pricing (GHS 49/149)
with wallet renewals, Paystack prepay, session-based store switcher, and scoped UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 01:14:08 +00:00

170 lines
6.7 KiB
PHP

<?php
namespace App\Http\Controllers\Woo;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Woo\Concerns\ResolvesWooContext;
use App\Models\WooProduct;
use App\Services\Woo\CatalogSyncService;
use App\Services\Woo\CatalogWriteService;
use App\Services\Woo\ProductMediaService;
use App\Services\Woo\SubscriptionService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
class ProductController extends Controller
{
use ResolvesWooContext;
public function __construct(
private CatalogWriteService $write,
private CatalogSyncService $sync,
private ProductMediaService $media,
private SubscriptionService $subscriptions,
) {}
public function index(Request $request): View
{
$user = $this->accountUser($request);
$store = $this->currentStore($request);
$search = trim((string) $request->query('q', ''));
$statusFilter = $request->query('status');
$products = WooProduct::query()
->when($store, fn ($q) => $q->where('woo_store_id', $store->id))
->when(! $store, fn ($q) => $q->whereRaw('1 = 0'))
->when($statusFilter, fn ($q) => $q->where('status', $statusFilter))
->when($search !== '', function ($query) use ($search) {
$like = '%'.$search.'%';
$query->where(function ($inner) use ($like) {
$inner->where('name', 'like', $like)
->orWhere('sku', 'like', $like);
});
})
->with('store')
->latest('updated_at')
->paginate(20)
->withQueryString();
return view('woo.products.index', [
'products' => $products,
'currentStore' => $store,
'search' => $search,
'statusFilter' => $statusFilter,
'productCount' => $this->subscriptions->productCount($user),
'productLimit' => $this->subscriptions->isPro($user) ? null : (int) config('woo.free.max_products', 20),
]);
}
public function create(Request $request): View|RedirectResponse
{
$user = $this->accountUser($request);
$store = $this->currentStoreOrFail($request);
if (! $this->subscriptions->canCreateProduct($user)) {
return redirect()->route('woo.pro.index')
->with('upsell', 'You have reached the free plan limit of '.(int) config('woo.free.max_products', 20).' products. Upgrade to Pro for unlimited products.');
}
return view('woo.products.form', [
'product' => new WooProduct(['status' => WooProduct::STATUS_DRAFT, 'woo_store_id' => $store->id]),
'currentStore' => $store,
'categories' => $store->categories()->orderBy('name')->get(),
]);
}
public function store(Request $request): RedirectResponse
{
$user = $this->accountUser($request);
$store = $this->currentStoreOrFail($request);
if (! $this->subscriptions->canCreateProduct($user)) {
return redirect()->route('woo.pro.index')
->with('upsell', 'You have reached the free plan limit of '.(int) config('woo.free.max_products', 20).' products. Upgrade to Pro for unlimited products.');
}
$validated = $this->validateProduct($request, $store);
$validated['images'] = $this->media->buildImagesFromRequest($request, $store);
$this->write->createProduct($store, $validated);
return redirect()->route('woo.products.index')
->with('success', 'Product saved.');
}
public function edit(Request $request, WooProduct $product): View
{
$store = $this->currentStoreOrFail($request);
abort_if($product->woo_store_id !== $store->id, 404);
return view('woo.products.form', [
'product' => $product,
'currentStore' => $store,
'categories' => $store->categories()->orderBy('name')->get(),
]);
}
public function update(Request $request, WooProduct $product): RedirectResponse
{
$store = $this->currentStoreOrFail($request);
abort_if($product->woo_store_id !== $store->id, 404);
$validated = $this->validateProduct($request, $store, $product);
$validated['images'] = $this->media->buildImagesFromRequest($request, $store);
$this->write->updateProduct($product, $validated);
return redirect()->route('woo.products.index')
->with('success', 'Product updated.');
}
public function sync(Request $request): RedirectResponse
{
$store = $this->currentStoreOrFail($request);
$user = $this->accountUser($request);
$counts = $this->sync->syncAll($store, $user);
$message = sprintf(
'Synced %d categories and %d products from %s.',
$counts['categories'],
$counts['products'],
$store->site_name ?? $store->site_url,
);
if (! $this->subscriptions->isPro($user) && $this->subscriptions->productCount($user) >= (int) config('woo.free.max_products', 20)) {
$message .= ' Free plan is limited to '.(int) config('woo.free.max_products', 20).' products — upgrade to Pro to sync your full catalog.';
}
return back()->with('success', $message);
}
/** @return array<string, mixed> */
private function validateProduct(Request $request, $store, ?WooProduct $product = null): array
{
return array_merge($request->validate([
'name' => ['required', 'string', 'max:255'],
'sku' => ['nullable', 'string', 'max:120'],
'status' => ['required', Rule::in(array_keys(WooProduct::STATUSES))],
'regular_price' => ['nullable', 'numeric', 'min:0'],
'sale_price' => ['nullable', 'numeric', 'min:0'],
'manage_stock' => ['sometimes', 'boolean'],
'stock_quantity' => ['nullable', 'integer', 'min:0'],
'short_description' => ['nullable', 'string', 'max:5000'],
'description' => ['nullable', 'string', 'max:20000'],
'category_external_ids' => ['nullable', 'array'],
'category_external_ids.*' => ['integer', 'min:1'],
'product_images' => ['nullable', 'string'],
'featured_image_upload' => ['nullable', 'image', 'max:10240'],
'featured_image_src' => ['nullable', 'url', 'max:2000'],
'featured_image_alt' => ['nullable', 'string', 'max:255'],
'gallery_uploads' => ['nullable', 'array'],
'gallery_uploads.*' => ['image', 'max:10240'],
'gallery_image_srcs' => ['nullable', 'array'],
'gallery_image_srcs.*' => ['url', 'max:2000'],
]), [
'woo_store_id' => $store->id,
]);
}
}