- 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.
152 lines
6.0 KiB
PHP
152 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
use App\Models\Barangay;
|
|
use App\Models\DailyCollectionStat;
|
|
use App\Models\Dumpsite;
|
|
use App\Models\DumpsiteRelease;
|
|
use App\Models\MonthlyStoreSale;
|
|
use App\Models\PartnerStore;
|
|
use App\Models\Route;
|
|
use App\Models\Trip;
|
|
use App\Models\User;
|
|
use App\Models\WeeklyRoutePerformance;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Database\Seeder;
|
|
use Illuminate\Support\Str;
|
|
use MatanYadaev\EloquentSpatial\Objects\Point;
|
|
|
|
class ReportDataSeeder extends Seeder
|
|
{
|
|
public function run(): void
|
|
{
|
|
$tenantId = 4; // GenSan
|
|
|
|
// Truncate existing data to avoid unique constraint issues
|
|
DailyCollectionStat::where('tenant_id', $tenantId)->delete();
|
|
WeeklyRoutePerformance::where('tenant_id', $tenantId)->delete();
|
|
MonthlyStoreSale::where('tenant_id', $tenantId)->delete();
|
|
|
|
$barangays = Barangay::limit(5)->get();
|
|
$routes = Route::where('tenant_id', $tenantId)->get();
|
|
$stores = PartnerStore::where('tenant_id', $tenantId)->get();
|
|
$dumpsite = Dumpsite::where('tenant_id', $tenantId)->first();
|
|
$driver = User::where('role', User::ROLE_DRIVER)->where('tenant_id', $tenantId)->first();
|
|
|
|
if ($barangays->isEmpty() || $routes->isEmpty() || $stores->isEmpty() || !$dumpsite || !$driver) {
|
|
$this->command->warn('Prerequisites for ReportDataSeeder missing. Run SimulationContinuationSeeder first.');
|
|
return;
|
|
}
|
|
|
|
$this->seedDailyStats($tenantId, $barangays);
|
|
$this->seedWeeklyPerformance($tenantId, $routes);
|
|
$this->seedMonthlySales($tenantId, $stores);
|
|
$this->seedDumpsiteReleases($tenantId, $dumpsite, $driver);
|
|
|
|
$this->command->info('Report data seeded successfully!');
|
|
}
|
|
|
|
private function seedDailyStats($tenantId, $barangays): void
|
|
{
|
|
$this->command->info('Seeding Daily Collection Stats...');
|
|
for ($i = 30; $i >= 0; $i--) {
|
|
$date = Carbon::now()->subDays($i);
|
|
foreach ($barangays as $barangay) {
|
|
$baseScans = 150 + rand(-50, 100);
|
|
$baseWeight = $baseScans * 5 + rand(-50, 50);
|
|
|
|
DailyCollectionStat::withoutGlobalScopes()->updateOrCreate(
|
|
['date' => $date->toDateString(), 'barangay_id' => $barangay->id],
|
|
[
|
|
'tenant_id' => $tenantId,
|
|
'total_scans' => $baseScans,
|
|
'total_weight_kg' => $baseWeight,
|
|
'unique_households' => (int)($baseScans * 0.8),
|
|
'missed_pickups' => rand(0, 10),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function seedWeeklyPerformance($tenantId, $routes): void
|
|
{
|
|
$this->command->info('Seeding Weekly Route Performance...');
|
|
for ($i = 8; $i >= 0; $i--) {
|
|
$weekStart = Carbon::now()->subWeeks($i)->startOfWeek();
|
|
foreach ($routes as $route) {
|
|
WeeklyRoutePerformance::withoutGlobalScopes()->updateOrCreate(
|
|
['week_start_date' => $weekStart->toDateString(), 'route_id' => $route->id],
|
|
[
|
|
'tenant_id' => $tenantId,
|
|
'on_time_rate_percent' => rand(85, 98),
|
|
'avg_trip_duration_minutes' => rand(150, 210),
|
|
'completion_rate_percent' => rand(95, 100),
|
|
'trips_count' => 6,
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function seedMonthlySales($tenantId, $stores): void
|
|
{
|
|
$this->command->info('Seeding Monthly Store Sales...');
|
|
for ($i = 6; $i >= 0; $i--) {
|
|
$monthStart = Carbon::now()->subMonths($i)->startOfMonth();
|
|
foreach ($stores as $store) {
|
|
$qty = 200 + rand(-50, 300);
|
|
$retail = $qty * 1500;
|
|
$commission = (int)($retail * ($store->commission_rate_percent / 100));
|
|
|
|
MonthlyStoreSale::withoutGlobalScopes()->updateOrCreate(
|
|
['month_start_date' => $monthStart->toDateString(), 'store_id' => $store->id],
|
|
[
|
|
'tenant_id' => $tenantId,
|
|
'total_quantity_sold' => $qty,
|
|
'total_retail_centavos' => $retail,
|
|
'total_commission_centavos' => $commission,
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function seedDumpsiteReleases($tenantId, $dumpsite, $driver): void
|
|
{
|
|
$this->command->info('Seeding Dumpsite Releases...');
|
|
for ($i = 20; $i >= 1; $i--) {
|
|
$releasedAt = Carbon::now()->subDays($i)->setTime(11, 30);
|
|
$trip = Trip::firstOrCreate(
|
|
['trip_number' => "HIST-TRIP-" . $releasedAt->format('Ymd')],
|
|
[
|
|
'uuid' => (string) Str::uuid(),
|
|
'tenant_id' => $tenantId,
|
|
'route_id' => Route::where('tenant_id', $tenantId)->first()->id,
|
|
'team_id' => 1,
|
|
'truck_id' => 1,
|
|
'scheduled_date' => $releasedAt->toDateString(),
|
|
'status' => Trip::STATUS_COMPLETED,
|
|
'actual_end_time' => $releasedAt,
|
|
'end_reason' => 'completed'
|
|
]
|
|
);
|
|
|
|
DumpsiteRelease::updateOrCreate(
|
|
['tenant_id' => $tenantId, 'trip_id' => $trip->id],
|
|
[
|
|
'dumpsite_id' => $dumpsite->id,
|
|
'released_at' => $releasedAt,
|
|
'released_by_driver_id' => $driver->id,
|
|
'weight_kg' => 4500 + rand(-500, 500),
|
|
'waste_type_breakdown' => ['mixed' => 70, 'recyclable' => 20, 'organic' => 10],
|
|
'gate_pass_number' => "GP-" . strtoupper(Str::random(8)),
|
|
'dumpsite_attendant_name' => "Attendant " . rand(1, 5),
|
|
'coordinates_at_release' => new Point(6.1136, 125.1719, 4326),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|