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
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable([
'provider_profile_id', 'external_profile_import_id', 'uploaded_by_id',
'platform', 'source_type', 'parse_status', 'review_status', 'disk', 'path',
'original_name', 'mime_type', 'size', 'provider_notes', 'parsed_payload',
'parse_warnings', 'uploaded_at',
])]
class ExternalImportSource extends Model
{
public const PLATFORMS = [
'Field Nation' => 'Field Nation',
'WorkMarket' => 'WorkMarket',
'Other' => 'Other',
];
public const SOURCE_TYPES = [
'profile_pdf' => 'Profile PDF',
'screenshot' => 'Screenshot',
'csv_export' => 'CSV export',
'json_export' => 'JSON export',
'html_export' => 'HTML copy/export',
'plain_text' => 'Plain text paste/export',
'email_export' => 'Email or message export',
'other' => 'Other source',
];
public const PARSE_STATUSES = [
'pending' => 'Pending parser',
'parsed' => 'Parsed',
'failed' => 'Parse failed',
'not_parseable' => 'Stored as proof only',
];
public const REVIEW_STATUSES = [
'needs_review' => 'Needs provider review',
'provider_reviewed' => 'Provider reviewed',
'admin_reviewed' => 'Admin reviewed',
'approved_for_summary' => 'Approved for public summary',
'rejected_for_public' => 'Rejected for public display',
];
protected function casts(): array
{
return [
'parsed_payload' => 'array',
'parse_warnings' => 'array',
'uploaded_at' => 'datetime',
];
}
public function providerProfile(): BelongsTo
{
return $this->belongsTo(ProviderProfile::class);
}
public function externalProfileImport(): BelongsTo
{
return $this->belongsTo(ExternalProfileImport::class);
}
public function uploadedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'uploaded_by_id');
}
public function sourceTypeLabel(): string
{
return self::SOURCE_TYPES[$this->source_type] ?? $this->source_type;
}
public function parseStatusLabel(): string
{
return self::PARSE_STATUSES[$this->parse_status] ?? $this->parse_status;
}
public function reviewStatusLabel(): string
{
return self::REVIEW_STATUSES[$this->review_status] ?? $this->review_status;
}
}
+12
View File
@@ -4,12 +4,14 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Model;
#[Fillable([
'provider_profile_id', 'platform', 'external_id', 'profile_url', 'status',
'visibility', 'verification_status', 'verified_by_id', 'verified_at',
'background_check_status', 'background_check_date', 'drug_test_status', 'drug_test_date',
'rating', 'review_count', 'completed_jobs', 'work_categories', 'endorsements',
'operational_metrics', 'metrics', 'review_snapshots', 'selected_reviews',
'notes', 'imported_at',
@@ -59,6 +61,16 @@ class ExternalProfileImport extends Model
return $this->morphMany(Attachment::class, 'attachable');
}
public function sources(): HasMany
{
return $this->hasMany(ExternalImportSource::class);
}
public function workHistoryEntries(): HasMany
{
return $this->hasMany(ExternalWorkHistoryEntry::class);
}
public function publiclyVisible(): bool
{
return $this->visibility !== 'private';
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable([
'provider_profile_id', 'external_profile_import_id', 'external_import_source_id',
'platform', 'external_work_order_id', 'service_title', 'performed_on',
'customer_name', 'work_type', 'location', 'rating', 'status', 'source_url',
'visibility', 'raw_payload',
])]
class ExternalWorkHistoryEntry extends Model
{
protected function casts(): array
{
return [
'performed_on' => 'date',
'rating' => 'decimal:2',
'raw_payload' => 'array',
];
}
public function providerProfile(): BelongsTo
{
return $this->belongsTo(ProviderProfile::class);
}
public function externalProfileImport(): BelongsTo
{
return $this->belongsTo(ExternalProfileImport::class);
}
public function externalImportSource(): BelongsTo
{
return $this->belongsTo(ExternalImportSource::class);
}
}
+10
View File
@@ -39,6 +39,16 @@ class ProviderProfile extends Model
return $this->hasMany(ExternalProfileImport::class);
}
public function externalImportSources(): HasMany
{
return $this->hasMany(ExternalImportSource::class);
}
public function externalWorkHistoryEntries(): HasMany
{
return $this->hasMany(ExternalWorkHistoryEntry::class);
}
public function attachments(): MorphMany
{
return $this->morphMany(Attachment::class, 'attachable');