- Increased icon sizes in service items, service orders, users, and technician management for better visibility. - Added custom loading indicators with appropriate icons in search fields for vehicles, work orders, and technicians. - Introduced invoice management routes for better organization and access control. - Created a new test for the estimate PDF functionality to ensure proper rendering and data integrity.
341 lines
11 KiB
PHP
341 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Estimates;
|
|
|
|
use App\Models\Estimate;
|
|
use App\Notifications\EstimateNotification;
|
|
use Barryvdh\DomPDF\Facade\Pdf;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
|
|
class Show extends Component
|
|
{
|
|
public Estimate $estimate;
|
|
|
|
public $showItemDetails = false;
|
|
|
|
public function mount(Estimate $estimate)
|
|
{
|
|
$this->estimate = $estimate->load([
|
|
'jobCard.customer',
|
|
'jobCard.vehicle',
|
|
'jobCard.branch',
|
|
'customer', // For standalone estimates
|
|
'vehicle', // For standalone estimates
|
|
'diagnosis',
|
|
'preparedBy',
|
|
'lineItems',
|
|
'workOrders',
|
|
]);
|
|
}
|
|
|
|
public function approveEstimate()
|
|
{
|
|
if (! auth()->user()->can('approve', $this->estimate)) {
|
|
session()->flash('error', 'You do not have permission to approve this estimate.');
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$this->estimate->update([
|
|
'customer_approval_status' => 'approved',
|
|
'customer_approved_at' => now(),
|
|
'customer_approval_method' => 'staff_portal',
|
|
'status' => 'approved',
|
|
]);
|
|
|
|
// Update job card status to approved (if job card exists)
|
|
if ($this->estimate->jobCard) {
|
|
$this->estimate->jobCard->update([
|
|
'status' => 'approved',
|
|
]);
|
|
}
|
|
|
|
// Notify relevant parties
|
|
$customer = $this->estimate->customer ?? $this->estimate->jobCard?->customer;
|
|
if ($customer) {
|
|
$customer->notify(
|
|
new EstimateNotification($this->estimate, 'approved')
|
|
);
|
|
}
|
|
|
|
Log::info('Estimate approved', [
|
|
'estimate_id' => $this->estimate->id,
|
|
'approved_by' => auth()->id(),
|
|
]);
|
|
|
|
session()->flash('success', 'Estimate approved successfully.');
|
|
|
|
// Refresh estimate data
|
|
$this->estimate->refresh();
|
|
$this->dispatch('$refresh');
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('Failed to approve estimate', [
|
|
'estimate_id' => $this->estimate->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
session()->flash('error', 'Failed to approve estimate. Please try again.');
|
|
}
|
|
}
|
|
|
|
public function rejectEstimate()
|
|
{
|
|
if (! auth()->user()->can('reject', $this->estimate)) {
|
|
session()->flash('error', 'You do not have permission to reject this estimate.');
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$this->estimate->update([
|
|
'customer_approval_status' => 'rejected',
|
|
'customer_approved_at' => now(),
|
|
'customer_approval_method' => 'staff_portal',
|
|
'status' => 'rejected',
|
|
]);
|
|
|
|
// Notify relevant parties
|
|
$customer = $this->estimate->customer ?? $this->estimate->jobCard?->customer;
|
|
if ($customer) {
|
|
$customer->notify(
|
|
new EstimateNotification($this->estimate, 'rejected')
|
|
);
|
|
}
|
|
|
|
Log::info('Estimate rejected', [
|
|
'estimate_id' => $this->estimate->id,
|
|
'rejected_by' => auth()->id(),
|
|
]);
|
|
|
|
session()->flash('success', 'Estimate rejected.');
|
|
|
|
// Refresh estimate data
|
|
$this->estimate->refresh();
|
|
$this->dispatch('$refresh');
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('Failed to reject estimate', [
|
|
'estimate_id' => $this->estimate->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
session()->flash('error', 'Failed to reject estimate. Please try again.');
|
|
}
|
|
}
|
|
|
|
public function sendToCustomer()
|
|
{
|
|
try {
|
|
$this->estimate->update([
|
|
'status' => 'sent',
|
|
'sent_to_customer_at' => now(),
|
|
'sent_by_id' => auth()->id(),
|
|
]);
|
|
|
|
// Send notification to customer
|
|
$customer = $this->estimate->customer ?? $this->estimate->jobCard?->customer;
|
|
if ($customer) {
|
|
$customer->notify(
|
|
new EstimateNotification($this->estimate, 'sent')
|
|
);
|
|
}
|
|
|
|
// Update job card status (if job card exists)
|
|
if ($this->estimate->jobCard) {
|
|
$this->estimate->jobCard->update([
|
|
'status' => 'estimate_sent',
|
|
]);
|
|
}
|
|
|
|
Log::info('Estimate sent to customer', [
|
|
'estimate_id' => $this->estimate->id,
|
|
'customer_id' => $this->estimate->customer_id ?? $this->estimate->jobCard?->customer?->id,
|
|
'sent_by' => auth()->id(),
|
|
]);
|
|
|
|
session()->flash('success', 'Estimate sent to customer successfully.');
|
|
|
|
// Refresh estimate data
|
|
$this->estimate->refresh();
|
|
$this->dispatch('$refresh');
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('Failed to send estimate to customer', [
|
|
'estimate_id' => $this->estimate->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
session()->flash('error', 'Failed to send estimate to customer. Please try again.');
|
|
}
|
|
}
|
|
|
|
public function duplicateEstimate()
|
|
{
|
|
try {
|
|
// Create a new estimate based on current one
|
|
$newEstimate = $this->estimate->replicate();
|
|
$newEstimate->estimate_number = 'EST-'.str_pad(Estimate::max('id') + 1, 6, '0', STR_PAD_LEFT);
|
|
$newEstimate->status = 'draft';
|
|
$newEstimate->customer_approval_status = 'pending';
|
|
$newEstimate->sent_to_customer_at = null;
|
|
$newEstimate->customer_viewed_at = null;
|
|
$newEstimate->customer_approved_at = null;
|
|
$newEstimate->prepared_by_id = auth()->id();
|
|
$newEstimate->save();
|
|
|
|
// Duplicate line items
|
|
foreach ($this->estimate->lineItems as $lineItem) {
|
|
$newLineItem = $lineItem->replicate();
|
|
$newLineItem->estimate_id = $newEstimate->id;
|
|
$newLineItem->save();
|
|
}
|
|
|
|
Log::info('Estimate duplicated', [
|
|
'original_estimate_id' => $this->estimate->id,
|
|
'new_estimate_id' => $newEstimate->id,
|
|
'duplicated_by' => auth()->id(),
|
|
]);
|
|
|
|
session()->flash('success', 'Estimate has been duplicated successfully.');
|
|
|
|
return redirect()->route('estimates.edit', $newEstimate);
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('Failed to duplicate estimate', [
|
|
'estimate_id' => $this->estimate->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
session()->flash('error', 'Failed to duplicate estimate. Please try again.');
|
|
}
|
|
}
|
|
|
|
public function downloadPDF()
|
|
{
|
|
try {
|
|
// Load the estimate with relationships for PDF generation
|
|
$estimate = $this->estimate->load([
|
|
'customer',
|
|
'jobCard.customer',
|
|
'jobCard.vehicle',
|
|
'vehicle',
|
|
'lineItems.part',
|
|
'preparedBy',
|
|
'diagnosis',
|
|
]);
|
|
|
|
// For now, let's use a simple approach that should work
|
|
// First, verify HTML generation works
|
|
$html = view('estimates.pdf', compact('estimate'))->render();
|
|
|
|
if (empty($html)) {
|
|
throw new \Exception('Failed to generate HTML template');
|
|
}
|
|
|
|
// Try different approaches for PDF generation
|
|
try {
|
|
// Approach 1: Use Laravel's PDF facade if available
|
|
if (class_exists('\Barryvdh\DomPDF\Facade\Pdf')) {
|
|
$pdf = \Barryvdh\DomPDF\Facade\Pdf::loadHtml($html);
|
|
$pdf->setPaper('letter', 'portrait');
|
|
$output = $pdf->output();
|
|
} else {
|
|
throw new \Exception('PDF facade not available');
|
|
}
|
|
} catch (\Exception $e1) {
|
|
try {
|
|
// Approach 2: Direct DomPDF instantiation
|
|
$dompdf = new \Dompdf\Dompdf;
|
|
$dompdf->loadHtml($html);
|
|
$dompdf->setPaper('letter', 'portrait');
|
|
$dompdf->render();
|
|
$output = $dompdf->output();
|
|
} catch (\Exception $e2) {
|
|
// Approach 3: Return HTML for now
|
|
$filename = 'estimate-'.$estimate->estimate_number.'.html';
|
|
|
|
return response()->streamDownload(function () use ($html) {
|
|
echo $html;
|
|
}, $filename, [
|
|
'Content-Type' => 'text/html',
|
|
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Create filename with estimate number
|
|
$filename = 'estimate-'.$estimate->estimate_number.'.pdf';
|
|
|
|
// Return PDF for download
|
|
return response()->streamDownload(function () use ($output) {
|
|
echo $output;
|
|
}, $filename, [
|
|
'Content-Type' => 'application/pdf',
|
|
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
// Log the error for debugging
|
|
Log::error('PDF generation failed for estimate '.$this->estimate->id, [
|
|
'error' => $e->getMessage(),
|
|
'trace' => $e->getTraceAsString(),
|
|
]);
|
|
|
|
// Show user-friendly error message
|
|
session()->flash('error', 'Failed to generate PDF: '.$e->getMessage());
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public function toggleItemDetails()
|
|
{
|
|
$this->showItemDetails = ! $this->showItemDetails;
|
|
}
|
|
|
|
public function refreshEstimate()
|
|
{
|
|
$this->estimate->refresh();
|
|
session()->flash('success', 'Estimate data refreshed.');
|
|
$this->dispatch('$refresh');
|
|
}
|
|
|
|
public function createWorkOrder()
|
|
{
|
|
if ($this->estimate->customer_approval_status !== 'approved') {
|
|
session()->flash('error', 'Estimate must be approved before creating a work order.');
|
|
|
|
return;
|
|
}
|
|
|
|
return redirect()->route('work-orders.create', ['estimate' => $this->estimate->id]);
|
|
}
|
|
|
|
public function convertToInvoice()
|
|
{
|
|
if ($this->estimate->customer_approval_status !== 'approved') {
|
|
session()->flash('error', 'Estimate must be approved before converting to invoice.');
|
|
|
|
return;
|
|
}
|
|
|
|
if (! auth()->user()->can('create', \App\Models\Invoice::class)) {
|
|
session()->flash('error', 'You do not have permission to create invoices.');
|
|
|
|
return;
|
|
}
|
|
|
|
return redirect()->route('invoices.create-from-estimate', $this->estimate);
|
|
}
|
|
|
|
#[Layout('components.layouts.app')]
|
|
public function render()
|
|
{
|
|
return view('livewire.estimates.show');
|
|
}
|
|
}
|