Files
nnterp-react-admin/database/seeders/DemoPropertyManagementSeeder.php

304 lines
20 KiB
PHP

<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Carbon\Carbon;
class DemoPropertyManagementSeeder extends Seeder
{
public function run(DemoContext $ctx): void
{
if (DB::table('pm_properties')->where('created_by', $ctx->userId)->exists()) {
return;
}
// Self-healing: ensure schema matches what the seeder expects
// (migrations may have run out of order on production)
DB::statement("ALTER TABLE pm_properties MODIFY COLUMN status ENUM('available','occupied','reserved','under_maintenance') NOT NULL DEFAULT 'available'");
DB::statement("ALTER TABLE pm_properties MODIFY COLUMN purpose ENUM('residential','commercial','industrial','land','mixed') NOT NULL DEFAULT 'residential'");
if (!Schema::hasColumn('pm_properties', 'selected_agent')) {
Schema::table('pm_properties', function ($t) { $t->unsignedBigInteger('selected_agent')->nullable()->after('created_by'); });
}
if (!Schema::hasColumn('pm_leases', 'billing_cycle')) {
Schema::table('pm_leases', function ($t) {
$t->string('billing_cycle', 20)->default('monthly')->after('monthly_rent');
$t->date('next_invoice_date')->nullable()->after('billing_cycle');
$t->string('invoice_prefix', 10)->default('RENT')->after('next_invoice_date');
});
}
if (!Schema::hasColumn('pm_leases', 'tenant_user_id')) {
Schema::table('pm_leases', function ($t) { $t->unsignedBigInteger('tenant_user_id')->nullable()->after('tenant_email'); });
}
$now = now();
$uid = $ctx->userId;
// 1. Categories
$categories = [];
foreach (['Residential', 'Commercial', 'Industrial', 'Condominium', 'Townhouse', 'Warehouse'] as $name) {
$categories[$name] = DB::table('pm_property_categories')->insertGetId([
'name' => $name, 'icon' => strtolower($name), 'is_active' => true,
'created_by' => $uid, 'created_at' => $now, 'updated_at' => $now,
]);
}
// 2. Ownership Types
$ownershipTypes = [];
foreach (['Freehold', 'Leasehold', 'Co-operative', 'Joint Venture', 'REIT'] as $name) {
$ownershipTypes[] = DB::table('pm_ownership_types')->insertGetId([
'name' => $name, 'is_active' => true,
'created_by' => $uid, 'created_at' => $now, 'updated_at' => $now,
]);
}
// 3. Area Types
$areaTypes = [];
foreach (['Gross Floor Area', 'Net Usable Area', 'Carpeted Area', 'Super Built-up Area'] as $name) {
$areaTypes[] = DB::table('pm_area_types')->insertGetId([
'name' => $name, 'is_active' => true,
'created_by' => $uid, 'created_at' => $now, 'updated_at' => $now,
]);
}
// 4. Energy Types
$energyTypes = [];
foreach (['A+ (Excellent)', 'A (Very Good)', 'B (Good)', 'C (Average)'] as $name) {
$energyTypes[] = DB::table('pm_energy_efficiency_types')->insertGetId([
'name' => $name, 'is_active' => true,
'created_by' => $uid, 'created_at' => $now, 'updated_at' => $now,
]);
}
// 5. Amenity Types + Amenities
$amenityNames = ['Swimming Pool', 'Gym/Fitness Center', 'Covered Parking', '24/7 Security', 'Elevator', 'Rooftop Deck', 'Function Room', 'Playground', 'Laundry Area', 'CCTV System', 'Fire Alarm', 'Garden/Landscape'];
$amenityIds = [];
foreach ($amenityNames as $name) {
$typeId = DB::table('pm_amenity_types')->insertGetId([
'name' => $name, 'is_active' => true,
'created_by' => $uid, 'created_at' => $now, 'updated_at' => $now,
]);
// Create actual amenity record referencing the type
$amenityIds[] = DB::table('pm_amenities')->insertGetId([
'name' => $name, 'icon' => strtolower(str_replace(['/', ' '], ['_', '_'], $name)),
'amenity_type_id' => $typeId, 'is_active' => true,
'created_by' => $uid, 'created_at' => $now, 'updated_at' => $now,
]);
}
// 6. Properties (10)
$properties = [
['name' => 'Sunset Residences Unit 301', 'address' => '301 Sunset Blvd, Brgy. San Antonio', 'city' => 'Makati', 'country' => 'Philippines', 'zip_code' => '1203', 'size' => 65, 'bedrooms' => 2, 'bathrooms' => 1, 'rooms' => 4, 'floor' => 3, 'rent_price_incl' => 25000, 'status' => 'occupied', 'purpose' => 'residential', 'cat' => 'Condominium'],
['name' => 'Greenfield Commercial Space', 'address' => '88 Greenfield Ave, Brgy. Ugong', 'city' => 'Pasig', 'country' => 'Philippines', 'zip_code' => '1604', 'size' => 150, 'bedrooms' => 0, 'bathrooms' => 2, 'rooms' => 3, 'floor' => 1, 'rent_price_incl' => 85000, 'status' => 'occupied', 'purpose' => 'commercial', 'cat' => 'Commercial'],
['name' => 'Pine Valley Townhouse', 'address' => 'Lot 12 Block 5, Pine Valley Subd', 'city' => 'Antipolo', 'country' => 'Philippines', 'zip_code' => '1870', 'size' => 120, 'bedrooms' => 3, 'bathrooms' => 2, 'rooms' => 6, 'floor' => 2, 'rent_price_incl' => 18000, 'status' => 'occupied', 'purpose' => 'residential', 'cat' => 'Townhouse'],
['name' => 'Metro Tower Office 15F', 'address' => '15F Metro Tower, Ayala Ave', 'city' => 'Makati', 'country' => 'Philippines', 'zip_code' => '1226', 'size' => 200, 'bedrooms' => 0, 'bathrooms' => 3, 'rooms' => 5, 'floor' => 15, 'rent_price_incl' => 120000, 'status' => 'occupied', 'purpose' => 'commercial', 'cat' => 'Commercial'],
['name' => 'Lakeview Apartment 2B', 'address' => 'Unit 2B, Lakeview Gardens', 'city' => 'Taguig', 'country' => 'Philippines', 'zip_code' => '1634', 'size' => 45, 'bedrooms' => 1, 'bathrooms' => 1, 'rooms' => 3, 'floor' => 2, 'rent_price_incl' => 15000, 'status' => 'available', 'purpose' => 'residential', 'cat' => 'Residential'],
['name' => 'BGC Studio Unit', 'address' => '32F One Uptown, BGC', 'city' => 'Taguig', 'country' => 'Philippines', 'zip_code' => '1634', 'size' => 32, 'bedrooms' => 0, 'bathrooms' => 1, 'rooms' => 1, 'floor' => 32, 'rent_price_incl' => 35000, 'status' => 'occupied', 'purpose' => 'residential', 'cat' => 'Condominium'],
['name' => 'Makati Warehouse A', 'address' => 'Lot 5, Makati Industrial Park', 'city' => 'Makati', 'country' => 'Philippines', 'zip_code' => '1218', 'size' => 500, 'bedrooms' => 0, 'bathrooms' => 1, 'rooms' => 2, 'floor' => 1, 'rent_price_incl' => 65000, 'status' => 'occupied', 'purpose' => 'industrial', 'cat' => 'Warehouse'],
['name' => 'Cebu Beach House', 'address' => 'Lot 8 Moalboal Road', 'city' => 'Cebu', 'country' => 'Philippines', 'zip_code' => '6032', 'size' => 180, 'bedrooms' => 4, 'bathrooms' => 3, 'rooms' => 8, 'floor' => 1, 'rent_price_incl' => 22000, 'status' => 'reserved', 'purpose' => 'residential', 'cat' => 'Residential'],
['name' => 'Davao Commercial Lot', 'address' => 'Lot 3, JP Laurel Ave', 'city' => 'Davao', 'country' => 'Philippines', 'zip_code' => '8000', 'size' => 300, 'bedrooms' => 0, 'bathrooms' => 0, 'rooms' => 0, 'floor' => 0, 'rent_price_incl' => 0, 'status' => 'available', 'purpose' => 'commercial', 'cat' => 'Commercial'],
['name' => 'QC Industrial Compound', 'address' => 'Compound A, Balintawak', 'city' => 'Quezon City', 'country' => 'Philippines', 'zip_code' => '1106', 'size' => 800, 'bedrooms' => 0, 'bathrooms' => 2, 'rooms' => 4, 'floor' => 1, 'rent_price_incl' => 95000, 'status' => 'under_maintenance', 'purpose' => 'industrial', 'cat' => 'Industrial'],
];
$propertyIds = [];
$agent = $ctx->randomStaff();
foreach ($properties as $i => $p) {
$propertyIds[] = DB::table('pm_properties')->insertGetId([
'name' => $p['name'], 'short_description' => "Demo property: {$p['name']}",
'long_description' => "This is a demo {$p['purpose']} property located in {$p['city']}, Philippines. It features {$p['size']} sqm of space.",
'address' => $p['address'], 'country' => $p['country'], 'city' => $p['city'], 'zip_code' => $p['zip_code'],
'size' => $p['size'], 'bedrooms' => $p['bedrooms'], 'bathrooms' => $p['bathrooms'], 'rooms' => $p['rooms'], 'floor' => $p['floor'],
'sale_price' => 0, 'rent_price_incl' => $p['rent_price_incl'], 'rent_price_excl' => round($p['rent_price_incl'] * 0.88),
'category_id' => $categories[$p['cat']], 'ownership_type_id' => $ownershipTypes[$i % count($ownershipTypes)],
'area_type_id' => $areaTypes[$i % count($areaTypes)], 'energy_efficiency_type_id' => $energyTypes[$i % count($energyTypes)],
'status' => $p['status'], 'purpose' => $p['purpose'], 'selected_agent' => $agent,
'is_featured' => $i < 3, 'is_enabled' => true, 'created_by' => $uid, 'created_at' => $now, 'updated_at' => $now,
]);
}
$ctx->propertyIds = $propertyIds;
// 7. Property Amenities (2-5 per property)
foreach ($propertyIds as $propId) {
$count = rand(2, 5);
$randomAmenities = array_rand(array_flip($amenityIds), min($count, count($amenityIds)));
if (!is_array($randomAmenities)) $randomAmenities = [$randomAmenities];
foreach ($randomAmenities as $amenityId) {
DB::table('pm_property_amenities')->insert([
'property_id' => $propId, 'amenity_id' => $amenityId,
]);
}
}
// 8. Leases (8 — 6 active, 2 expired) for occupied properties
$occupiedProps = [0, 1, 2, 3, 5, 6]; // indices of occupied properties
$tenants = [
['name' => 'Maria Santos', 'email' => 'maria.santos@demo.test', 'phone' => '+639171234567'],
['name' => 'Juan dela Cruz', 'email' => 'juan.delacruz@demo.test', 'phone' => '+639182345678'],
['name' => 'Ana Reyes', 'email' => 'ana.reyes@demo.test', 'phone' => '+639193456789'],
['name' => 'Carlos Garcia', 'email' => 'carlos.garcia@demo.test', 'phone' => '+639204567890'],
['name' => 'Sofia Hernandez', 'email' => 'sofia.hernandez@demo.test', 'phone' => '+639215678901'],
['name' => 'Miguel Torres', 'email' => 'miguel.torres@demo.test', 'phone' => '+639226789012'],
['name' => 'Rosa Aquino', 'email' => 'rosa.aquino@demo.test', 'phone' => '+639237890123'],
['name' => 'Pedro Ramos', 'email' => 'pedro.ramos@demo.test', 'phone' => '+639248901234'],
];
$leaseIds = [];
// Active leases
foreach ($occupiedProps as $idx => $propIdx) {
$t = $tenants[$idx];
$start = Carbon::now()->subMonths(rand(3, 10));
$leaseIds[] = DB::table('pm_leases')->insertGetId([
'property_id' => $propertyIds[$propIdx],
'tenant_name' => $t['name'], 'tenant_email' => $t['email'], 'tenant_phone' => $t['phone'],
'start_date' => $start->format('Y-m-d'), 'end_date' => $start->copy()->addYear()->format('Y-m-d'),
'monthly_rent' => $properties[$propIdx]['rent_price_incl'],
'billing_cycle' => 'monthly', 'next_invoice_date' => Carbon::now()->addDays(rand(1, 15))->format('Y-m-d'),
'security_deposit' => $properties[$propIdx]['rent_price_incl'] * 2,
'lease_status' => 'active', 'auto_invoice' => true,
'created_by' => $uid, 'created_at' => $start, 'updated_at' => $now,
]);
}
// 2 expired leases
foreach ([6, 7] as $tIdx) {
$t = $tenants[$tIdx];
$start = Carbon::now()->subMonths(18);
$leaseIds[] = DB::table('pm_leases')->insertGetId([
'property_id' => $propertyIds[4], // Lakeview (available)
'tenant_name' => $t['name'], 'tenant_email' => $t['email'], 'tenant_phone' => $t['phone'],
'start_date' => $start->format('Y-m-d'), 'end_date' => $start->copy()->addYear()->format('Y-m-d'),
'monthly_rent' => 14000, 'billing_cycle' => 'monthly',
'security_deposit' => 28000, 'lease_status' => 'expired',
'created_by' => $uid, 'created_at' => $start, 'updated_at' => $now,
]);
}
// 9. Maintenance Requests (6)
$maintenanceData = [
['prop' => 0, 'title' => 'Leaking faucet in bathroom', 'priority' => 'medium', 'status' => 'resolved', 'est' => 2500, 'act' => 1800, 'days' => 14],
['prop' => 1, 'title' => 'AC unit not cooling properly', 'priority' => 'high', 'status' => 'in_progress', 'est' => 15000, 'act' => null, 'days' => 5],
['prop' => 2, 'title' => 'Broken window lock', 'priority' => 'medium', 'status' => 'assigned', 'est' => 3000, 'act' => null, 'days' => 3],
['prop' => 3, 'title' => 'Elevator maintenance required', 'priority' => 'critical', 'status' => 'open', 'est' => 50000, 'act' => null, 'days' => 1],
['prop' => 5, 'title' => 'Water heater replacement', 'priority' => 'high', 'status' => 'resolved', 'est' => 8000, 'act' => 9500, 'days' => 21],
['prop' => 6, 'title' => 'Roof leak during typhoon', 'priority' => 'critical', 'status' => 'in_progress', 'est' => 25000, 'act' => null, 'days' => 2],
];
$maintenanceIds = [];
foreach ($maintenanceData as $m) {
$created = Carbon::now()->subDays($m['days']);
$maintenanceIds[] = DB::table('pm_maintenance_requests')->insertGetId([
'property_id' => $propertyIds[$m['prop']], 'lease_id' => $leaseIds[$m['prop']] ?? null,
'title' => $m['title'], 'description' => "Demo maintenance: {$m['title']}",
'priority' => $m['priority'], 'status' => $m['status'],
'assigned_to' => $m['status'] !== 'open' ? $agent : null,
'estimated_cost' => $m['est'], 'actual_cost' => $m['act'] ?? 0,
'resolved_at' => $m['status'] === 'resolved' ? $now : null,
'requested_by' => $uid, 'created_by' => $uid,
'created_at' => $created, 'updated_at' => $now,
]);
}
// 10. Maintenance Files (metadata only)
foreach (array_slice($maintenanceIds, 0, 3) as $mId) {
DB::table('pm_maintenance_files')->insert([
'maintenance_request_id' => $mId,
'filename' => 'photo_evidence.jpg', 'filepath' => 'maintenance/files/demo_placeholder.jpg',
'created_at' => $now, 'updated_at' => $now,
]);
}
// 11. Invoice Links (2 per active lease = 12 total)
foreach (array_slice($leaseIds, 0, 6) as $idx => $leaseId) {
$lease = DB::table('pm_leases')->find($leaseId);
for ($month = 1; $month <= 2; $month++) {
$period = Carbon::now()->subMonths($month)->format('Y-m');
// Link to existing sales invoices if available, otherwise create a stub
$invoiceId = !empty($ctx->salesInvoiceIds) ? $ctx->salesInvoiceIds[$idx % count($ctx->salesInvoiceIds)] : null;
if ($invoiceId) {
DB::table('pm_invoice_links')->insert([
'property_id' => $lease->property_id, 'lease_id' => $leaseId,
'invoice_id' => $invoiceId, 'billing_period' => $period,
'created_at' => $now, 'updated_at' => $now,
]);
}
}
}
// 12. Expense Links (6 — 1 per occupied property)
$expenseIds = DB::table('expenses')->where('created_by', $uid)->pluck('id')->toArray();
foreach (array_slice($propertyIds, 0, min(6, count($expenseIds))) as $i => $propId) {
if (isset($expenseIds[$i])) {
DB::table('pm_expense_links')->insert([
'property_id' => $propId, 'expense_id' => $expenseIds[$i],
'created_at' => $now, 'updated_at' => $now,
]);
}
}
// 13. Property Notes (5)
$notes = [
['prop' => 0, 'note' => 'Tenant renewed for another year. Consider increasing rent by 5% next cycle.', 'pinned' => true],
['prop' => 1, 'note' => 'Fire safety inspection due next month. Contact building admin.', 'pinned' => true],
['prop' => 3, 'note' => 'Office layout reconfiguration approved by tenant. Budget: ₱150,000.', 'pinned' => false],
['prop' => 5, 'note' => 'Tenant requests pet-friendly policy update. Review HOA rules.', 'pinned' => false],
['prop' => 9, 'note' => 'Structural assessment completed. Estimated repair timeline: 6 weeks.', 'pinned' => true],
];
foreach ($notes as $n) {
DB::table('pm_property_notes')->insert([
'property_id' => $propertyIds[$n['prop']], 'note_text' => $n['note'],
'note_type' => 'general', 'is_pinned' => $n['pinned'], 'created_by' => $uid,
'created_at' => Carbon::now()->subDays(rand(1, 30)), 'updated_at' => $now,
]);
}
// 14. Property History (10 events)
$historyEvents = [
['prop' => 0, 'event' => 'status_change', 'field' => 'status', 'old' => 'available', 'new' => 'occupied', 'days' => 90],
['prop' => 1, 'event' => 'status_change', 'field' => 'status', 'old' => 'available', 'new' => 'occupied', 'days' => 60],
['prop' => 2, 'event' => 'status_change', 'field' => 'status', 'old' => 'available', 'new' => 'occupied', 'days' => 45],
['prop' => 3, 'event' => 'lease_created', 'field' => null, 'old' => null, 'new' => 'Active lease signed', 'days' => 120],
['prop' => 4, 'event' => 'status_change', 'field' => 'status', 'old' => 'occupied', 'new' => 'available', 'days' => 30],
['prop' => 5, 'event' => 'rent_increase', 'field' => 'rent_price_incl', 'old' => '30000', 'new' => '35000', 'days' => 15],
['prop' => 6, 'event' => 'status_change', 'field' => 'status', 'old' => 'available', 'new' => 'occupied', 'days' => 150],
['prop' => 7, 'event' => 'status_change', 'field' => 'status', 'old' => 'available', 'new' => 'reserved', 'days' => 7],
['prop' => 9, 'event' => 'status_change', 'field' => 'status', 'old' => 'occupied', 'new' => 'under_maintenance', 'days' => 10],
['prop' => 0, 'event' => 'agent_assigned', 'field' => 'selected_agent', 'old' => null, 'new' => 'Agent assigned', 'days' => 100],
];
foreach ($historyEvents as $h) {
DB::table('pm_property_history')->insert([
'property_id' => $propertyIds[$h['prop']], 'event' => $h['event'],
'field' => $h['field'], 'old_value' => $h['old'], 'new_value' => $h['new'],
'changed_by' => $uid,
'created_at' => Carbon::now()->subDays($h['days']), 'updated_at' => $now,
]);
}
// 15. Messages (6 — 3 tenant, 3 landlord)
$msgData = [
['prop' => 0, 'type' => 'tenant', 'msg' => 'Hi, the kitchen sink is leaking again. Can someone check it?', 'days' => 5],
['prop' => 0, 'type' => 'landlord', 'msg' => 'Thanks for letting us know, Maria. We\'ll send a plumber tomorrow morning.', 'days' => 4],
['prop' => 1, 'type' => 'tenant', 'msg' => 'When will the AC repair be completed? It\'s been 3 days.', 'days' => 3],
['prop' => 1, 'type' => 'landlord', 'msg' => 'The parts are on order, expected delivery tomorrow. Repair should be done by Friday.', 'days' => 2],
['prop' => 3, 'type' => 'tenant', 'msg' => 'Requesting approval for office renovation plans attached.', 'days' => 10],
['prop' => 3, 'type' => 'landlord', 'msg' => 'Renovation plans approved. Please coordinate with building admin for permits.', 'days' => 8],
];
foreach ($msgData as $m) {
DB::table('pm_messages')->insert([
'property_id' => $propertyIds[$m['prop']],
'lease_id' => $leaseIds[$m['prop']] ?? null,
'sender_id' => $m['type'] === 'landlord' ? $uid : ($ctx->randomStaff() ?? $uid),
'sender_type' => $m['type'],
'message' => $m['msg'],
'read_at' => $m['days'] > 3 ? $now : null,
'created_at' => Carbon::now()->subDays($m['days']),
'updated_at' => $now,
]);
}
}
}