Deploy Ladill Merchant / deploy (push) Successful in 42s
Surface wallet balance and key counts in gradient hero sections so Merchant list pages match the Events pattern. Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
3.4 KiB
PHP
103 lines
3.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Merchant;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Services\Crm\CrmClient;
|
|
use App\Services\Qr\QrCodeManagerService;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
|
|
/**
|
|
* Products page — fully backed by the Ladill CRM products API. The merchant's
|
|
* catalog lives in CRM (owner-scoped); this controller proxies CRUD to it.
|
|
*/
|
|
class ProductController extends Controller
|
|
{
|
|
public function __construct(private QrCodeManagerService $manager) {}
|
|
|
|
private function crm(): CrmClient
|
|
{
|
|
return CrmClient::for((string) ladill_account()->public_id);
|
|
}
|
|
|
|
public function index(Request $request): View
|
|
{
|
|
$search = trim((string) $request->query('search', ''));
|
|
|
|
$response = $this->crm()->products([
|
|
'type' => 'product',
|
|
'per_page' => 100,
|
|
'search' => $search !== '' ? $search : null,
|
|
]);
|
|
|
|
$products = (array) ($response['data'] ?? []);
|
|
$wallet = $this->manager->walletFor(ladill_account());
|
|
|
|
return view('merchant.products.index', [
|
|
'products' => $products,
|
|
'search' => $search,
|
|
'wallet' => $wallet,
|
|
'productCount' => count($products),
|
|
'activeCount' => collect($products)->filter(fn ($p) => ($p['active'] ?? true))->count(),
|
|
]);
|
|
}
|
|
|
|
public function create(): View
|
|
{
|
|
return view('merchant.products.create');
|
|
}
|
|
|
|
public function store(Request $request): RedirectResponse
|
|
{
|
|
$this->crm()->createProduct($this->payload($request));
|
|
|
|
return redirect()->route('merchant.products.index')->with('success', 'Product created.');
|
|
}
|
|
|
|
public function edit(string $product): View
|
|
{
|
|
return view('merchant.products.edit', ['product' => $this->crm()->product($product)]);
|
|
}
|
|
|
|
public function update(Request $request, string $product): RedirectResponse
|
|
{
|
|
$this->crm()->updateProduct($product, $this->payload($request));
|
|
|
|
return redirect()->route('merchant.products.index')->with('success', 'Product updated.');
|
|
}
|
|
|
|
public function destroy(string $product): RedirectResponse
|
|
{
|
|
$this->crm()->deleteProduct($product);
|
|
|
|
return redirect()->route('merchant.products.index')->with('success', 'Product deleted.');
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
private function payload(Request $request): array
|
|
{
|
|
$data = $request->validate([
|
|
'name' => ['required', 'string', 'max:255'],
|
|
'sku' => ['nullable', 'string', 'max:80'],
|
|
'description' => ['nullable', 'string', 'max:5000'],
|
|
'unit_price' => ['nullable', 'numeric', 'min:0'],
|
|
'currency' => ['nullable', 'string', 'size:3'],
|
|
'tax_rate' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
|
'active' => ['nullable', 'boolean'],
|
|
]);
|
|
|
|
return [
|
|
'name' => $data['name'],
|
|
'sku' => $data['sku'] ?? null,
|
|
'type' => 'product',
|
|
'description' => $data['description'] ?? null,
|
|
'unit_price_minor' => (int) round(((float) ($data['unit_price'] ?? 0)) * 100),
|
|
'currency' => strtoupper((string) ($data['currency'] ?? config('crm.default_currency', 'GHS'))),
|
|
'tax_rate' => (float) ($data['tax_rate'] ?? 0),
|
|
'active' => $request->boolean('active'),
|
|
];
|
|
}
|
|
}
|