362 lines
13 KiB
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);
|
|
}
|
|
}
|
|
}
|