Add external import sources, work history, screening fields and Phase 1 docs

- New ExternalImportSource and ExternalWorkHistoryEntry models, controller and migrations
- Screening fields on external profile imports
- Admin and provider profile views, routes and workflow tests updated
- Phase 1 validation, route inventory, gap report template and roadmap docs
- Add the Google Sheets work-order parser tool (tools/google-sheets-wo-parser)
This commit is contained in:
Codex
2026-10-02 15:50:18 -05:00
parent 0670f48878
commit 62220186af
37 changed files with 3284 additions and 4 deletions
@@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('external_import_sources', function (Blueprint $table) {
$table->id();
$table->foreignId('provider_profile_id')->constrained()->cascadeOnDelete();
$table->foreignId('external_profile_import_id')->nullable()->constrained()->nullOnDelete();
$table->foreignId('uploaded_by_id')->constrained('users')->cascadeOnDelete();
$table->string('platform');
$table->string('source_type');
$table->string('parse_status')->default('pending');
$table->string('review_status')->default('needs_review');
$table->string('disk')->default('local');
$table->string('path');
$table->string('original_name');
$table->string('mime_type')->nullable();
$table->unsignedBigInteger('size')->default(0);
$table->text('provider_notes')->nullable();
$table->json('parsed_payload')->nullable();
$table->json('parse_warnings')->nullable();
$table->timestamp('uploaded_at')->nullable();
$table->timestamps();
$table->index(['provider_profile_id', 'platform']);
$table->index(['parse_status', 'review_status']);
});
}
public function down(): void
{
Schema::dropIfExists('external_import_sources');
}
};
@@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('external_work_history_entries', function (Blueprint $table) {
$table->id();
$table->foreignId('provider_profile_id')->constrained()->cascadeOnDelete();
$table->foreignId('external_profile_import_id')->constrained()->cascadeOnDelete();
$table->foreignId('external_import_source_id')->nullable()->constrained()->nullOnDelete();
$table->string('platform');
$table->string('external_work_order_id')->nullable();
$table->string('service_title');
$table->date('performed_on')->nullable();
$table->string('customer_name')->nullable();
$table->string('work_type')->nullable();
$table->string('location')->nullable();
$table->decimal('rating', 3, 2)->nullable();
$table->string('status')->nullable();
$table->string('source_url')->nullable();
$table->string('visibility')->default('private');
$table->json('raw_payload')->nullable();
$table->timestamps();
$table->index(['provider_profile_id', 'platform', 'performed_on']);
$table->index(['external_profile_import_id', 'performed_on']);
$table->unique(['external_profile_import_id', 'platform', 'external_work_order_id'], 'external_work_history_unique_order');
});
}
public function down(): void
{
Schema::dropIfExists('external_work_history_entries');
}
};
@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('external_profile_imports', function (Blueprint $table) {
$table->string('background_check_status')->nullable()->after('verification_status');
$table->string('background_check_date')->nullable()->after('background_check_status');
$table->string('drug_test_status')->nullable()->after('background_check_date');
$table->string('drug_test_date')->nullable()->after('drug_test_status');
});
}
public function down(): void
{
Schema::table('external_profile_imports', function (Blueprint $table) {
$table->dropColumn([
'background_check_status',
'background_check_date',
'drug_test_status',
'drug_test_date',
]);
});
}
};
+103
View File
@@ -225,6 +225,109 @@ class DatabaseSeeder extends Seeder
'evidence_notes' => 'Screenshots, chat, and deliverable notes would be attached here later.',
]
);
$this->seedValidationScenarios($buyer, $provider, $taxonomy);
}
private function seedValidationScenarios(User $buyer, User $provider, array $taxonomy): void
{
$remoteJob = JobPost::updateOrCreate(
['title' => 'Validation remote firewall rule review'],
[
'buyer_id' => $buyer->id,
'status' => 'open',
'service_category' => 'Networking',
'work_category_id' => $taxonomy['categories']['server-networking']->id,
'work_specialty_id' => $taxonomy['specialties']['networking']->id,
'required_technician_level' => 4,
'work_mode' => 'remote',
'remote_eligible' => true,
'pay_type' => 'hourly',
'posted_terms_summary' => 'Provider hourly terms, one-hour minimum',
'location' => 'Remote',
'starts_at' => now()->addDays(3)->setTime(10, 0),
'time_window' => 'Scheduled remote window',
'schedule_type' => 'scheduled',
'scope' => 'Review a planned firewall rule change and confirm the rollback path before the buyer applies it.',
'primary_objective' => 'Validate a firewall rule change plan before production use.',
'included_work' => 'Review requested ports, source/destination notes, rollback steps, and test plan.',
'excluded_work' => 'Provider does not make production changes or retain credentials.',
'maximum_onsite_expectations' => 'Remote advisory only.',
'expected_duration' => 'One hour',
'required_skills' => 'Firewall policy review, networking, change planning',
'required_tools' => 'Video bridge and shared screen',
'deliverables' => "Written risk notes\nRollback checklist\nGo/no-go recommendation",
'closeout_conditions' => 'Buyer confirms the written recommendation is received.',
'support_channel' => 'Video bridge',
'support_expected_response_time' => 'Immediate during scheduled window',
'support_availability_window' => 'Full scheduled window',
'primary_contact_name' => 'Network Change Lead',
'primary_contact_email' => 'buyer@example.com',
'backup_contact_name' => 'Backup Dispatch',
'backup_contact_email' => 'admin@example.com',
'contact_certified' => true,
'contact_certified_by_id' => $buyer->id,
'contact_certified_at' => now(),
'scope_clarity_status' => 'clear',
'risk_flags' => [],
'payment_terms' => 'Direct buyer/provider payment terms.',
'vendor_onboarding' => 'No credential storage in platform.',
'visibility' => 'public',
]
);
$remoteJob->comments()->firstOrCreate(
['user_id' => $provider->id, 'body' => 'Validation note: this job should appear when remote-only and high-level networking filters are used.']
);
$riskyJob = JobPost::updateOrCreate(
['title' => 'Validation risky smart-hands dispatch'],
[
'buyer_id' => $buyer->id,
'status' => 'open',
'service_category' => 'Smart hands',
'work_category_id' => $taxonomy['categories']['server-networking']->id,
'work_specialty_id' => $taxonomy['specialties']['networking']->id,
'required_technician_level' => 1,
'work_mode' => 'onsite',
'remote_eligible' => false,
'pay_type' => 'not_listed',
'posted_terms_summary' => 'Terms not listed for validation',
'location' => 'Dallas, TX',
'starts_at' => now()->addHours(12),
'time_window' => 'ASAP',
'schedule_type' => 'urgent',
'scope' => 'Show up onsite and handle whatever network issue the store has.',
'primary_objective' => 'Troubleshoot the network outage and fix any related issue.',
'included_work' => null,
'excluded_work' => null,
'maximum_onsite_expectations' => null,
'expected_duration' => null,
'required_skills' => 'Troubleshooting, cabling, firewall, wireless',
'required_tools' => null,
'required_certifications' => 'Network certification preferred',
'deliverables' => null,
'closeout_conditions' => null,
'supplemental_instructions' => str_repeat('Follow all onsite instructions. ', 90),
'support_channel' => null,
'support_expected_response_time' => null,
'support_availability_window' => null,
'primary_contact_name' => 'Store Manager',
'primary_contact_email' => 'buyer@example.com',
'backup_contact_name' => null,
'backup_contact_email' => null,
'contact_certified' => false,
'contact_certified_by_id' => null,
'contact_certified_at' => null,
'payment_terms' => 'Direct buyer/provider payment terms.',
'vendor_onboarding' => 'Buyer to advise after assignment.',
'visibility' => 'public',
]
);
$riskyJob->risk_flags = $riskyJob->computeRiskFlags();
$riskyJob->scope_clarity_status = $riskyJob->computedScopeClarityStatus();
$riskyJob->save();
}
private function seedTaxonomy(): array