Allow invoices to target either a client or a client group by making `client_id` nullable and validating that exactly one recipient is set. Load group relations in invoice data so the frontend can display group details in invoice views and type definitions. Make quote acceptance reuse an existing invoice instead of creating a duplicate, and surface backend status update errors in the quote UI.
134 lines
4.7 KiB
PHP
134 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Repositories;
|
|
|
|
use App\Models\Quote;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
use App\Repositories\ClientActivityTimelineRepositoryInterface;
|
|
|
|
class QuoteRepository extends BaseRepository implements QuoteRepositoryInterface
|
|
{
|
|
public function __construct(
|
|
Quote $model,
|
|
protected QuoteLineRepositoryInterface $quoteLineRepository,
|
|
protected InvoiceRepositoryInterface $invoiceRepository,
|
|
protected readonly ClientActivityTimelineRepositoryInterface $timelineRepository
|
|
) {
|
|
parent::__construct($model);
|
|
}
|
|
|
|
|
|
public function all(array $columns = ['*']): \Illuminate\Support\Collection
|
|
{
|
|
return $this->model->with(['client', 'group', 'lines.product'])->get($columns);
|
|
}
|
|
|
|
public function create(array $data): Quote
|
|
{
|
|
return DB::transaction(function () use ($data) {
|
|
try {
|
|
// Create the quote
|
|
$quote = parent::create($data);
|
|
|
|
// Create the quote lines
|
|
if (isset($data['lines']) && is_array($data['lines'])) {
|
|
foreach ($data['lines'] as $lineData) {
|
|
$lineData['quote_id'] = $quote->id;
|
|
$this->quoteLineRepository->create($lineData);
|
|
}
|
|
}
|
|
|
|
// Record initial status history
|
|
$this->recordHistory($quote->id, null, $quote->status, 'Quote created');
|
|
|
|
try {
|
|
$this->timelineRepository->logActivity([
|
|
'client_id' => $quote->client_id,
|
|
'actor_type' => 'user',
|
|
'actor_user_id' => auth()->id(),
|
|
'event_type' => 'quote_created',
|
|
'entity_type' => 'quote',
|
|
'entity_id' => $quote->id,
|
|
'title' => 'Nouveau devis créé',
|
|
'description' => "Le devis #{$quote->id} a été créé.",
|
|
'created_at' => now(),
|
|
]);
|
|
} catch (\Exception $e) {
|
|
Log::error("Failed to log quote creation activity: " . $e->getMessage());
|
|
}
|
|
|
|
return $quote;
|
|
} catch (\Exception $e) {
|
|
Log::error('Error creating quote with lines: ' . $e->getMessage(), [
|
|
'exception' => $e,
|
|
'data' => $data,
|
|
]);
|
|
throw $e;
|
|
}
|
|
});
|
|
}
|
|
|
|
public function update(int|string $id, array $attributes): bool
|
|
{
|
|
return DB::transaction(function () use ($id, $attributes) {
|
|
try {
|
|
$quote = $this->find($id);
|
|
if (!$quote) {
|
|
return false;
|
|
}
|
|
|
|
$oldStatus = $quote->status;
|
|
|
|
// Update the quote
|
|
$updated = parent::update($id, $attributes);
|
|
|
|
if ($updated) {
|
|
$newStatus = $attributes['status'] ?? $oldStatus;
|
|
|
|
// If status changed, record history
|
|
if ($oldStatus !== $newStatus) {
|
|
$this->recordHistory((int) $id, $oldStatus, $newStatus, 'Quote status updated');
|
|
|
|
// Auto-create invoice when status changes to 'accepte'
|
|
if ($newStatus === 'accepte' && $oldStatus !== 'accepte') {
|
|
$this->invoiceRepository->createFromQuote($id);
|
|
Log::info('Invoice auto-created from quote', ['quote_id' => $id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $updated;
|
|
} catch (\Exception $e) {
|
|
Log::error('Error updating quote: ' . $e->getMessage(), [
|
|
'id' => $id,
|
|
'attributes' => $attributes,
|
|
'exception' => $e,
|
|
]);
|
|
throw $e;
|
|
}
|
|
});
|
|
}
|
|
|
|
public function find(int|string $id, array $columns = ['*']): ?Quote
|
|
{
|
|
return $this->model->with(['client', 'group', 'lines.product', 'history.user'])->find($id, $columns);
|
|
}
|
|
|
|
private function recordHistory(int $quoteId, ?string $oldStatus, string $newStatus, ?string $comment = null): void
|
|
{
|
|
\App\Models\DocumentStatusHistory::create([
|
|
'document_type' => 'quote',
|
|
'document_id' => $quoteId,
|
|
'old_status' => $oldStatus,
|
|
'new_status' => $newStatus,
|
|
'changed_by' => auth()->id(), // Assuming authenticated user
|
|
'comment' => $comment,
|
|
'changed_at' => now(),
|
|
]);
|
|
}
|
|
}
|