sackey e3b2b220d2
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
Enhance UI and functionality across various components
- 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.
2025-08-16 14:36:58 +00:00

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');
}
}