Files
Verde-Web/database/seeders/SimulationContinuationSeeder.php
Developer 7f3357206f feat: implement fixed dashboard layout, custom geofencing shapes, and QC seeder
- UI: Refactored admin layout to a fixed 'App Shell' design with independent scrollbars for sidebar and content area.
- UI: Cleaned up sidebar partial structure to support nested scrolling.
- GEOFENCING: Added support for Circle and Rectangle tools in LGU configuration.
- GEOFENCING: Implemented high-precision (128-point) circle-to-polygon conversion for database storage.
- PAGINATION: Integrated standard Laravel pagination across Teams and Reports tables.
- PAGINATION: Added a global 'renderPagination' helper and 'Per Page' selector (25, 50, 100).
- DATA: Created QuezonCitySeeder with 142 official barangays and 3 months of historical collection/sales data.
- FIX: Corrected API total aggregations in reports to remain accurate during pagination.
- FIX: Resolved unique constraint and axis-order issues in spatial data seeders.
2026-07-03 11:19:08 +08:00

251 lines
8.6 KiB
PHP

<?php
namespace Database\Seeders;
use App\Models\CollectionLog;
use App\Models\CollectionTeam;
use App\Models\DropOffPoint;
use App\Models\Household;
use App\Models\QrCode;
use App\Models\QrCodeBatch;
use App\Models\Route;
use App\Models\RouteStop;
use App\Models\ServiceArea;
use App\Models\Tenant;
use App\Models\Trip;
use App\Models\TripStop;
use App\Models\Truck;
use App\Models\User;
use App\Services\Trip\TripExecutor;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use MatanYadaev\EloquentSpatial\Objects\Point;
class SimulationContinuationSeeder extends Seeder
{
public function run(): void
{
$tenantId = 4; // LGU General Santos
$tenant = Tenant::findOrFail($tenantId);
// 1. Create Accounts for Team A and Team B
$password = Hash::make('password1');
$driverA = User::updateOrCreate(
['email' => 'driver_a@verde.local'],
[
'tenant_id' => $tenantId,
'first_name' => 'Driver',
'last_name' => 'Alpha',
'phone' => '+639111111111',
'password' => $password,
'role' => User::ROLE_DRIVER,
'status' => User::STATUS_ACTIVE,
]
);
$scannerA = User::updateOrCreate(
['email' => 'scanner_a@verde.local'],
[
'tenant_id' => $tenantId,
'first_name' => 'Scanner',
'last_name' => 'Alpha',
'phone' => '+639111111112',
'password' => $password,
'role' => User::ROLE_SCANNER,
'status' => User::STATUS_ACTIVE,
]
);
$driverB = User::updateOrCreate(
['email' => 'driver_b@verde.local'],
[
'tenant_id' => $tenantId,
'first_name' => 'Driver',
'last_name' => 'Bravo',
'phone' => '+639222222221',
'password' => $password,
'role' => User::ROLE_DRIVER,
'status' => User::STATUS_ACTIVE,
]
);
$scannerB = User::updateOrCreate(
['email' => 'scanner_b@verde.local'],
[
'tenant_id' => $tenantId,
'first_name' => 'Scanner',
'last_name' => 'Bravo',
'phone' => '+639222222222',
'password' => $password,
'role' => User::ROLE_SCANNER,
'status' => User::STATUS_ACTIVE,
]
);
// 2. Setup Teams
$truck = Truck::firstOrCreate(
['plate_number' => 'GEN-001'],
[
'tenant_id' => $tenantId,
'model' => 'Isuzu Elf',
'capacity_kg' => 5000,
'status' => 'active'
]
);
$teamA = CollectionTeam::updateOrCreate(
['name' => 'GenSan Team Alpha'],
[
'tenant_id' => $tenantId,
'driver_id' => $driverA->id,
'scanner_id' => $scannerA->id,
'truck_id' => $truck->id,
'status' => CollectionTeam::STATUS_ACTIVE,
]
);
$teamB = CollectionTeam::updateOrCreate(
['name' => 'GenSan Team Bravo'],
[
'tenant_id' => $tenantId,
'driver_id' => $driverB->id,
'scanner_id' => $scannerB->id,
'truck_id' => null, // Will assign a truck later or use same
'status' => CollectionTeam::STATUS_ACTIVE,
]
);
// 3. Setup Route and Stops
$area = ServiceArea::firstOrCreate(
['tenant_id' => $tenantId, 'name' => 'Central District'],
[
'code' => 'GENSAN-CENTRAL',
'status' => 'active'
]
);
$route = Route::updateOrCreate(
['name' => 'Morning Central Route'],
[
'tenant_id' => $tenantId,
'area_id' => $area->id,
'code' => 'GENSAN-MORNING-CENTRAL',
'status' => 'active'
]
);
// Create 5 Drop Off Points and Route Stops
for ($i = 1; $i <= 5; $i++) {
$dop = DropOffPoint::firstOrCreate(
['name' => "GenSan Stop $i"],
[
'tenant_id' => $tenantId,
'code' => "GENSAN-STOP-$i",
'address_line' => "GenSan Street Address $i",
'coordinates' => new Point(6.1136 + ($i * 0.001), 125.1719 + ($i * 0.001), 4326),
]
);
RouteStop::updateOrCreate(
['route_id' => $route->id, 'drop_off_point_id' => $dop->id],
['sequence' => $i]
);
}
// 4. Create Trip for Team A
$tripA = Trip::updateOrCreate(
['trip_number' => 'TRIP-' . now()->format('Ymd') . '-A1'],
[
'uuid' => Str::uuid(),
'tenant_id' => $tenantId,
'route_id' => $route->id,
'team_id' => $teamA->id,
'truck_id' => $truck->id,
'scheduled_date' => now()->toDateString(),
'scheduled_start_time' => '08:00:00',
'actual_start_time' => now()->subHours(2),
'status' => Trip::STATUS_IN_PROGRESS,
'total_load_kg' => 5000,
'current_load_kg' => 4800, // Almost full
]
);
// Copy stops to Trip A
foreach ($route->stops as $rs) {
TripStop::updateOrCreate(
['trip_id' => $tripA->id, 'sequence' => $rs->sequence],
[
'drop_off_point_id' => $rs->drop_off_point_id,
'status' => $rs->sequence == 1 ? TripStop::STATUS_COMPLETED : TripStop::STATUS_PENDING,
'actual_arrival' => $rs->sequence == 1 ? now()->subHours(1) : null,
'actual_departure' => $rs->sequence == 1 ? now()->subMinutes(45) : null,
]
);
}
// 5. Simulate 1 QR Scan for Stop 1
$batch = QrCodeBatch::firstOrCreate(
['tenant_id' => $tenantId, 'batch_number' => 'SIM-001'],
['quantity' => 100, 'purpose' => QrCodeBatch::PURPOSE_FREE]
);
$qr = QrCode::updateOrCreate(
['serial' => 'VERDE-SIM-001'],
[
'tenant_id' => $tenantId,
'barcode_value' => 'VERDE-SIM-001',
'batch_id' => $batch->id,
'status' => \App\States\QrCode\Active::class,
]
);
$household = Household::firstOrCreate(
['tenant_id' => $tenantId, 'address_line' => 'GenSan Main St Simulated'],
[
'head_user_id' => $driverA->id, // Reusing driver as head for sim
'household_size' => 4,
'verification_status' => 'approved'
]
);
$qr->update(['assigned_to_household_id' => $household->id]);
CollectionLog::updateOrCreate(
['trip_id' => $tripA->id, 'qr_code_id' => $qr->id],
[
'tenant_id' => $tenantId,
'household_id' => $household->id,
'drop_off_point_id' => $route->stops->first()->drop_off_point_id,
'scanned_by_user_id' => $scannerA->id,
'trip_stop_id' => $tripA->stops->first()->id,
'scanned_at' => now()->subHours(1),
'coordinates_at_scan' => new Point(6.1136, 125.1719, 4326),
'weight_kg' => 50,
'waste_type' => 'mixed',
'verification_status' => CollectionLog::STATUS_VALID,
]
);
// 6. Assign Team B to continue the route
$executor = app(TripExecutor::class);
$admin = User::where('role', User::ROLE_ADMIN)->where('tenant_id', $tenantId)->first()
?? User::where('role', User::ROLE_SUPER_ADMIN)->first();
if ($admin) {
$executor->createContinuation(
originalTrip: $tripA,
admin: $admin,
newTeamId: $teamB->id,
newTruckId: $truck->id, // Use same truck for simulation or different
endReason: 'truck_full'
);
}
$this->command->info('Simulation Seeder Completed Successfully!');
$this->command->info("Team A Trip (Full): {$tripA->trip_number}");
$this->command->info("Team B Account: driver_b@verde.local / password1");
}
}