09fd0bada2
4 Mailables auto-trigger pe model events: - WorkOrderReadyMail: la WO.status → 'ready', către client.email • Atașat PDF fișa lucru (via WorkOrderPdfService) • Total/achitat/rest, recomandări (warning box) - PaymentReceivedMail: la Payment::created, confirmare cu sumă/metodă/ref - AppointmentConfirmedMail: la Appointment::created status='scheduled' - ServiceReminderMail: dispatch manual (vehicle, type=itp/oil/general, note) Layout email branded (resources/views/emails/layout.blade.php): - Header cu logo tenant + theme_color border-bottom - Footer cu telefon/email/disclaimer - Stiluri inline (compatibil tot mail client) Settings page extins cu 4 toggle: - 'Mașina e gata de ridicat' - 'Confirmare plată primită' - 'Programare confirmată' - 'Reminder ITP / revizie' Salvate în companies.settings.notify (JSON), default true. NotificationDispatcher service centralizat: - Verifică isEnabled() pe settings.notify[$key] - Skip dacă client n-are email - Try/catch + Log::warning pe eșec (nu crapă request-ul) Mailables folosesc UsesTenantBranding trait pentru context unitar. Test prin Mailpit: https://mailpit.service.mir.md (capturează toate).
85 lines
2.2 KiB
PHP
85 lines
2.2 KiB
PHP
<?php
|
|
|
|
namespace App\Models\Tenant;
|
|
|
|
use App\Models\Concerns\BelongsToTenant;
|
|
use App\Models\Concerns\Auditable;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
class Payment extends Model
|
|
{
|
|
use Auditable, BelongsToTenant, SoftDeletes;
|
|
|
|
public const METHODS = [
|
|
'cash' => 'Numerar',
|
|
'card' => 'Card',
|
|
'transfer' => 'Virament',
|
|
'mobile' => 'Mobile pay',
|
|
];
|
|
|
|
protected $fillable = [
|
|
'company_id', 'client_id', 'work_order_id', 'user_id',
|
|
'paid_at', 'amount', 'method', 'reference', 'notes',
|
|
];
|
|
|
|
protected $casts = [
|
|
'paid_at' => 'date',
|
|
'amount' => 'decimal:2',
|
|
];
|
|
|
|
public function client(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Client::class);
|
|
}
|
|
|
|
public function workOrder(): BelongsTo
|
|
{
|
|
return $this->belongsTo(WorkOrder::class);
|
|
}
|
|
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
/**
|
|
* After save/delete, recompute the work order's pay_status.
|
|
*/
|
|
protected static function booted(): void
|
|
{
|
|
$sync = function (self $payment) {
|
|
if (! $payment->work_order_id) {
|
|
return;
|
|
}
|
|
$wo = WorkOrder::withoutGlobalScopes()->find($payment->work_order_id);
|
|
if (! $wo) {
|
|
return;
|
|
}
|
|
$paid = (float) static::withoutGlobalScopes()
|
|
->where('work_order_id', $wo->id)
|
|
->whereNull('deleted_at')
|
|
->sum('amount');
|
|
$total = (float) $wo->total;
|
|
|
|
if ($paid <= 0) {
|
|
$wo->pay_status = 'unpaid';
|
|
} elseif ($paid + 0.01 < $total) {
|
|
$wo->pay_status = 'partial';
|
|
} else {
|
|
$wo->pay_status = 'paid';
|
|
}
|
|
$wo->save();
|
|
};
|
|
|
|
static::saved($sync);
|
|
static::deleted($sync);
|
|
|
|
// Auto-send confirmation email on new payment.
|
|
static::created(function (self $payment) {
|
|
app(\App\Services\NotificationDispatcher::class)->paymentReceived($payment);
|
|
});
|
|
}
|
|
}
|