- 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)
235 lines
7.3 KiB
PHP
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;
|
|
}
|
|
}
|
|
}
|