ownerRef($request); $location = $this->locations->ensureDefault($owner); return view('pos.register', [ 'products' => $this->registerProducts($owner, $location), 'location' => $location, 'crmCustomers' => $this->crmCustomers($owner), ]); } /** * Register catalog: local pos_products in restaurant mode, live CRM products * in retail mode (resilient — empty if CRM is unreachable). * * @return list */ private function registerProducts(string $owner, PosLocation $location): array { if ($location->isRestaurant()) { return PosProduct::owned($owner)->active()->orderBy('name')->get() ->map(fn (PosProduct $p) => ['id' => $p->id, 'name' => $p->name, 'price_minor' => $p->price_minor]) ->all(); } try { $rows = (array) (CrmClient::for($owner)->products(['type' => 'product', 'active' => 1, 'per_page' => 500])['data'] ?? []); } catch (\Throwable) { $rows = []; } return collect($rows) ->map(fn ($r) => [ 'id' => $r['id'] ?? null, 'name' => (string) ($r['name'] ?? ''), 'price_minor' => (int) ($r['unit_price_minor'] ?? 0), ]) ->filter(fn ($r) => $r['name'] !== '' && $r['price_minor'] > 0) ->values() ->all(); } /** Quick switch between retail and restaurant service from the register. */ public function setMode(Request $request): RedirectResponse { $data = $request->validate(['style' => ['required', 'in:retail,restaurant']]); $location = $this->locations->ensureDefault($this->ownerRef($request)); $location->update(['service_style' => $data['style']]); if ($data['style'] === PosLocation::STYLE_RESTAURANT) { return redirect()->route('pos.floor')->with('success', 'Restaurant mode on — Floor, Kitchen & Menu are now in the sidebar.'); } return redirect()->route('pos.register')->with('success', 'Retail mode on.'); } /** @return list> */ private function crmCustomers(string $owner): array { try { return (array) ((CrmClient::for($owner)->customers(['per_page' => 200])['data']) ?? []); } catch (\Throwable) { return []; } } public function charge(Request $request): RedirectResponse { $data = $request->validate([ 'lines' => ['required', 'array', 'min:1'], 'lines.*.product_id' => ['nullable', 'integer'], 'lines.*.name' => ['required', 'string', 'max:200'], 'lines.*.unit_price_minor' => ['required', 'integer', 'min:1'], 'lines.*.quantity' => ['required', 'integer', 'min:1', 'max:999'], 'customer_name' => ['nullable', 'string', 'max:120'], 'customer_email' => ['nullable', 'email', 'max:255'], 'customer_phone' => ['nullable', 'string', 'max:40'], 'crm_customer_id' => ['nullable', 'integer'], 'payment_method' => ['required', 'in:pay,cash'], ]); $merchant = ladill_account() ?? $request->user(); $location = $this->locations->ensureDefault($this->ownerRef($request)); $lines = $data['lines']; if (! $location->isRestaurant()) { // Retail lines reference CRM products, not local pos_products — keep a name/price snapshot only. $lines = array_map(fn ($l) => [...$l, 'product_id' => null], $lines); } try { $sale = $this->sales->createSale($merchant, $lines, [ 'location_id' => $location->id, 'currency' => $location->currency, 'customer_name' => $data['customer_name'] ?? null, 'customer_email' => $data['customer_email'] ?? null, 'customer_phone' => $data['customer_phone'] ?? null, 'crm_customer_id' => $data['crm_customer_id'] ?? null, ]); if ($data['payment_method'] === 'cash') { $this->sales->recordCashPayment($sale); return redirect() ->route('pos.sales.show', $sale) ->with('success', 'Cash sale recorded.'); } $result = $this->sales->initiatePayCheckout($sale, $merchant); return redirect()->away($result['checkout_url']); } catch (RuntimeException $e) { return back()->withInput()->with('error', $e->getMessage()); } } }