ultimatepos/database/seeders/EnterpriseModulesSampleSeeder.php

362 lines
13 KiB
PHP

<?php
namespace Database\Seeders;
use App\Business;
use App\System;
use Carbon\Carbon;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class EnterpriseModulesSampleSeeder extends Seeder
{
public function run(?int $businessId = null): void
{
$businessId = $businessId ?? (int) Business::query()->value('id');
if (! $businessId) {
$this->command?->warn('No business found — skipping enterprise sample seed.');
return;
}
$userId = (int) DB::table('users')->where('business_id', $businessId)->value('id');
$this->seedQms($businessId, $userId);
$this->seedShopFloor($businessId, $userId);
$this->seedSupplyChain($businessId);
$this->seedAdvancedPlanning($businessId);
$this->seedIntegrationHub($businessId);
$this->seedCpq($businessId, $userId);
$this->seedWms($businessId);
$this->seedIot($businessId);
$this->registerModuleVersions();
$this->grantEnterprisePermissions($businessId);
$this->command?->info("Enterprise sample data seeded for business #{$businessId}.");
}
protected function seedQms(int $businessId, int $userId): void
{
if (! Schema::hasTable('qms_ncr_reports')) {
return;
}
if (DB::table('qms_ncr_reports')->where('business_id', $businessId)->exists()) {
return;
}
$ncrId = DB::table('qms_ncr_reports')->insertGetId([
'business_id' => $businessId,
'ref_no' => 'NCR-00001',
'title' => 'Dimensional deviation on machined shaft',
'description' => 'Sample NCR for executive cockpit demo.',
'severity' => 'major',
'source' => 'production',
'status' => 'investigating',
'reported_by' => $userId ?: null,
'detected_at' => Carbon::today()->subDays(3)->toDateString(),
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('qms_capa_actions')->insert([
'business_id' => $businessId,
'ncr_id' => $ncrId,
'ref_no' => 'CAPA-00001',
'type' => 'corrective',
'title' => 'Recalibrate CNC offset and retrain operator',
'status' => 'in_progress',
'owner_id' => $userId ?: null,
'due_date' => Carbon::today()->addDays(14)->toDateString(),
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('qms_fmea_records')->insert([
'business_id' => $businessId,
'ref_no' => 'FMEA-00001',
'process_step' => 'Final assembly torque',
'failure_mode' => 'Under-torque',
'severity' => 8,
'occurrence' => 4,
'detection' => 3,
'rpn' => 96,
'status' => 'active',
'created_at' => now(),
'updated_at' => now(),
]);
for ($i = 0; $i < 5; $i++) {
DB::table('qms_spc_measurements')->insert([
'business_id' => $businessId,
'characteristic_name' => 'shaft_diameter_mm',
'measured_value' => 49.98 + ($i * 0.01),
'usl' => 50.05,
'lsl' => 49.95,
'target' => 50.00,
'measured_at' => now()->subDays($i),
'in_control' => true,
'recorded_by' => $userId ?: null,
'created_at' => now(),
'updated_at' => now(),
]);
}
DB::table('qms_calibration_assets')->insert([
'business_id' => $businessId,
'asset_tag' => 'CAL-001',
'name' => 'Digital micrometer 0-150mm',
'status' => 'active',
'calibration_interval_days' => 180,
'next_due_at' => Carbon::today()->addMonths(3)->toDateString(),
'created_at' => now(),
'updated_at' => now(),
]);
}
protected function seedShopFloor(int $businessId, int $userId): void
{
if (! Schema::hasTable('sf_work_centers') || DB::table('sf_work_centers')->where('business_id', $businessId)->exists()) {
return;
}
$wcId = DB::table('sf_work_centers')->insertGetId([
'business_id' => $businessId,
'name' => 'Assembly Line A',
'code' => 'WC-A01',
'capacity_per_hour' => 12,
'status' => 'active',
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('sf_production_events')->insert([
'business_id' => $businessId,
'work_center_id' => $wcId,
'event_type' => 'complete',
'quantity' => 48,
'good_quantity' => 46,
'scrap_quantity' => 2,
'recorded_at' => now()->subHours(2),
'recorded_by' => $userId ?: null,
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('sf_oee_snapshots')->insert([
'business_id' => $businessId,
'work_center_id' => $wcId,
'snapshot_date' => Carbon::today()->toDateString(),
'availability_pct' => 92.5,
'performance_pct' => 88.0,
'quality_pct' => 95.8,
'oee_pct' => 78.1,
'planned_minutes' => 480,
'run_minutes' => 444,
'downtime_minutes' => 36,
'ideal_output' => 96,
'actual_output' => 48,
'good_output' => 46,
'created_at' => now(),
'updated_at' => now(),
]);
}
protected function seedSupplyChain(int $businessId): void
{
if (! Schema::hasTable('sc_supplier_scorecards')) {
return;
}
if (DB::table('sc_supplier_scorecards')->where('business_id', $businessId)->exists()) {
return;
}
$supplierId = DB::table('contacts')
->where('business_id', $businessId)
->whereIn('type', ['supplier', 'both'])
->value('id');
DB::table('sc_supplier_scorecards')->insert([
'business_id' => $businessId,
'contact_id' => $supplierId,
'period_start' => Carbon::today()->startOfMonth()->toDateString(),
'period_end' => Carbon::today()->endOfMonth()->toDateString(),
'otif_score' => 87.5,
'quality_score' => 92.0,
'price_score' => 78.0,
'responsiveness_score' => 85.0,
'overall_score' => 85.6,
'risk_level' => 'low',
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('sc_demand_forecasts')->insert([
'business_id' => $businessId,
'forecast_month' => Carbon::today()->addMonth()->format('Y-m-01'),
'forecast_qty' => 120,
'method' => 'moving_average',
'confidence' => 0.82,
'source' => 'sales_history',
'created_at' => now(),
'updated_at' => now(),
]);
}
protected function seedAdvancedPlanning(int $businessId): void
{
if (! Schema::hasTable('ap_capacity_resources') || DB::table('ap_capacity_resources')->where('business_id', $businessId)->exists()) {
return;
}
DB::table('ap_capacity_resources')->insert([
'business_id' => $businessId,
'name' => 'Main Assembly Line',
'resource_type' => 'line',
'daily_capacity_hours' => 16,
'efficiency_pct' => 85,
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('ap_sop_cycles')->insert([
'business_id' => $businessId,
'cycle_month' => Carbon::today()->format('Y-m-01'),
'status' => 'draft',
'demand_plan' => json_encode(['units' => 500, 'revenue' => 2500000]),
'supply_plan' => json_encode(['capacity_hours' => 320, 'open_wo' => 12]),
'financial_plan' => json_encode(['cogs' => 1800000, 'margin_pct' => 28]),
'gaps' => json_encode(['capacity_gap_hours' => 24]),
'created_at' => now(),
'updated_at' => now(),
]);
}
protected function seedIntegrationHub(int $businessId): void
{
if (! Schema::hasTable('ih_webhook_endpoints') || DB::table('ih_webhook_endpoints')->where('business_id', $businessId)->exists()) {
return;
}
DB::table('ih_webhook_endpoints')->insert([
'business_id' => $businessId,
'name' => 'Power BI / External BI',
'url' => 'https://example.com/webhooks/executive-cockpit',
'secret' => bin2hex(random_bytes(16)),
'events' => json_encode(['executive_cockpit.exported', 'export.completed']),
'is_active' => false,
'created_at' => now(),
'updated_at' => now(),
]);
}
protected function seedCpq(int $businessId, int $userId): void
{
if (! Schema::hasTable('ie_cpq_configurations') || DB::table('ie_cpq_configurations')->where('business_id', $businessId)->exists()) {
return;
}
DB::table('ie_cpq_configurations')->insert([
'business_id' => $businessId,
'name' => 'Custom press line configuration',
'ref_no' => 'CPQ-00001',
'status' => 'quoted',
'selected_options' => json_encode([]),
'bom_snapshot' => json_encode(['lines' => []]),
'quoted_price' => 1250000,
'created_by' => $userId ?: null,
'created_at' => now(),
'updated_at' => now(),
]);
}
protected function seedWms(int $businessId): void
{
if (! Schema::hasTable('im_wms_waves') || DB::table('im_wms_waves')->where('business_id', $businessId)->exists()) {
return;
}
$locationId = DB::table('business_locations')->where('business_id', $businessId)->value('id') ?? 1;
DB::table('im_wms_waves')->insert([
'business_id' => $businessId,
'location_id' => $locationId,
'wave_no' => 'WAVE-00001',
'status' => 'planned',
'pick_strategy' => 'wave',
'priority' => 100,
'created_at' => now(),
'updated_at' => now(),
]);
}
protected function seedIot(int $businessId): void
{
if (! Schema::hasTable('mt_iot_devices') || DB::table('mt_iot_devices')->where('business_id', $businessId)->exists()) {
return;
}
DB::table('mt_iot_devices')->insert([
'business_id' => $businessId,
'device_uid' => 'DEMO-VIB-001',
'name' => 'Vibration sensor — Press #1',
'protocol' => 'mqtt',
'topic' => 'plant/press1/vibration',
'status' => 'active',
'last_seen_at' => now()->subMinutes(5),
'created_at' => now(),
'updated_at' => now(),
]);
}
protected function registerModuleVersions(): void
{
$modules = [
'qualitymanagement' => '1.0.0',
'shopfloor' => '1.0.0',
'integrationhub' => '1.0.0',
'supplychain' => '1.0.0',
'advancedplanning' => '1.0.0',
];
foreach ($modules as $key => $version) {
if (empty(System::getProperty($key.'_version'))) {
System::addProperty($key.'_version', $version);
}
}
}
protected function grantEnterprisePermissions(int $businessId): void
{
$permissions = [
'qms.access', 'qms.ncr.view', 'qms.ncr.create', 'qms.ncr.update',
'qms.capa.view', 'qms.capa.create', 'qms.capa.update',
'qms.fmea.view', 'qms.fmea.create', 'qms.spc.view', 'qms.spc.create',
'qms.calibration.view', 'qms.calibration.create', 'qms.calibration.update',
'qms.audit.view', 'qms.audit.create', 'qms.document.view', 'qms.document.create',
'shopfloor.access', 'shopfloor.view', 'shopfloor.production.create',
'shopfloor.downtime.create', 'shopfloor.oee.view', 'shopfloor.instructions.view',
'integrationhub.access', 'integrationhub.manage_webhooks', 'integrationhub.manage_tokens', 'integrationhub.export',
'supplychain.access', 'supplychain.view', 'supplychain.scorecard.view', 'supplychain.scorecard.create',
'supplychain.forecast.view', 'supplychain.forecast.create',
'supplychain.rfq.view', 'supplychain.rfq.create', 'supplychain.rfq.update',
'ap.access', 'ap.view', 'ap.resources.view', 'ap.resources.manage',
'ap.schedule.view', 'ap.schedule.run', 'ap.sop.view', 'ap.sop.manage',
'ie.cpq.view', 'ie.cpq.create',
];
foreach ($permissions as $name) {
Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']);
}
$role = Role::where('name', 'Admin#'.$businessId)->first();
if ($role) {
$role->givePermissionTo($permissions);
}
}
}