- Refactor New Quote page to enterprise data-entry layout (2-column, dense) - Add Payment Terms dropdown (COD, NET 15, NET 30, NET 60) - Fix sidebar menu active states and route names - Fix brand filter badge visibility on brands page - Remove company_name references (use business instead) - Polish Promotions page layout - Fix double-click issue on sidebar menu collapse - Make all searches case-insensitive (like -> ilike for PostgreSQL)
172 lines
5.3 KiB
PHP
172 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Seller;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Business;
|
|
use App\Models\Conversation;
|
|
use App\Models\Message;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Mail;
|
|
|
|
class ConversationController extends Controller
|
|
{
|
|
public function index(Business $business)
|
|
{
|
|
$status = request('status'); // open / closed
|
|
$unread = request('unread'); // '1'
|
|
$hasSms = request('has_sms'); // '1'
|
|
$hasEmail = request('has_email'); // '1'
|
|
$search = request('q');
|
|
|
|
$query = Conversation::with('contact')
|
|
->orderBy('last_message_at', 'desc');
|
|
|
|
if ($search) {
|
|
$query->where(function ($q) use ($search) {
|
|
$q->whereHas('contact', function ($c) use ($search) {
|
|
$c->where('name', 'ilike', "%{$search}%")
|
|
->orWhere('email', 'ilike', "%{$search}%")
|
|
->orWhere('phone', 'ilike', "%{$search}%");
|
|
})
|
|
->orWhereHas('messages', function ($m) use ($search) {
|
|
$m->where('message_body', 'ilike', "%{$search}%");
|
|
});
|
|
});
|
|
}
|
|
|
|
if ($status === 'open' || $status === 'closed') {
|
|
$query->where('status', $status);
|
|
}
|
|
|
|
if ($unread === '1') {
|
|
$query->whereHas('messages', function ($q) {
|
|
$q->where('direction', 'inbound')
|
|
->where('is_read', false);
|
|
});
|
|
}
|
|
|
|
if ($hasSms === '1') {
|
|
$query->whereHas('messages', function ($q) {
|
|
$q->where('channel', 'sms');
|
|
});
|
|
}
|
|
|
|
if ($hasEmail === '1') {
|
|
$query->whereHas('messages', function ($q) {
|
|
$q->where('channel', 'email');
|
|
});
|
|
}
|
|
|
|
$conversations = $query->paginate(20);
|
|
|
|
return view('seller.messaging.conversations.index', compact('business', 'conversations'));
|
|
}
|
|
|
|
public function show(Business $business, Conversation $conversation)
|
|
{
|
|
$messages = $conversation->messages()
|
|
->orderBy('created_at', 'asc')
|
|
->get();
|
|
|
|
$conversation->messages()
|
|
->where('direction', 'inbound')
|
|
->where('is_read', false)
|
|
->update(['is_read' => true]);
|
|
|
|
$conversation->refresh();
|
|
|
|
return view('seller.messaging.conversations.show', compact('business', 'conversation', 'messages'));
|
|
}
|
|
|
|
public function reply(Request $request, Business $business, Conversation $conversation)
|
|
{
|
|
$request->validate([
|
|
'message_body' => 'required|string',
|
|
]);
|
|
|
|
$body = $request->input('message_body');
|
|
|
|
// Determine channel: default to email if available, otherwise SMS
|
|
$contact = $conversation->contact;
|
|
|
|
// If both exist AND reply_channel is provided
|
|
if ($request->filled('reply_channel')) {
|
|
$channel = $request->input('reply_channel'); // email or sms
|
|
} else {
|
|
// Fallback: auto-detect based on available contact info
|
|
$channel = $contact->email
|
|
? 'email'
|
|
: ($contact->phone ? 'sms' : 'in_app');
|
|
}
|
|
|
|
// Validate email subject for email replies
|
|
if ($channel === 'email') {
|
|
$request->validate([
|
|
'email_subject' => 'required|string|max:200',
|
|
]);
|
|
}
|
|
|
|
// Store outbound message
|
|
$service = app(\App\Services\ConversationService::class);
|
|
|
|
$messageModel = $service->storeMessage(
|
|
$conversation,
|
|
$channel,
|
|
'outbound',
|
|
$body
|
|
);
|
|
|
|
// Now send via email or SMS
|
|
if ($channel === 'email') {
|
|
// outbound email
|
|
$subject = $request->input('email_subject') ?: ('Reply from '.$conversation->brand->name);
|
|
|
|
Mail::raw($body, function ($message) use ($conversation, $contact, $subject) {
|
|
$message->to($contact->email)
|
|
->from($conversation->brand->inbound_email)
|
|
->subject($subject);
|
|
});
|
|
|
|
// Mark as 'sent' after handing to mail provider
|
|
// Provider webhooks can later update to 'delivered' or 'failed'
|
|
$messageModel->update([
|
|
'delivery_status' => 'sent',
|
|
'delivery_status_at' => now(),
|
|
]);
|
|
}
|
|
|
|
if ($channel === 'sms') {
|
|
// outbound SMS
|
|
// (Hook into your SMS provider here)
|
|
// Example placeholder:
|
|
// Sms::send($contact->phone, $body, $conversation->brand->sms_number);
|
|
|
|
// Mark as 'sent' after handing to SMS provider
|
|
// Provider webhooks can later update to 'delivered' or 'failed'
|
|
$messageModel->update([
|
|
'delivery_status' => 'sent',
|
|
'delivery_status_at' => now(),
|
|
]);
|
|
}
|
|
|
|
$conversation->update(['last_message_at' => now()]);
|
|
|
|
return back();
|
|
}
|
|
|
|
public function close(Business $business, Conversation $conversation)
|
|
{
|
|
$conversation->update(['status' => 'closed']);
|
|
|
|
return back();
|
|
}
|
|
|
|
public function reopen(Business $business, Conversation $conversation)
|
|
{
|
|
$conversation->update(['status' => 'open']);
|
|
|
|
return back();
|
|
}
|
|
}
|