withoutMiddleware(EnsurePlatformSession::class); $this->withoutVite(); config([ 'crm.url' => 'https://crm.test/api', 'crm.key' => 'test-crm-key', ]); } private function user(): User { return User::create([ 'public_id' => 'u-'.uniqid(), 'name' => 'Cashier', 'email' => uniqid().'@example.com', ]); } public function test_cash_sale_pushes_crm_timeline(): void { Http::fake([ 'crm.test/api/timeline' => Http::response(['id' => 1], 201), ]); $user = $this->user(); $this->actingAs($user)->post(route('pos.register.charge'), [ 'payment_method' => 'cash', 'lines' => [ ['name' => 'Tea', 'unit_price_minor' => 1000, 'quantity' => 1], ], ])->assertRedirect(); Http::assertSent(fn ($request) => $request->url() === 'https://crm.test/api/timeline' && $request['event'] === 'order.paid' && $request['owner'] === $user->public_id); } public function test_crm_product_import_creates_local_products(): void { Http::fake([ 'crm.test/api/products*' => Http::response([ 'data' => [ [ 'name' => 'Imported Mug', 'sku' => 'MUG-1', 'type' => 'product', 'unit_price_minor' => 2500, 'currency' => 'GHS', 'active' => true, ], [ 'name' => 'Consulting Hour', 'sku' => 'SVC-1', 'type' => 'service', 'unit_price_minor' => 9000, 'currency' => 'GHS', 'active' => true, ], ], ], 200), ]); $user = $this->user(); $this->actingAs($user) ->post(route('pos.settings.import-crm')) ->assertRedirect() ->assertSessionHas('success'); $product = PosProduct::owned($user->public_id)->where('sku', 'MUG-1')->first(); $this->assertNotNull($product); $this->assertSame('Imported Mug', $product->name); $this->assertSame(2500, $product->price_minor); // Services must never be imported as POS products. $this->assertNull(PosProduct::owned($user->public_id)->where('sku', 'SVC-1')->first()); // The CRM request must constrain to products only. Http::assertSent(fn ($request) => str_contains($request->url(), 'crm.test/api/products') && str_contains($request->url(), 'type=product')); } public function test_paid_sale_links_to_invoice_prefill(): void { $user = $this->user(); $sale = PosSale::create([ 'owner_ref' => $user->public_id, 'reference' => 'POS-TESTREF01', 'status' => PosSale::STATUS_PAID, 'payment_method' => PosSale::METHOD_CASH, 'customer_name' => 'Ada', 'total_minor' => 1500, 'subtotal_minor' => 1500, 'currency' => 'GHS', 'paid_at' => now(), ]); $sale->lines()->create([ 'name' => 'Coffee', 'unit_price_minor' => 1500, 'quantity' => 1, 'line_total_minor' => 1500, 'position' => 0, ]); $url = app(CrossAppLinkService::class)->invoiceFromSale($sale->fresh('lines')); $this->assertStringContainsString('invoice.', $url); $this->assertStringContainsString('invoices%2Fcreate', $url); $this->actingAs($user) ->get(route('pos.sales.show', $sale)) ->assertOk() ->assertSee('Create invoice'); } }