Files
hub/app/Http/Controllers/Buyer/Crm/InvoiceController.php
kelly 7e2b3d4ce6 fix: update Buyer CRM controllers to use App\Models\Crm namespace
The Buyer CRM controllers were using incorrect namespace imports from
Modules\Crm\Entities\* which doesn't exist. Updated all controllers to
use the correct App\Models\Crm\* namespace:

- DashboardController: CrmInvoice, CrmQuote, CrmThread
- InboxController: CrmThread
- InvoiceController: CrmInvoice, CrmThread
- QuoteController: CrmQuote
- BrandHubController: CrmThread
- MessageController: CrmChannelMessage, CrmThread
- OrderController: CrmThread (inline)

Also changed CrmMessage to CrmChannelMessage as CrmMessage doesn't exist.
2025-12-05 16:09:48 -07:00

299 lines
9.5 KiB
PHP

<?php
namespace App\Http\Controllers\Buyer\Crm;
use App\Http\Controllers\Controller;
use App\Models\Buyer\BuyerInvoiceRecord;
use App\Models\Buyer\BuyerSavedFilter;
use App\Models\Crm\CrmInvoice;
use App\Models\Crm\CrmThread;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class InvoiceController extends Controller
{
public function index(Request $request)
{
$business = Auth::user()->business;
$user = Auth::user();
$query = CrmInvoice::forBuyerBusiness($business->id)
->with(['brand', 'order', 'buyerRecord']);
// Tab filter
$tab = $request->get('tab', 'outstanding');
$query = match ($tab) {
'outstanding' => $query->whereIn('status', [CrmInvoice::STATUS_DRAFT, CrmInvoice::STATUS_SENT, CrmInvoice::STATUS_VIEWED]),
'overdue' => $query->overdue(),
'paid' => $query->paid(),
'disputed' => $query->whereHas('buyerRecord', fn ($q) => $q->disputed()),
default => $query,
};
// Brand filter
if ($brandId = $request->get('brand_id')) {
$query->where('brand_id', $brandId);
}
// Date range
if ($from = $request->get('from_date')) {
$query->whereDate('issue_date', '>=', $from);
}
if ($to = $request->get('to_date')) {
$query->whereDate('issue_date', '<=', $to);
}
// Search
if ($search = $request->get('search')) {
$query->where(function ($q) use ($search) {
$q->where('invoice_number', 'ilike', "%{$search}%")
->orWhereHas('brand', fn ($bq) => $bq->where('name', 'ilike', "%{$search}%"));
});
}
$invoices = $query->orderByDesc('issue_date')
->paginate(20)
->withQueryString();
// Get saved filters
$savedFilters = BuyerSavedFilter::getFiltersForType($business->id, $user->id, BuyerSavedFilter::TYPE_INVOICES);
// Get brands for filter
$brands = \App\Models\Brand::whereHas('invoices', function ($q) use ($business) {
$q->where('buyer_business_id', $business->id);
})->orderBy('name')->get();
// Tab counts
$counts = [
'outstanding' => CrmInvoice::forBuyerBusiness($business->id)
->whereIn('status', [CrmInvoice::STATUS_DRAFT, CrmInvoice::STATUS_SENT, CrmInvoice::STATUS_VIEWED])
->count(),
'overdue' => CrmInvoice::forBuyerBusiness($business->id)->overdue()->count(),
'paid' => CrmInvoice::forBuyerBusiness($business->id)->paid()->count(),
'disputed' => BuyerInvoiceRecord::forBusiness($business->id)->disputed()->count(),
];
// Summary stats
$stats = [
'total_outstanding' => CrmInvoice::forBuyerBusiness($business->id)
->whereIn('status', [CrmInvoice::STATUS_SENT, CrmInvoice::STATUS_VIEWED])
->sum('total'),
'total_overdue' => CrmInvoice::forBuyerBusiness($business->id)
->overdue()
->sum('total'),
];
return view('buyer.crm.invoices.index', compact(
'invoices',
'savedFilters',
'brands',
'tab',
'counts',
'stats'
));
}
public function show(CrmInvoice $invoice)
{
$business = Auth::user()->business;
if ($invoice->buyer_business_id !== $business->id) {
abort(403);
}
$invoice->load(['brand', 'order', 'items.product', 'payments']);
// Get or create buyer record
$buyerRecord = BuyerInvoiceRecord::getOrCreateForInvoice(
$business->id,
$invoice,
$invoice->order_id
);
// Mark as viewed if not already
if ($invoice->status === CrmInvoice::STATUS_SENT) {
$invoice->markAsViewed();
}
// Get related thread if exists
$thread = CrmThread::where('buyer_business_id', $business->id)
->where(function ($q) use ($invoice) {
$q->where('order_id', $invoice->order_id)
->orWhere('subject', 'ilike', "%{$invoice->invoice_number}%");
})
->first();
return view('buyer.crm.invoices.show', compact('invoice', 'buyerRecord', 'thread'));
}
public function pay(Request $request, CrmInvoice $invoice)
{
$business = Auth::user()->business;
$user = Auth::user();
if ($invoice->buyer_business_id !== $business->id) {
abort(403);
}
$buyerRecord = BuyerInvoiceRecord::getOrCreateForInvoice(
$business->id,
$invoice,
$invoice->order_id
);
if (! $buyerRecord->can_pay) {
return back()->with('error', 'This invoice cannot be paid at this time.');
}
// Start payment process (integrate with payment gateway)
$buyerRecord->initiatePayment();
// TODO: Redirect to payment gateway or process payment
// For now, redirect to a payment form
return redirect()->route('buyer.crm.invoices.payment', $invoice);
}
public function payment(CrmInvoice $invoice)
{
$business = Auth::user()->business;
if ($invoice->buyer_business_id !== $business->id) {
abort(403);
}
$invoice->load('brand');
$buyerRecord = BuyerInvoiceRecord::where('invoice_id', $invoice->id)
->where('business_id', $business->id)
->first();
return view('buyer.crm.invoices.payment', compact('invoice', 'buyerRecord'));
}
public function confirmPayment(Request $request, CrmInvoice $invoice)
{
$business = Auth::user()->business;
$user = Auth::user();
if ($invoice->buyer_business_id !== $business->id) {
abort(403);
}
$validated = $request->validate([
'payment_reference' => 'required|string|max:255',
'payment_method' => 'required|string|in:bank_transfer,check,cash,other',
'notes' => 'nullable|string|max:1000',
]);
$buyerRecord = BuyerInvoiceRecord::where('invoice_id', $invoice->id)
->where('business_id', $business->id)
->first();
if ($buyerRecord) {
$buyerRecord->confirmPayment($user->id, $validated['payment_reference']);
}
// Mark invoice as paid (pending seller confirmation)
$invoice->update([
'status' => CrmInvoice::STATUS_PAID,
'paid_at' => now(),
]);
// TODO: Notify seller of payment
return redirect()->route('buyer.crm.invoices.show', $invoice)
->with('success', 'Payment confirmed. The seller will be notified.');
}
public function dispute(Request $request, CrmInvoice $invoice)
{
$business = Auth::user()->business;
if ($invoice->buyer_business_id !== $business->id) {
abort(403);
}
$validated = $request->validate([
'reason' => 'required|string|max:2000',
]);
$buyerRecord = BuyerInvoiceRecord::getOrCreateForInvoice(
$business->id,
$invoice,
$invoice->order_id
);
$buyerRecord->openDispute($validated['reason']);
// TODO: Notify seller of dispute
// TODO: Create thread for dispute discussion
return back()->with('success', 'Dispute opened. The seller will be notified.');
}
public function download(CrmInvoice $invoice)
{
$business = Auth::user()->business;
if ($invoice->buyer_business_id !== $business->id) {
abort(403);
}
$invoice->load(['brand', 'items.product']);
// Generate PDF
$pdf = \PDF::loadView('buyer.crm.invoices.pdf', compact('invoice'));
return $pdf->download("invoice_{$invoice->invoice_number}.pdf");
}
public function export(Request $request)
{
$business = Auth::user()->business;
$query = CrmInvoice::forBuyerBusiness($business->id)
->with(['brand', 'order']);
// Apply filters
if ($status = $request->get('status')) {
$query->where('status', $status);
}
if ($from = $request->get('from_date')) {
$query->whereDate('issue_date', '>=', $from);
}
if ($to = $request->get('to_date')) {
$query->whereDate('issue_date', '<=', $to);
}
$invoices = $query->orderByDesc('issue_date')->get();
$filename = 'invoices_export_'.now()->format('Y-m-d_His').'.csv';
$headers = [
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename=\"{$filename}\"",
];
$callback = function () use ($invoices) {
$file = fopen('php://output', 'w');
fputcsv($file, ['Invoice #', 'Brand', 'Issue Date', 'Due Date', 'Status', 'Total', 'Paid']);
foreach ($invoices as $invoice) {
fputcsv($file, [
$invoice->invoice_number,
$invoice->brand->name ?? 'N/A',
$invoice->issue_date?->format('Y-m-d'),
$invoice->due_date?->format('Y-m-d'),
$invoice->status,
number_format($invoice->total, 2),
$invoice->paid_at?->format('Y-m-d') ?? 'No',
]);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}