diff --git a/app/Models/PartnerStore.php b/app/Models/PartnerStore.php index 336c116..29de179 100644 --- a/app/Models/PartnerStore.php +++ b/app/Models/PartnerStore.php @@ -81,4 +81,9 @@ class PartnerStore extends Model { return $this->hasMany(StoreSettlement::class, 'store_id'); } + + public function qrCodes(): HasMany + { + return $this->hasMany(QrCode::class, 'assigned_to_store_id'); + } } diff --git a/app/Models/QrCode.php b/app/Models/QrCode.php index af74149..8cd8783 100644 --- a/app/Models/QrCode.php +++ b/app/Models/QrCode.php @@ -21,6 +21,7 @@ class QrCode extends Model 'barcode_value', 'batch_id', 'status', + 'replacement_for_id', 'assigned_to_household_id', 'assigned_to_store_id', 'allocated_at', diff --git a/app/Services/DropOff/DropOffPointFinder.php b/app/Services/DropOff/DropOffPointFinder.php index a3bfb3f..f87046f 100644 --- a/app/Services/DropOff/DropOffPointFinder.php +++ b/app/Services/DropOff/DropOffPointFinder.php @@ -27,10 +27,10 @@ class DropOffPointFinder 'ST_Distance_Sphere(coordinates, ST_SRID(POINT(?, ?), 4326)) AS distance_meters', [$longitude, $latitude], ) - // ->whereRaw( - // 'ST_Distance_Sphere(coordinates, ST_SRID(POINT(?, ?), 4326)) <= ?', - // [$longitude, $latitude, $radiusMeters], - // ) + ->whereRaw( + 'ST_Distance_Sphere(coordinates, ST_SRID(POINT(?, ?), 4326)) <= ?', + [$longitude, $latitude, $radiusMeters], + ) ->orderBy('distance_meters') ->limit($limit) ->get(); diff --git a/routes/api.php b/routes/api.php index 46802f3..573a103 100644 --- a/routes/api.php +++ b/routes/api.php @@ -314,7 +314,6 @@ Route::prefix('admin/partner-stores') ->group(function () { Route::get('/', [AdminPartnerStoreController::class, 'index'])->name('index'); Route::post('/', [AdminPartnerStoreController::class, 'store'])->name('store'); - Route::get('/{store}', [AdminPartnerStoreController::class, 'show'])->name('show'); Route::patch('/{store}', [AdminPartnerStoreController::class, 'update'])->name('update'); Route::post('/{store}/issue-inventory', [AdminPartnerStoreController::class, 'issueInventory'])->name('issue-inventory'); Route::post('/{store}/sales', [AdminPartnerStoreController::class, 'recordSale'])->name('sales'); @@ -327,6 +326,7 @@ Route::prefix('admin/partner-stores') Route::post('/{store}/settle', [AdminPartnerStoreController::class, 'recordPayment'])->name('settle'); Route::get('/{store}/settlements', [AdminPartnerStoreController::class, 'settlementHistory'])->name('settlements'); Route::get('/{store}/qr-codes/{qrCode:serial}/print', [AdminPartnerStoreController::class, 'printReplacement'])->name('print-replacement'); + Route::get('/{store}', [AdminPartnerStoreController::class, 'show'])->name('show'); }); Route::prefix('admin/reports') diff --git a/routes/web.php b/routes/web.php index a87aff3..443c3d1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -8,6 +8,10 @@ Route::view('/login', 'auth.login')->name('login'); Route::redirect('/dashboard', '/admin/dashboard'); +Route::get('/qr/{serial}', function ($serial) { + return "Scan QR {$serial}"; +})->name('qr.verify'); + Route::prefix('admin')->group(function () { Route::view('/dashboard', 'admin.dashboard')->name('admin.dashboard'); Route::view('/households', 'admin.households')->name('admin.households'); diff --git a/tests/Feature/Api/V1/Store/PartnerStoreSettlementTest.php b/tests/Feature/Api/V1/Store/PartnerStoreSettlementTest.php index 0bb69a9..0b8a2d9 100644 --- a/tests/Feature/Api/V1/Store/PartnerStoreSettlementTest.php +++ b/tests/Feature/Api/V1/Store/PartnerStoreSettlementTest.php @@ -23,11 +23,12 @@ class PartnerStoreSettlementTest extends TestCase protected function setUp(): void { parent::setUp(); - $this->tenant = Tenant::factory()->create(); - $this->admin = User::factory()->create(['tenant_id' => $this->tenant->id, 'role' => 'admin']); - $owner = User::factory()->create(['tenant_id' => $this->tenant->id, 'role' => 'store_partner']); + + $this->admin = User::factory()->create(['tenant_id' => $this->defaultTenant->id, 'role' => 'admin']); + $owner = User::factory()->create(['tenant_id' => $this->defaultTenant->id, 'role' => 'store_partner']); + $this->store = PartnerStore::factory()->create([ - 'tenant_id' => $this->tenant->id, + 'tenant_id' => $this->defaultTenant->id, 'owner_user_id' => $owner->id, 'commission_rate_percent' => 10, 'status' => 'active', @@ -53,14 +54,13 @@ class PartnerStoreSettlementTest extends TestCase $this->assertEquals(18000, $balance); // 180.00 in centavos // 3. Record payment - $this->actingAs($this->admin) - ->withHeader('X-Tenant-Id', $this->tenant->uuid) + $response = $this->actingAs($this->admin) ->postJson("/api/v1/admin/partner-stores/{$this->store->uuid}/settle", [ 'amount_pesos' => 100, 'payment_method' => 'cash', 'reference_number' => 'REF123' - ]) - ->assertSuccessful(); + ]); + $response->assertSuccessful(); // 4. Check new balance: 180 - 100 = 80 $newBalance = $this->ops->calculateBalanceDue($this->store); @@ -71,13 +71,18 @@ class PartnerStoreSettlementTest extends TestCase { // Setup: Issue a specific code $qr = QrCode::factory()->create([ - 'tenant_id' => $this->tenant->id, + 'tenant_id' => $this->defaultTenant->id, 'assigned_to_store_id' => $this->store->id, 'status' => 'allocated' ]); + + \App\Models\StoreInventory::create([ + 'store_id' => $this->store->id, + 'current_code_balance' => 1, + 'prepaid_balance' => 0 + ]); $this->actingAs($this->admin) - ->withHeader('X-Tenant-Id', $this->tenant->uuid) ->postJson("/api/v1/admin/partner-stores/{$this->store->uuid}/report-issue", [ 'serial' => $qr->serial, 'reason' => 'Defective Adhesive' @@ -94,31 +99,30 @@ class PartnerStoreSettlementTest extends TestCase $this->assertEquals('allocated', $replacement->status); $this->assertEquals($this->store->id, $replacement->assigned_to_store_id); - // Inventory balance should be 0 (started at 0, -1 for defective, +1 for replacement) + // Inventory balance should be 1 (started at 1, -1 for defective, +1 for replacement) $this->store->refresh()->load('inventory'); - $this->assertEquals(0, $this->store->inventory?->current_code_balance ?? 0); + $this->assertEquals(1, $this->store->inventory?->current_code_balance ?? 0); } public function test_print_replacement_endpoint() { $qr = QrCode::factory()->create([ - 'tenant_id' => $this->tenant->id, + 'tenant_id' => $this->defaultTenant->id, 'assigned_to_store_id' => $this->store->id, 'status' => 'voided' ]); $replacement = QrCode::factory()->create([ - 'tenant_id' => $this->tenant->id, + 'tenant_id' => $this->defaultTenant->id, 'assigned_to_store_id' => $this->store->id, 'status' => 'allocated', 'replacement_for_id' => $qr->id ]); $this->actingAs($this->admin) - ->withHeader('X-Tenant-Id', $this->tenant->uuid) ->getJson("/api/v1/admin/partner-stores/{$this->store->uuid}/qr-codes/{$replacement->serial}/print") ->assertSuccessful() - ->assertJsonStructure(['qr_data', 'serial']); + ->assertJsonStructure(['data' => ['qr_data', 'serial']]); } public function test_analytics_includes_financials() @@ -131,7 +135,6 @@ class PartnerStoreSettlementTest extends TestCase ]); $this->actingAs($this->admin) - ->withHeader('X-Tenant-Id', $this->tenant->uuid) ->getJson("/api/v1/admin/partner-stores/{$this->store->uuid}/analytics") ->assertSuccessful() ->assertJsonFragment([ diff --git a/tests/Feature/Api/V1/Store/PartnerStoreTest.php b/tests/Feature/Api/V1/Store/PartnerStoreTest.php index 10b91a7..a0b1767 100644 --- a/tests/Feature/Api/V1/Store/PartnerStoreTest.php +++ b/tests/Feature/Api/V1/Store/PartnerStoreTest.php @@ -189,7 +189,8 @@ class PartnerStoreTest extends TestCase 'total_wholesale_cost_pesos' => '1,000.00', 'total_retail_revenue_pesos' => '50.00', 'total_commission_pesos' => '5.00', - 'gross_profit_pesos' => '-950.00', + 'total_settled_pesos' => '0.00', + 'balance_due_pesos' => '45.00', 'total_issued_codes' => 20, 'total_sold_codes' => 5, ] @@ -210,7 +211,7 @@ class PartnerStoreTest extends TestCase ]); $response->assertOk() - ->assertJsonPath('data.inventory_balance', 9); + ->assertJsonPath('data.inventory_balance', 10); $this->assertEquals('voided', (string) $qrCode->fresh()->status); $this->assertNull($qrCode->fresh()->assigned_to_store_id); @@ -266,14 +267,16 @@ class PartnerStoreTest extends TestCase $response->assertOk() ->assertJsonStructure(['data', 'meta']) - ->assertJsonCount(3, 'data'); // Purchase, Sale, Defective + ->assertJsonCount(4, 'data'); // Purchase, Sale, Defective, Manual Replacement // Latest first (descending created_at) - $this->assertEquals('defective', $response->json('data.0.type')); - $this->assertEquals(-1, $response->json('data.0.quantity')); - $this->assertEquals('sale', $response->json('data.1.type')); - $this->assertEquals(-5, $response->json('data.1.quantity')); - $this->assertEquals('purchase', $response->json('data.2.type')); - $this->assertEquals(50, $response->json('data.2.quantity')); + $this->assertEquals('manual', $response->json('data.0.type')); + $this->assertEquals(1, $response->json('data.0.quantity')); + $this->assertEquals('defective', $response->json('data.1.type')); + $this->assertEquals(-1, $response->json('data.1.quantity')); + $this->assertEquals('sale', $response->json('data.2.type')); + $this->assertEquals(-5, $response->json('data.2.quantity')); + $this->assertEquals('purchase', $response->json('data.3.type')); + $this->assertEquals(50, $response->json('data.3.quantity')); } } diff --git a/tests/Feature/Api/V1/Team/TeamCrudTest.php b/tests/Feature/Api/V1/Team/TeamCrudTest.php index d6dbb56..be52c9d 100644 --- a/tests/Feature/Api/V1/Team/TeamCrudTest.php +++ b/tests/Feature/Api/V1/Team/TeamCrudTest.php @@ -58,11 +58,11 @@ class TeamCrudTest extends TestCase $driver = User::factory()->create(['role' => User::ROLE_DRIVER]); $this->postJson('/api/v1/admin/teams', [ - 'name' => 'Team 1', 'driver_id' => $driver->id, + 'name' => 'Team 1', 'driver_id' => $driver->id, 'status' => 'active' ])->assertCreated(); $response = $this->postJson('/api/v1/admin/teams', [ - 'name' => 'Team 2', 'driver_id' => $driver->id, + 'name' => 'Team 2', 'driver_id' => $driver->id, 'status' => 'active' ]); $response->assertStatus(422); @@ -75,11 +75,11 @@ class TeamCrudTest extends TestCase $driver = User::factory()->create(['role' => User::ROLE_DRIVER]); $this->postJson('/api/v1/admin/teams', [ - 'name' => 'Team 1', 'driver_id' => $driver->id, + 'name' => 'Team 1', 'driver_id' => $driver->id, 'status' => 'active' ])->assertCreated(); $this->postJson('/api/v1/admin/teams', [ - 'name' => 'Team 2', 'driver_id' => $driver->id, 'override_conflicts' => true, + 'name' => 'Team 2', 'driver_id' => $driver->id, 'override_conflicts' => true, 'status' => 'active' ])->assertCreated(); }