Enth\u00e4lt gemischt: Laravel-10-Upgrade + Phase 1 (Contacts-Modul, Duplicats-Commands, Soft-Delete+Merge-Fields) + Phase 2 Code-Umstellungen (inquiry_id, $table='contacts'/'inquiries') + Offers-Modul (Migrationen, Models, offer_id in Booking, offer-Disk in filesystems.php). Phase 2 + Offers werden im folgenden Commit nach dev/backups/phase2-offers-2026-04-17/ verschoben, damit der Workspace auf Phase-1-only (= Test-System-Stand) reduziert ist und direkt auf Live deploybar wird. Tarball-Backup zus\u00e4tzlich unter: ../backups-safety/workspace-pre-phase1-rollback-2026-04-17.tar.gz Made-with: Cursor
132 lines
3.6 KiB
PHP
132 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
/**
|
|
* Angebot (Modul 6).
|
|
*
|
|
* Ein Offer ist der logische Angebots-Kopf (Angebotsnummer, Status,
|
|
* Referenzen). Die Inhalte (Texte, Positionen, PDF) liegen versionsweise
|
|
* in {@see OfferVersion}. Nach dem ersten Versand ist jede Änderung
|
|
* eine neue Version (Entscheidung 17.1 Entwicklungsplan).
|
|
*
|
|
* @property int $id
|
|
* @property string $offer_number
|
|
* @property int $contact_id
|
|
* @property int|null $inquiry_id
|
|
* @property int|null $booking_id
|
|
* @property string $status
|
|
* @property int|null $current_version_id
|
|
* @property int $created_by
|
|
* @property Carbon $created_at
|
|
* @property Carbon $updated_at
|
|
* @property Carbon|null $deleted_at
|
|
* @property-read Contact $contact
|
|
* @property-read Lead|null $inquiry
|
|
* @property-read Booking|null $booking
|
|
* @property-read OfferVersion|null $currentVersion
|
|
* @property-read Collection|OfferVersion[] $versions
|
|
* @property-read Collection|OfferAccessToken[] $accessTokens
|
|
* @property-read User $creator
|
|
*/
|
|
class Offer extends Model
|
|
{
|
|
use HasFactory, SoftDeletes;
|
|
|
|
public const STATUS_DRAFT = 'draft';
|
|
public const STATUS_SENT = 'sent';
|
|
public const STATUS_ACCEPTED = 'accepted';
|
|
public const STATUS_DECLINED = 'declined';
|
|
public const STATUS_EXPIRED = 'expired';
|
|
public const STATUS_WITHDRAWN = 'withdrawn';
|
|
|
|
public const STATUSES = [
|
|
self::STATUS_DRAFT,
|
|
self::STATUS_SENT,
|
|
self::STATUS_ACCEPTED,
|
|
self::STATUS_DECLINED,
|
|
self::STATUS_EXPIRED,
|
|
self::STATUS_WITHDRAWN,
|
|
];
|
|
|
|
protected $table = 'offers';
|
|
|
|
protected $fillable = [
|
|
'offer_number',
|
|
'contact_id',
|
|
'inquiry_id',
|
|
'booking_id',
|
|
'status',
|
|
'current_version_id',
|
|
'created_by',
|
|
];
|
|
|
|
protected $casts = [
|
|
'contact_id' => 'int',
|
|
'inquiry_id' => 'int',
|
|
'booking_id' => 'int',
|
|
'current_version_id' => 'int',
|
|
'created_by' => 'int',
|
|
];
|
|
|
|
public function contact(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Contact::class);
|
|
}
|
|
|
|
public function inquiry(): BelongsTo
|
|
{
|
|
// Nach Modul 3 Phase 2: `Lead`-Model bildet die `inquiries`-Tabelle ab
|
|
return $this->belongsTo(Lead::class, 'inquiry_id');
|
|
}
|
|
|
|
public function booking(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Booking::class);
|
|
}
|
|
|
|
public function currentVersion(): BelongsTo
|
|
{
|
|
return $this->belongsTo(OfferVersion::class, 'current_version_id');
|
|
}
|
|
|
|
public function versions(): HasMany
|
|
{
|
|
return $this->hasMany(OfferVersion::class)->orderBy('version_no');
|
|
}
|
|
|
|
public function accessTokens(): HasMany
|
|
{
|
|
return $this->hasMany(OfferAccessToken::class);
|
|
}
|
|
|
|
public function creator(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'created_by');
|
|
}
|
|
|
|
public function scopeStatus(Builder $q, string $status): Builder
|
|
{
|
|
return $q->where('status', $status);
|
|
}
|
|
|
|
public function scopeOpen(Builder $q): Builder
|
|
{
|
|
return $q->whereIn('status', [self::STATUS_DRAFT, self::STATUS_SENT]);
|
|
}
|
|
|
|
public function isEditable(): bool
|
|
{
|
|
return in_array($this->status, [self::STATUS_DRAFT, self::STATUS_SENT], true);
|
|
}
|
|
}
|