Files
hub/app/Http/Controllers/Seller/MessagingController.php
kelly 11c67f491c feat: MySQL data import and parallel test fixes
- Import Cannabrands data from MySQL to PostgreSQL (strains, categories,
  companies, locations, contacts, products, images, invoices)
- Make migrations idempotent for parallel test execution
- Add ParallelTesting setup for separate test databases per process
- Update product type constraint for imported data
- Keep MysqlImport seeders for reference (data already in PG)
2025-12-04 19:26:38 -07:00

235 lines
7.3 KiB
PHP

<?php
namespace App\Http\Controllers\Seller;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Seller\Crm\InboxController as CrmInboxController;
use App\Models\Conversation;
use App\Models\Message;
use Illuminate\Http\Request;
class MessagingController extends Controller
{
/**
* Display messaging inbox with conversation list.
*
* If CRM is enabled, delegates to the enhanced CRM inbox controller
* which provides additional features like task integration, pipeline
* context, and advanced filtering. Both views stay under the
* Conversations menu area for consistent navigation.
*/
public function index(Request $request, \App\Models\Business $business)
{
// If CRM is enabled, use the enhanced CRM inbox
if ($business->hasCrmAccess()) {
return app(CrmInboxController::class)->index($request, $business);
}
// Basic conversations view
$query = Conversation::where('business_id', $business->id)
->with(['primaryContact', 'latestMessage']);
// Filter by status
if ($request->has('status') && $request->status) {
$query->where('status', $request->status);
}
// Filter by channel
if ($request->has('channel') && $request->channel) {
$query->where('channel_type', $request->channel);
}
// Search
if ($request->has('search') && $request->search) {
$query->where(function ($q) use ($request) {
$q->where('subject', 'LIKE', "%{$request->search}%")
->orWhereHas('primaryContact', function ($contactQuery) use ($request) {
$contactQuery->where('name', 'LIKE', "%{$request->search}%")
->orWhere('email', 'LIKE', "%{$request->search}%")
->orWhere('phone', 'LIKE', "%{$request->search}%");
});
});
}
$conversations = $query
->orderByDesc('last_message_at')
->paginate(20);
return view('seller.messaging.index', compact('business', 'conversations'));
}
/**
* Display a specific conversation thread.
*
* If CRM is enabled, delegates to the enhanced CRM inbox controller.
*/
public function show(Request $request, \App\Models\Business $business, Conversation $conversation)
{
// If CRM is enabled, use the enhanced CRM inbox view
if ($business->hasCrmAccess()) {
return app(CrmInboxController::class)->show($request, $business, $conversation);
}
// Ensure business owns this conversation
if ($conversation->business_id !== $business->id) {
abort(403);
}
$conversation->load(['primaryContact', 'participants']);
$messages = $conversation->messages()
->with('contact')
->orderBy('created_at', 'asc')
->get();
return view('seller.messaging.show', compact('business', 'conversation', 'messages'));
}
/**
* Close a conversation
*/
public function close(Request $request, \App\Models\Business $business, Conversation $conversation)
{
if ($conversation->business_id !== $business->id) {
abort(403);
}
$conversation->close();
return back()->with('success', 'Conversation closed');
}
/**
* Reopen a conversation
*/
public function reopen(Request $request, \App\Models\Business $business, Conversation $conversation)
{
if ($conversation->business_id !== $business->id) {
abort(403);
}
$conversation->reopen();
return back()->with('success', 'Conversation reopened');
}
/**
* Archive a conversation
*/
public function archive(Request $request, \App\Models\Business $business, Conversation $conversation)
{
if ($conversation->business_id !== $business->id) {
abort(403);
}
$conversation->update(['status' => 'archived']);
return back()->with('success', 'Conversation archived');
}
/**
* Send a reply in a conversation
*/
public function reply(Request $request, \App\Models\Business $business, Conversation $conversation)
{
// Ensure business owns this conversation
if ($conversation->business_id !== $business->id) {
abort(403);
}
$request->validate([
'body' => 'required|string|max:5000',
'channel_type' => 'required|in:sms,email',
]);
try {
// Create the outbound message
$message = Message::create([
'conversation_id' => $conversation->id,
'business_id' => $business->id,
'contact_id' => $conversation->primary_contact_id,
'direction' => 'outbound',
'channel_type' => $request->channel_type,
'body' => $request->body,
'status' => 'pending',
]);
// Send via appropriate channel
if ($request->channel_type === 'sms') {
$this->sendSms($message, $conversation->primaryContact);
} else {
$this->sendEmail($message, $conversation->primaryContact);
}
// Update conversation metadata
$conversation->updateLastMessage($message);
// Mark conversation as open (in case it was closed)
if ($conversation->status !== 'open') {
$conversation->update(['status' => 'open']);
}
return back()->with('success', 'Reply sent successfully');
} catch (\Exception $e) {
return back()->with('error', 'Failed to send reply: '.$e->getMessage());
}
}
/**
* Send SMS message
*/
private function sendSms(Message $message, $contact)
{
if (! $contact || ! $contact->phone) {
$message->markAsFailed('No phone number available');
return;
}
try {
$smsManager = app(\App\Services\SMS\SmsManager::class);
$result = $smsManager->send(
to: $contact->phone,
message: $message->body,
business: $message->business
);
$message->update([
'status' => 'sent',
'sent_at' => now(),
'provider_message_id' => $result['message_id'] ?? null,
]);
} catch (\Exception $e) {
$message->markAsFailed($e->getMessage());
throw $e;
}
}
/**
* Send email message
*/
private function sendEmail(Message $message, $contact)
{
if (! $contact || ! $contact->email) {
$message->markAsFailed('No email address available');
return;
}
try {
\Mail::raw($message->body, function ($mail) use ($contact, $message) {
$mail->to($contact->email)
->subject('Message from '.$message->business->name);
});
$message->update([
'status' => 'sent',
'sent_at' => now(),
]);
} catch (\Exception $e) {
$message->markAsFailed($e->getMessage());
throw $e;
}
}
}