- 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.
251 lines
8.6 KiB
PHP
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");
|
|
}
|
|
}
|