300 lines
14 KiB
PHP
300 lines
14 KiB
PHP
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
use Illuminate\Database\Seeder;
|
|
use App\Models\User;
|
|
use Modules\ContractorManagement\Models\Contractor;
|
|
use Modules\ProjectManagement\Models\Project;
|
|
use Modules\ProjectManagement\Models\Task;
|
|
use Modules\ProjectManagement\Enums\ProjectStatus;
|
|
use Modules\ProjectManagement\Enums\TaskStatus;
|
|
use Modules\DailyReports\Models\DailyReport;
|
|
use Modules\DocumentManagement\Models\Document;
|
|
use Modules\DocumentManagement\Models\DocumentCategory;
|
|
use Modules\MasterData\Models\Material;
|
|
use Modules\MaterialLogistics\Models\Warehouse;
|
|
use Modules\MaterialLogistics\Models\ProjectInventory;
|
|
use Faker\Factory as Faker;
|
|
use Illuminate\Support\Str;
|
|
|
|
class DemoProjectContractorSeeder extends Seeder
|
|
{
|
|
/**
|
|
* Run the database seeds.
|
|
*/
|
|
public function run(): void
|
|
{
|
|
$faker = Faker::create();
|
|
|
|
$this->command->info('Seeding Customers...');
|
|
$customers = [];
|
|
for ($i = 1; $i <= 3; $i++) {
|
|
$customers[] = User::firstOrCreate(
|
|
['email' => "customer{$i}@demo.com"],
|
|
[
|
|
'name' => "Demo Customer {$i}",
|
|
'password' => bcrypt('password'),
|
|
'user_type' => 'customer',
|
|
]
|
|
);
|
|
}
|
|
|
|
$this->command->info('Seeding Contractors...');
|
|
$specializations = ['General Building', 'MEP', 'Civil Works', 'Landscaping', 'Interior Fit-out'];
|
|
$contractors = [];
|
|
for ($i = 1; $i <= 5; $i++) {
|
|
$contractors[] = Contractor::firstOrCreate(
|
|
['email' => "contractor{$i}@demo.com"],
|
|
[
|
|
'company_name' => "Demo Contractor {$i}",
|
|
'contact_person' => $faker->name,
|
|
'phone' => $faker->phoneNumber,
|
|
'specialization' => $specializations[array_rand($specializations)],
|
|
'address' => $faker->address,
|
|
'tax_id' => $faker->numerify('TAX-####-####'),
|
|
'payment_terms' => 'Net 30',
|
|
'rating' => 4.5,
|
|
'status' => 'active',
|
|
'type' => 'sub',
|
|
'parent_id' => \Modules\ContractorManagement\Models\Contractor::where('type', 'main')->value('id') ?? null,
|
|
]
|
|
);
|
|
}
|
|
|
|
$this->command->info('Seeding Materials Catalog...');
|
|
$materialNames = ['Portland Cement 40kg', 'Steel Rebar 12mm', 'Concrete Hollow Blocks 4"', 'Sand (per cu.m)', 'Gravel (per cu.m)', 'Plywood 1/2"', 'PVC Pipe 2"'];
|
|
$materials = [];
|
|
foreach ($materialNames as $mName) {
|
|
$materials[] = Material::firstOrCreate(
|
|
['name' => $mName],
|
|
[
|
|
'sku' => strtoupper(Str::random(8)),
|
|
'category' => 'Construction',
|
|
'unit' => $faker->randomElement(['pcs', 'bags', 'cu.m', 'length']),
|
|
'unit_cost' => $faker->randomFloat(2, 50, 1000),
|
|
'status' => 'active',
|
|
'type' => 'single',
|
|
]
|
|
);
|
|
}
|
|
|
|
$this->command->info('Seeding Main Warehouse...');
|
|
$warehouse = Warehouse::firstOrCreate(
|
|
['code' => 'WH-CEN-01'],
|
|
[
|
|
'name' => 'Central Depot',
|
|
'address' => $faker->address,
|
|
'type' => 'main',
|
|
'status' => 'active',
|
|
]
|
|
);
|
|
|
|
$this->command->info('Seeding Document Categories...');
|
|
$docCategories = [];
|
|
foreach (['Architectural', 'Structural', 'MEP', 'Permits', 'Contracts'] as $catName) {
|
|
$docCategories[] = DocumentCategory::firstOrCreate(
|
|
['name' => $catName],
|
|
['description' => "Standard $catName category"]
|
|
);
|
|
}
|
|
|
|
$this->command->info('Seeding Projects & Tasks...');
|
|
$statuses = [
|
|
ProjectStatus::Planning,
|
|
ProjectStatus::InProgress,
|
|
ProjectStatus::Completed,
|
|
ProjectStatus::OnHold
|
|
];
|
|
|
|
$projectNames = [
|
|
'Skyline Tower Construction', 'Riverside Commercial Complex', 'Green Valley Residential Estate',
|
|
'Harbor View Bridge Expansion', 'Downtown Plaza Renovation', 'Metro Hospital Annex',
|
|
'Tech Park Office Building', 'Sunrise Shopping Mall', 'Highway 95 Repaving', 'Central Station Modernization'
|
|
];
|
|
|
|
$taskNames = [
|
|
'Site Clearing and Preparation', 'Excavation and Trenching', 'Foundation Concrete Pouring',
|
|
'Steel Framework Erection', 'Plumbing Rough-in', 'Electrical Wiring Installation',
|
|
'HVAC System Setup', 'Roofing and Waterproofing', 'Interior Drywall Installation', 'Painting and Finishing'
|
|
];
|
|
|
|
for ($i = 1; $i <= 10; $i++) {
|
|
$contractor = $contractors[array_rand($contractors)];
|
|
$customer = $customers[array_rand($customers)];
|
|
|
|
$startDate = $faker->dateTimeBetween('-1 year', '-1 month');
|
|
$endDate = (clone $startDate)->modify('+' . $faker->numberBetween(3, 24) . ' months');
|
|
|
|
$projectName = $projectNames[$i - 1];
|
|
$project = Project::firstOrCreate(
|
|
['name' => $projectName],
|
|
[
|
|
'description' => "Construction project for {$projectName}.",
|
|
'customer_id' => $customer->id,
|
|
'client_name' => $customer->name,
|
|
'contractor_id' => $contractor->id,
|
|
'location' => $faker->address,
|
|
'status' => $statuses[array_rand($statuses)],
|
|
'contract_value' => $faker->randomFloat(2, 50000, 5000000),
|
|
'contract_duration' => $faker->numberBetween(3, 24),
|
|
'start_date' => $startDate->format('Y-m-d'),
|
|
'target_end_date' => $endDate->format('Y-m-d'),
|
|
'completion_percentage' => $faker->randomFloat(2, 0, 100),
|
|
'total_capitalization' => 0,
|
|
]
|
|
);
|
|
|
|
// Sync contractor relation safely
|
|
$contractor->projects()->syncWithoutDetaching([
|
|
$project->id => ['role' => 'Primary Contractor', 'contract_amount' => $project->contract_value]
|
|
]);
|
|
|
|
$pmRoleExists = \Spatie\Permission\Models\Role::where('name', 'Project Manager')->exists();
|
|
if ($pmRoleExists) {
|
|
$pm = User::role('Project Manager')->first();
|
|
if ($pm) {
|
|
$project->personnel()->syncWithoutDetaching([$pm->id => ['role' => 'pm']]);
|
|
}
|
|
}
|
|
|
|
// Seed Tasks for this Project
|
|
$selectedTasks = $faker->randomElements($taskNames, 5);
|
|
foreach ($selectedTasks as $t => $taskName) {
|
|
Task::firstOrCreate(
|
|
['project_id' => $project->id, 'name' => $taskName],
|
|
[
|
|
'description' => "Execution of {$taskName}",
|
|
'status' => $faker->randomElement([TaskStatus::Pending, TaskStatus::InProgress, TaskStatus::Completed]),
|
|
'estimated_hours' => $faker->numberBetween(10, 100),
|
|
'start_date' => $startDate->format('Y-m-d'),
|
|
'end_date' => $endDate->format('Y-m-d'),
|
|
]
|
|
);
|
|
}
|
|
|
|
// Seed Milestones
|
|
if ($project->milestones()->count() === 0) {
|
|
$defaults = [
|
|
['name' => 'Mobilization', 'weight_percentage' => 5],
|
|
['name' => 'Earthworks & Foundation', 'weight_percentage' => 15],
|
|
['name' => 'Structural Works', 'weight_percentage' => 25],
|
|
['name' => 'Roofing & Waterproofing', 'weight_percentage' => 15],
|
|
['name' => 'Architectural Finishing', 'weight_percentage' => 20],
|
|
['name' => 'MEP Rough-In', 'weight_percentage' => 10],
|
|
['name' => 'Final Inspection & Punch List', 'weight_percentage' => 5],
|
|
['name' => 'Turnover', 'weight_percentage' => 5],
|
|
];
|
|
|
|
$duration = $project->contract_duration;
|
|
$count = count($defaults);
|
|
|
|
foreach ($defaults as $idx => $milestone) {
|
|
$plannedDate = null;
|
|
if ($startDate && $duration && $duration > 0) {
|
|
$daysOffset = (int) round(($idx + 1) / $count * $duration * 30); // duration is in months, approx * 30 days
|
|
$plannedDate = (clone $startDate)->modify("+{$daysOffset} days");
|
|
}
|
|
|
|
$project->milestones()->create([
|
|
'name' => $milestone['name'],
|
|
'weight_percentage' => $milestone['weight_percentage'],
|
|
'sort_order' => $idx,
|
|
'is_default' => true,
|
|
'planned_date' => $plannedDate,
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Seed Daily Reports for this Project (if InProgress)
|
|
if ($project->status === ProjectStatus::InProgress) {
|
|
for ($r = 1; $r <= 2; $r++) {
|
|
DailyReport::firstOrCreate(
|
|
['project_id' => $project->id, 'report_number' => "DR-{$project->id}-{$r}"],
|
|
[
|
|
'user_id' => $customers[0]->id,
|
|
'report_date' => $faker->dateTimeBetween($startDate, 'now')->format('Y-m-d'),
|
|
'weather' => $faker->randomElement(['Sunny', 'Cloudy', 'Rainy']),
|
|
'temperature' => $faker->numberBetween(20, 35) . 'C',
|
|
'work_accomplished' => "Completed general works for the day.",
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
// Seed Documents for this Project
|
|
for ($d = 1; $d <= 3; $d++) {
|
|
$cat = $docCategories[array_rand($docCategories)];
|
|
Document::firstOrCreate(
|
|
['project_id' => $project->id, 'title' => "Document {$d} for Project {$i}"],
|
|
[
|
|
'category_id' => $cat->id,
|
|
'status' => $faker->randomElement(['Pending', 'Approved']),
|
|
'description' => "Sample attached document",
|
|
'file_size' => $faker->numberBetween(1024, 10485760),
|
|
'version_count' => 1,
|
|
'mime_type' => 'application/pdf',
|
|
]
|
|
);
|
|
}
|
|
|
|
// Seed Project Inventory
|
|
$selectedMats = $faker->randomElements($materials, 3);
|
|
foreach ($selectedMats as $mat) {
|
|
ProjectInventory::firstOrCreate(
|
|
['project_id' => $project->id, 'material_id' => $mat->id],
|
|
[
|
|
'on_hand_qty' => $faker->numberBetween(10, 500),
|
|
'unit_cost' => $mat->unit_cost,
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
$this->command->info('Seeding Bidding Packages...');
|
|
$bidStatuses = [\Modules\BiddingManagement\Enums\BidPackageStatus::Open, \Modules\BiddingManagement\Enums\BidPackageStatus::Evaluating, \Modules\BiddingManagement\Enums\BidPackageStatus::Awarded];
|
|
$allProjects = Project::all();
|
|
|
|
if ($allProjects->count() > 0) {
|
|
for ($b = 1; $b <= 3; $b++) {
|
|
$proj = $allProjects->random();
|
|
|
|
$package = \Modules\BiddingManagement\Models\BidPackage::firstOrCreate(
|
|
['title' => "Bid Package {$b} - {$proj->name}"],
|
|
[
|
|
'project_id' => $proj->id,
|
|
'created_by' => $customers[0]->id,
|
|
'description' => "Tender document for phase {$b}",
|
|
'status' => $faker->randomElement($bidStatuses),
|
|
'evaluation_mode' => \Modules\BiddingManagement\Enums\EvaluationMode::Simple,
|
|
'submission_deadline' => $faker->dateTimeBetween('now', '+1 month'),
|
|
'validity_period' => clone $faker->dateTimeBetween('+2 months', '+3 months'),
|
|
]
|
|
);
|
|
|
|
// Invite contractors
|
|
$invitedContractors = $faker->randomElements($contractors, 2);
|
|
foreach ($invitedContractors as $ic) {
|
|
$invitation = \Modules\BiddingManagement\Models\BidInvitation::firstOrCreate(
|
|
['bid_package_id' => $package->id, 'contractor_id' => $ic->id],
|
|
['status' => 'invited', 'invited_at' => now()]
|
|
);
|
|
|
|
// Add Submission
|
|
\Modules\BiddingManagement\Models\BidSubmission::firstOrCreate(
|
|
['bid_invitation_id' => $invitation->id],
|
|
[
|
|
'submitted_price' => $faker->randomFloat(2, 10000, 500000),
|
|
'completion_days' => $faker->numberBetween(30, 180),
|
|
'status' => \Modules\BiddingManagement\Enums\BidSubmissionStatus::Submitted,
|
|
'submitted_at' => now(),
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->command->info('Demo Data seeded successfully across all modules!');
|
|
}
|
|
}
|