5e255b7b40
Completes the 18-stage roadmap (17/18 fully functional, 18 partial). Schema: - bodyshop_jobs (type body_repair/pdr/painting/detailing/ceramic/ppf/polishing, status workflow, insurance case fields, estimate/approved amounts) - damage_points (zone, kind, severity) — the damage map Models: - BodyshopJob (HasMedia: photos_before/photos_after), auto number BS-YY-NNNN - DamagePoint with ZONES/KINDS/SEVERITIES Filament (new "Tinichigerie" nav group): - BodyshopJobResource: type/status, collapsible insurance section (conditional fields), before/after photo upload, estimate/approved amounts - DamagePointsRelationManager (zone + kind + colour-coded severity) - Table with type badge, insurance flag, damage count; nav badge = open jobs Tests (5 new): - auto number; damage points relation; insurance fields persist; detailing types supported; tenant isolation Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
2.8 KiB
PHP
103 lines
2.8 KiB
PHP
<?php
|
|
|
|
namespace App\Models\Tenant;
|
|
|
|
use App\Models\Concerns\BelongsToTenant;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Spatie\MediaLibrary\HasMedia;
|
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
|
|
|
class BodyshopJob extends Model implements HasMedia
|
|
{
|
|
use BelongsToTenant, InteractsWithMedia, SoftDeletes;
|
|
|
|
public const TYPES = [
|
|
'body_repair' => 'Tinichigerie',
|
|
'pdr' => 'PDR (fără vopsire)',
|
|
'painting' => 'Vopsitorie',
|
|
'detailing' => 'Detailing',
|
|
'ceramic' => 'Ceramică',
|
|
'ppf' => 'Folie PPF',
|
|
'polishing' => 'Polish',
|
|
];
|
|
|
|
public const STATUSES = [
|
|
'estimate' => 'Deviz',
|
|
'approved' => 'Aprobat',
|
|
'in_progress' => 'În lucru',
|
|
'done' => 'Finalizat',
|
|
'delivered' => 'Predat',
|
|
'cancelled' => 'Anulat',
|
|
];
|
|
|
|
public const INSURANCE_STATUSES = [
|
|
'submitted' => 'Depus',
|
|
'approved' => 'Aprobat',
|
|
'rejected' => 'Respins',
|
|
'paid' => 'Plătit',
|
|
];
|
|
|
|
protected $fillable = [
|
|
'company_id', 'work_order_id', 'client_id', 'vehicle_id',
|
|
'number', 'type', 'status',
|
|
'is_insurance', 'insurer', 'policy_no', 'claim_no', 'insurance_status',
|
|
'estimate_amount', 'approved_amount', 'notes',
|
|
];
|
|
|
|
protected $casts = [
|
|
'is_insurance' => 'boolean',
|
|
'estimate_amount' => 'decimal:2',
|
|
'approved_amount' => 'decimal:2',
|
|
];
|
|
|
|
public function registerMediaCollections(): void
|
|
{
|
|
$this->addMediaCollection('photos_before');
|
|
$this->addMediaCollection('photos_after');
|
|
}
|
|
|
|
public function client(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Client::class);
|
|
}
|
|
|
|
public function vehicle(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Vehicle::class);
|
|
}
|
|
|
|
public function workOrder(): BelongsTo
|
|
{
|
|
return $this->belongsTo(WorkOrder::class);
|
|
}
|
|
|
|
public function damagePoints(): HasMany
|
|
{
|
|
return $this->hasMany(DamagePoint::class);
|
|
}
|
|
|
|
public static function generateNumber(int $companyId): string
|
|
{
|
|
$year = date('y');
|
|
$count = static::withoutGlobalScopes()
|
|
->where('company_id', $companyId)
|
|
->whereYear('created_at', date('Y'))
|
|
->count();
|
|
return sprintf('BS-%s-%04d', $year, $count + 1);
|
|
}
|
|
|
|
protected static function booted(): void
|
|
{
|
|
static::creating(function (self $job) {
|
|
if (empty($job->number)) {
|
|
$job->number = static::generateNumber(
|
|
$job->company_id ?: app(\App\Tenancy\TenantManager::class)->currentId()
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|