353 lines
14 KiB
PHP
353 lines
14 KiB
PHP
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
use App\Models\Barangay;
|
|
use App\Models\CollectionTeam;
|
|
use App\Models\DriverProfile;
|
|
use App\Models\DropOffPoint;
|
|
use App\Models\Dumpsite;
|
|
use App\Models\HelperProfile;
|
|
use App\Models\Household;
|
|
use App\Models\NotificationPreference;
|
|
use App\Models\PartnerStore;
|
|
use App\Models\ResidentProfile;
|
|
use App\Models\Route as CollectionRoute;
|
|
use App\Models\RouteStop;
|
|
use App\Models\ScannerProfile;
|
|
use App\Models\StoreInventory;
|
|
use App\Models\StorePartnerProfile;
|
|
use App\Models\StoreSale;
|
|
use App\Models\TeamMember;
|
|
use App\Models\Tenant;
|
|
use App\Models\Trip;
|
|
use App\Models\TripStop;
|
|
use App\Models\Truck;
|
|
use App\Models\User;
|
|
use App\Models\QrCode;
|
|
use App\Models\QrCodeBatch;
|
|
use App\States\QrCode\Active;
|
|
use App\States\QrCode\Allocated;
|
|
use App\Tenancy\Tenancy;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Database\Seeder;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Str;
|
|
use MatanYadaev\EloquentSpatial\Objects\Point;
|
|
|
|
class DevelopmentSeeder extends Seeder
|
|
{
|
|
public function __construct(private readonly \App\Services\Qr\BatchGenerator $generator) {}
|
|
|
|
public function run(): void
|
|
{
|
|
$this->call(SamplePsgcSeeder::class);
|
|
$this->call(RoleSeeder::class);
|
|
$password = Hash::make('password1');
|
|
|
|
// 1. Exactly 1 Super Admin
|
|
User::firstOrCreate(
|
|
['email' => 'super@verde.local'],
|
|
[
|
|
'phone' => '+639000000000',
|
|
'password' => $password,
|
|
'first_name' => 'System',
|
|
'last_name' => 'Super Admin',
|
|
'role' => 'super_admin',
|
|
'status' => User::STATUS_ACTIVE,
|
|
'tenant_id' => null,
|
|
'email_verified_at' => now(),
|
|
]
|
|
)->syncRoles(['super_admin']);
|
|
|
|
// 2. Three LGUs (Tenants)
|
|
// 1. Quezon City (Primary testing LGU)
|
|
$tenantQC = Tenant::updateOrCreate(['code' => 'QC'], ['name' => 'Quezon City', 'status' => Tenant::STATUS_ACTIVE, 'qr_retail_price_centavos' => 1000]);
|
|
$this->seedTenantData($tenantQC, $password, 0);
|
|
|
|
// 2. Manila
|
|
$tenantMNL = Tenant::updateOrCreate(['code' => 'MNL'], ['name' => 'Manila', 'status' => Tenant::STATUS_ACTIVE, 'qr_retail_price_centavos' => 1000]);
|
|
$this->seedTenantData($tenantMNL, $password, 1);
|
|
|
|
// 3. Pasig
|
|
$tenantPSG = Tenant::updateOrCreate(['code' => 'PSG'], ['name' => 'Pasig', 'status' => Tenant::STATUS_ACTIVE, 'qr_retail_price_centavos' => 1000]);
|
|
$this->seedTenantData($tenantPSG, $password, 2);
|
|
}
|
|
|
|
private function seedTenantData(Tenant $tenant, string $password, int $lguIndex): void
|
|
{
|
|
// NO LGU Admin seeded as requested
|
|
|
|
// 1. Infrastructure (3 Dumpsites, 5 DOPs)
|
|
$barangay = Barangay::where('name', 'like', '%Poblacion%')->first() ?? Barangay::first();
|
|
|
|
$dumpsites = [];
|
|
for ($i = 1; $i <= 3; $i++) {
|
|
$dumpsites[] = Dumpsite::updateOrCreate(
|
|
['code' => "DS-{$tenant->code}-00{$i}"],
|
|
[
|
|
'name' => "{$tenant->code} Landfill {$i}",
|
|
'uuid' => (string) Str::uuid(),
|
|
'tenant_id' => $tenant->id,
|
|
'address_line' => "Zone {$i}",
|
|
'coordinates' => new Point(14.67 + ($i * 0.01), 121.08, 4326),
|
|
]
|
|
);
|
|
}
|
|
|
|
$dops = [];
|
|
for ($i = 1; $i <= 5; $i++) {
|
|
$dops[] = DropOffPoint::updateOrCreate(
|
|
['code' => "DOP-{$tenant->code}-00{$i}"],
|
|
[
|
|
'name' => "{$tenant->code} Plaza {$i}",
|
|
'uuid' => (string) Str::uuid(),
|
|
'tenant_id' => $tenant->id,
|
|
'barangay_id' => $barangay->id,
|
|
'address_line' => "Point {$i}",
|
|
'coordinates' => new Point(14.65 + ($i * 0.01), 121.06, 4326),
|
|
]
|
|
);
|
|
}
|
|
|
|
// 2. Staff & Residents (10 each)
|
|
$drivers = $this->seedStaff($tenant, $password, $lguIndex, 'driver', 10);
|
|
$scanners = $this->seedStaff($tenant, $password, $lguIndex, 'scanner', 10);
|
|
$helpers = $this->seedStaff($tenant, $password, $lguIndex, 'helper', 10);
|
|
$residents = $this->seedStaff($tenant, $password, $lguIndex, 'resident', 10);
|
|
|
|
// 3. Households (10 per LGU)
|
|
foreach ($residents as $i => $resident) {
|
|
ResidentProfile::firstOrCreate(['user_id' => $resident->id]);
|
|
Household::updateOrCreate(
|
|
['head_user_id' => $resident->id],
|
|
[
|
|
'uuid' => (string) Str::uuid(),
|
|
'tenant_id' => $tenant->id,
|
|
'address_line' => "Street {$i}",
|
|
'barangay_id' => $barangay->id,
|
|
'assigned_drop_off_point_id' => $dops[rand(0, 4)]->id,
|
|
'verification_status' => 'approved',
|
|
'verified_at' => now(),
|
|
]
|
|
);
|
|
}
|
|
|
|
// 4. Store Admins (5 per LGU)
|
|
for ($i = 1; $i <= 5; $i++) {
|
|
$owner = $this->createStaff("store{$i}.{$tenant->code}@verde.local", "+63921{$lguIndex}000{$i}", "Store", "Owner {$i}", User::ROLE_STORE_PARTNER, $tenant, $password);
|
|
StorePartnerProfile::updateOrCreate(['user_id' => $owner->id], ['business_name' => "Store {$i} - {$tenant->code}", 'verification_status' => 'approved', 'verified_at' => now()]);
|
|
$store = PartnerStore::updateOrCreate(
|
|
['owner_user_id' => $owner->id],
|
|
[
|
|
'uuid' => (string) Str::uuid(),
|
|
'tenant_id' => $tenant->id,
|
|
'business_name' => "Store {$i} - {$tenant->code}",
|
|
'business_permit_number' => "BP-{$tenant->code}-{$i}",
|
|
'address_line' => "Market St {$i}",
|
|
'barangay_id' => $barangay->id,
|
|
'coordinates' => new Point(14.651 + ($i * 0.001), 121.061, 4326),
|
|
'status' => PartnerStore::STATUS_ACTIVE,
|
|
]
|
|
);
|
|
$inventory = StoreInventory::firstOrCreate(['store_id' => $store->id], ['current_code_balance' => 500, 'prepaid_balance' => 0]);
|
|
|
|
// Seed actual QR codes for the store only if they don't have any
|
|
if ($store->qrCodes()->count() === 0) {
|
|
$this->generator->generate(
|
|
quantity: 500,
|
|
purpose: QrCodeBatch::PURPOSE_STORE,
|
|
targetStoreId: $store->id,
|
|
notes: "Initial stock for {$store->business_name}"
|
|
);
|
|
|
|
// Assign allocated codes to the store
|
|
QrCode::whereIn('batch_id', QrCodeBatch::where('target_store_id', $store->id)->pluck('id'))
|
|
->update([
|
|
'status' => Allocated::$name,
|
|
'assigned_to_store_id' => $store->id,
|
|
'allocated_at' => now(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
// 5. Some Initial Sales for testing
|
|
foreach ($residents as $i => $resident) {
|
|
$household = Household::where('head_user_id', $resident->id)->first();
|
|
$store = PartnerStore::where('tenant_id', $tenant->id)->first();
|
|
|
|
if ($household && $store && StoreSale::where('household_id', $household->id)->count() === 0) {
|
|
// Record a sale for 10 codes
|
|
StoreSale::create([
|
|
'store_id' => $store->id,
|
|
'household_id' => $household->id,
|
|
'user_id' => $resident->id,
|
|
'quantity' => 10,
|
|
'retail_price_centavos' => 10000,
|
|
'commission_centavos' => 2000,
|
|
'sold_at' => now(),
|
|
]);
|
|
|
|
// 5 codes are active
|
|
$codes = QrCode::where('assigned_to_store_id', $store->id)
|
|
->where('status', Allocated::$name)
|
|
->limit(5)
|
|
->get();
|
|
|
|
foreach($codes as $code) {
|
|
$code->update([
|
|
'status' => Active::$name,
|
|
'assigned_to_household_id' => $household->id,
|
|
'assigned_to_store_id' => null,
|
|
'activated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
// 5 codes are allocated (purchased but not yet active)
|
|
$allocatedCodes = QrCode::where('assigned_to_store_id', $store->id)
|
|
->where('status', Allocated::$name)
|
|
->limit(5)
|
|
->get();
|
|
|
|
foreach($allocatedCodes as $code) {
|
|
$code->update([
|
|
'status' => Allocated::$name,
|
|
'assigned_to_household_id' => $household->id,
|
|
'assigned_to_store_id' => null,
|
|
'allocated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
// Update store inventory
|
|
$store->inventory()->decrement('current_code_balance', 10);
|
|
}
|
|
}
|
|
|
|
// 6. Operations (5 Trucks, 5 Teams, 5 Routes, mixed Trips)
|
|
for ($i = 0; $i < 5; $i++) {
|
|
$truck = Truck::updateOrCreate(
|
|
['plate_number' => "VRD-{$tenant->code}-{$i}"],
|
|
[
|
|
'uuid' => (string) Str::uuid(),
|
|
'tenant_id' => $tenant->id,
|
|
'capacity_kg' => 3000,
|
|
'status' => Truck::STATUS_ACTIVE,
|
|
]
|
|
);
|
|
|
|
$team = CollectionTeam::updateOrCreate(
|
|
['name' => "{$tenant->code} Team " . ($i + 1)],
|
|
[
|
|
'uuid' => (string) Str::uuid(),
|
|
'tenant_id' => $tenant->id,
|
|
'driver_id' => $drivers[$i]->id,
|
|
'scanner_id' => $scanners[$i]->id,
|
|
'truck_id' => $truck->id,
|
|
'status' => CollectionTeam::STATUS_ACTIVE,
|
|
]
|
|
);
|
|
TeamMember::firstOrCreate(['team_id' => $team->id, 'user_id' => $helpers[$i]->id], ['role_in_team' => 'helper', 'assigned_from' => now()]);
|
|
|
|
$route = CollectionRoute::updateOrCreate(
|
|
['name' => "Route {$tenant->code}-" . ($i + 1)],
|
|
[
|
|
'uuid' => (string) Str::uuid(),
|
|
'tenant_id' => $tenant->id,
|
|
'code' => "RT-{$tenant->code}-{$i}",
|
|
'default_team_id' => $team->id,
|
|
'status' => 'active',
|
|
]
|
|
);
|
|
|
|
if ($route->stops()->count() === 0) {
|
|
foreach ($dops as $idx => $dop) {
|
|
RouteStop::create(['route_id' => $route->id, 'drop_off_point_id' => $dop->id, 'sequence' => $idx + 1]);
|
|
}
|
|
}
|
|
|
|
if ($team->trips()->count() === 0) {
|
|
$this->seedTrips($tenant, $route, $team, $truck, $dumpsites[0], $dops);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function seedStaff($tenant, $password, $lguIndex, $role, $count): array
|
|
{
|
|
$users = [];
|
|
$prefix = [
|
|
'driver' => '918',
|
|
'scanner' => '920',
|
|
'helper' => '919',
|
|
'resident' => '945'
|
|
][$role];
|
|
|
|
for ($i = 1; $i <= $count; $i++) {
|
|
$user = $this->createStaff("{$role}{$i}.{$tenant->code}@verde.local", "+63{$prefix}{$lguIndex}000{$i}", ucfirst($role), "{$i} ({$tenant->code})", "{$role}", $tenant, $password);
|
|
|
|
if ($role === 'driver') DriverProfile::updateOrCreate(['user_id' => $user->id], ['license_number' => "L-{$tenant->code}-{$i}", 'verification_status' => 'approved', 'verified_at' => now()]);
|
|
if ($role === 'scanner') ScannerProfile::updateOrCreate(['user_id' => $user->id], ['verification_status' => 'approved', 'verified_at' => now()]);
|
|
if ($role === 'helper') HelperProfile::updateOrCreate(['user_id' => $user->id], ['verification_status' => 'approved', 'verified_at' => now()]);
|
|
|
|
$users[] = $user;
|
|
}
|
|
return $users;
|
|
}
|
|
|
|
private function seedTrips($tenant, $route, $team, $truck, $dumpsite, $dops): void
|
|
{
|
|
$scenarios = [
|
|
['date' => now(), 'status' => Trip::STATUS_IN_PROGRESS],
|
|
['date' => now()->subDay(), 'status' => Trip::STATUS_COMPLETED],
|
|
['date' => now()->subMonth(), 'status' => Trip::STATUS_COMPLETED],
|
|
];
|
|
|
|
foreach ($scenarios as $scenario) {
|
|
$isComp = $scenario['status'] === Trip::STATUS_COMPLETED;
|
|
$trip = Trip::create([
|
|
'uuid' => (string) Str::uuid(),
|
|
'tenant_id' => $tenant->id,
|
|
'trip_number' => "TRIP-{$tenant->code}-" . $scenario['date']->format('YmdHi') . Str::random(4),
|
|
'route_id' => $route->id,
|
|
'team_id' => $team->id,
|
|
'truck_id' => $truck->id,
|
|
'dumpsite_id' => $dumpsite->id,
|
|
'scheduled_date' => $scenario['date']->format('Y-m-d'),
|
|
'status' => $scenario['status'],
|
|
'current_load_kg' => $isComp ? rand(1000, 2500) : 0,
|
|
'actual_start_time' => $scenario['date']->copy()->setHour(8),
|
|
'actual_end_time' => $isComp ? $scenario['date']->copy()->setHour(12) : null,
|
|
]);
|
|
|
|
foreach ($dops as $idx => $dop) {
|
|
TripStop::create([
|
|
'trip_id' => $trip->id,
|
|
'drop_off_point_id' => $dop->id,
|
|
'sequence' => $idx + 1,
|
|
'status' => $isComp ? 'completed' : 'pending',
|
|
'total_scans' => $isComp ? rand(10, 30) : 0,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function createStaff($email, $phone, $first, $last, $role, $tenant, $password): User
|
|
{
|
|
$u = User::updateOrCreate(
|
|
['email' => $email],
|
|
[
|
|
'phone' => $phone,
|
|
'password' => $password,
|
|
'first_name' => $first,
|
|
'last_name' => $last,
|
|
'role' => $role,
|
|
'status' => User::STATUS_ACTIVE,
|
|
'tenant_id' => $tenant ? $tenant->id : null,
|
|
'email_verified_at' => now(),
|
|
]
|
|
);
|
|
$u->syncRoles([$role]);
|
|
NotificationPreference::firstOrCreate(['user_id' => $u->id]);
|
|
return $u;
|
|
}
|
|
}
|