fix: QrCode relation bug and Endroid create() error

This commit is contained in:
Developer
2026-07-02 13:27:53 +08:00
parent d9495be0e0
commit ba30a5fcdf
4 changed files with 138 additions and 6 deletions

View File

@@ -95,6 +95,7 @@ class StorePortalController extends ApiController
]);
if (!$request->household_id && !$request->user_id) {
dump($request->all());
return $this->fail('Either household_id or user_id must be provided.', null, 422);
}
@@ -121,14 +122,15 @@ class StorePortalController extends ApiController
->get();
$qrData = $codes->map(function ($code) {
$qr = \Endroid\QrCode\QrCode::create($code->serial)
->setSize(300)
->setMargin(10);
$writer = new PngWriter;
$qr = \Endroid\QrCode\Builder\Builder::create()
->data($code->serial)
->size(300)
->margin(10)
->build();
return [
'serial' => $code->serial,
'qr_base64' => base64_encode($writer->write($qr)->getString()),
'qr_base64' => base64_encode($qr->getString()),
];
});

View File

@@ -23,6 +23,7 @@ class QrCode extends Model
'status',
'replacement_for_id',
'assigned_to_household_id',
'assigned_to_user_id',
'assigned_to_store_id',
'allocated_at',
'activated_at',
@@ -54,7 +55,7 @@ class QrCode extends Model
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['status', 'assigned_to_household_id', 'assigned_to_store_id', 'used_at_drop_off_id'])
->logOnly(['status', 'assigned_to_household_id', 'assigned_to_user_id', 'assigned_to_store_id', 'used_at_drop_off_id'])
->logOnlyDirty()
->dontSubmitEmptyLogs()
->useLogName('qr_code');
@@ -70,6 +71,11 @@ class QrCode extends Model
return $this->belongsTo(Household::class, 'assigned_to_household_id');
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'assigned_to_user_id');
}
public function usedAtDropOff(): BelongsTo
{
return $this->belongsTo(DropOffPoint::class, 'used_at_drop_off_id');

View File

@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('qr_codes', function (Blueprint $table) {
$table->foreignId('assigned_to_user_id')->nullable()->after('assigned_to_household_id')->constrained('users')->nullOnDelete();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('qr_codes', function (Blueprint $table) {
$table->dropForeign(['assigned_to_user_id']);
$table->dropColumn('assigned_to_user_id');
});
}
};

View File

@@ -0,0 +1,95 @@
<?php
namespace Tests\Feature\Api\V1\Store;
use App\Models\DropOffPoint;
use App\Models\PartnerStore;
use App\Models\QrCode;
use App\Models\User;
use App\Services\Store\StoreOperations;
use Database\Seeders\RoleSeeder;
use Database\Seeders\SampleDropOffPointsSeeder;
use Database\Seeders\SamplePsgcSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class ResidentSaleScanTest extends TestCase
{
use RefreshDatabase;
public function test_end_to_end_resident_sale_and_scan(): void
{
// 1. Setup Environment
$this->seed([RoleSeeder::class, SamplePsgcSeeder::class, SampleDropOffPointsSeeder::class]);
$admin = User::factory()->create(['role' => User::ROLE_ADMIN, 'status' => 'active']);
$storeOwner = User::factory()->create(['role' => User::ROLE_STORE_PARTNER, 'status' => 'active']);
$resident = User::factory()->create(['role' => User::ROLE_RESIDENT, 'status' => 'active']);
$scannerUser = User::factory()->create(['role' => User::ROLE_SCANNER, 'status' => 'active']);
$dop = DropOffPoint::first();
// 2. Create Store & Issue Wholesale
$store = PartnerStore::factory()->create([
'owner_user_id' => $storeOwner->id,
'status' => 'active',
'commission_rate_percent' => 10
]);
app(StoreOperations::class)->issueWholesale($store, 10, 50000);
// 3. Store sells 5 QR codes to Resident via API
Sanctum::actingAs($storeOwner);
$saleResponse = $this->postJson("/api/v1/store/sales", [
'user_id' => $resident->id,
'quantity' => 5,
]);
if ($saleResponse->status() !== 201) {
dump($saleResponse->json());
}
$saleResponse->assertCreated();
$this->assertEquals(5, $saleResponse->json('data.quantity'));
// Verify codes are active and assigned to resident
$activeCodes = QrCode::where('assigned_to_user_id', $resident->id)
->where('status', 'active')
->get();
$this->assertCount(5, $activeCodes);
// 4. Scanner App scans the 5 QR codes
Sanctum::actingAs($scannerUser);
$scans = [];
foreach ($activeCodes as $code) {
$scans[] = [
'serial' => $code->serial,
'drop_off_point_id' => $dop->id,
'lat' => 14.6539,
'lng' => 121.0685,
];
}
$scanResponse = $this->postJson('/api/v1/scanner/scan/bulk', [
'scans' => $scans,
]);
$scanResponse->assertOk()
->assertJsonPath('data.accepted_count', 5)
->assertJsonPath('data.rejected_count', 0);
// Verify codes are marked as used
$usedCodes = QrCode::where('assigned_to_user_id', $resident->id)
->where('status', 'used')
->count();
$this->assertEquals(5, $usedCodes);
echo "✅ E2E Test Passed!\n";
echo "1. Issued wholesale to store.\n";
echo "2. Store sold 5 codes to resident (user_id: {$resident->id}).\n";
echo "3. Scanner app successfully scanned all 5 codes.\n";
}
}