Files
hub/app/Http/Controllers/Seller/MarketingAutomationController.php
kelly 05ef21cd71 fix: resolve multiple Gitea issues (#161-#167)
Issues fixed:
- #161: Quote submission - add tax_rate migration (already existed)
- #162: Contact edit 404 - change contact->id to contact->hashid in routes
- #163: Batch creation - expand batch_type constraint to include component/homogenized
- #164: Stock search - convert client-side to server-side search
- #165: Product edit save button - make always visible with different states
- #166: Product creation validation - make price_unit nullable with default
- #167: Invoice products - change stockFilter default from true to false

Additional fixes:
- Fix layouts.seller (non-existent) to layouts.app-with-sidebar in 14 views
- Fix CRM route names from seller.crm.* to seller.business.crm.*
2025-12-10 11:24:01 -07:00

241 lines
9.1 KiB
PHP

<?php
namespace App\Http\Controllers\Seller;
use App\Http\Controllers\Controller;
use App\Jobs\RunMarketingAutomationJob;
use App\Models\Business;
use App\Models\Marketing\MarketingAutomation;
use App\Models\Marketing\MarketingList;
use App\Models\Marketing\MarketingTemplate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class MarketingAutomationController extends Controller
{
public function index(Request $request, Business $business)
{
$this->authorizeForBusiness($business);
$automations = MarketingAutomation::where('business_id', $business->id)
->with('latestRun')
->when($request->status === 'active', fn ($q) => $q->where('is_active', true))
->when($request->status === 'inactive', fn ($q) => $q->where('is_active', false))
->when($request->trigger_type, fn ($q, $type) => $q->where('trigger_type', $type))
->latest()
->paginate(15);
return view('seller.marketing.automations.index', compact('business', 'automations'));
}
public function create(Request $request, Business $business)
{
$this->authorizeForBusiness($business);
$presets = MarketingAutomation::getTypePresets();
$selectedPreset = $request->query('preset');
$lists = MarketingList::where('business_id', $business->id)
->withCount('contacts')
->orderBy('name')
->get();
$templates = MarketingTemplate::where('business_id', $business->id)
->where('is_active', true)
->orderBy('name')
->get();
return view('seller.marketing.automations.create', compact(
'business',
'presets',
'selectedPreset',
'lists',
'templates'
));
}
public function store(Request $request, Business $business)
{
$this->authorizeForBusiness($business);
$validated = $request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string|max:1000',
'scope' => 'required|in:internal,portal',
'trigger_type' => 'required|in:'.implode(',', array_keys(MarketingAutomation::TRIGGER_TYPES)),
'trigger_config' => 'required|json',
'condition_config' => 'required|json',
'action_config' => 'required|json',
]);
// Decode JSON configs from the form
$triggerConfig = json_decode($validated['trigger_config'], true) ?? [];
$conditionConfig = json_decode($validated['condition_config'], true) ?? [];
$actionConfig = json_decode($validated['action_config'], true) ?? [];
// Normalize condition config - convert percentage values
if (isset($conditionConfig['min_price_advantage']) && $conditionConfig['min_price_advantage'] > 1) {
$conditionConfig['min_price_advantage'] = $conditionConfig['min_price_advantage'] / 100;
}
// Map velocity_threshold to velocity_30d_threshold for slow mover clearance
if (isset($conditionConfig['velocity_threshold'])) {
$conditionConfig['velocity_30d_threshold'] = $conditionConfig['velocity_threshold'];
unset($conditionConfig['velocity_threshold']);
}
$automation = MarketingAutomation::create([
'business_id' => $business->id,
'name' => $validated['name'],
'description' => $validated['description'],
'is_active' => true,
'scope' => $validated['scope'],
'trigger_type' => $validated['trigger_type'],
'trigger_config' => $triggerConfig,
'condition_config' => $conditionConfig,
'action_config' => $actionConfig,
]);
return redirect()
->route('seller.business.marketing.automations.index', $business)
->with('success', "Automation \"{$automation->name}\" created successfully.");
}
public function edit(Request $request, Business $business, MarketingAutomation $automation)
{
$this->authorizeForBusiness($business);
$this->ensureAutomationBelongsToBusiness($automation, $business);
$presets = MarketingAutomation::getTypePresets();
$lists = MarketingList::where('business_id', $business->id)
->withCount('contacts')
->orderBy('name')
->get();
$templates = MarketingTemplate::where('business_id', $business->id)
->where('is_active', true)
->orderBy('name')
->get();
return view('seller.marketing.automations.edit', compact(
'business',
'automation',
'presets',
'lists',
'templates'
));
}
public function update(Request $request, Business $business, MarketingAutomation $automation)
{
$this->authorizeForBusiness($business);
$this->ensureAutomationBelongsToBusiness($automation, $business);
$validated = $request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string|max:1000',
'scope' => 'required|in:internal,portal',
'trigger_type' => 'required|in:'.implode(',', array_keys(MarketingAutomation::TRIGGER_TYPES)),
'trigger_config' => 'required|json',
'condition_config' => 'required|json',
'action_config' => 'required|json',
]);
// Decode JSON configs from the form
$triggerConfig = json_decode($validated['trigger_config'], true) ?? [];
$conditionConfig = json_decode($validated['condition_config'], true) ?? [];
$actionConfig = json_decode($validated['action_config'], true) ?? [];
// Normalize condition config - convert percentage values
if (isset($conditionConfig['min_price_advantage']) && $conditionConfig['min_price_advantage'] > 1) {
$conditionConfig['min_price_advantage'] = $conditionConfig['min_price_advantage'] / 100;
}
// Map velocity_threshold to velocity_30d_threshold for slow mover clearance
if (isset($conditionConfig['velocity_threshold'])) {
$conditionConfig['velocity_30d_threshold'] = $conditionConfig['velocity_threshold'];
unset($conditionConfig['velocity_threshold']);
}
$automation->update([
'name' => $validated['name'],
'description' => $validated['description'],
'scope' => $validated['scope'],
'trigger_type' => $validated['trigger_type'],
'trigger_config' => $triggerConfig,
'condition_config' => $conditionConfig,
'action_config' => $actionConfig,
]);
return redirect()
->route('seller.business.marketing.automations.index', $business)
->with('success', "Automation \"{$automation->name}\" updated successfully.");
}
public function toggle(Request $request, Business $business, MarketingAutomation $automation)
{
$this->authorizeForBusiness($business);
$this->ensureAutomationBelongsToBusiness($automation, $business);
$automation->update([
'is_active' => ! $automation->is_active,
]);
$status = $automation->is_active ? 'enabled' : 'disabled';
return redirect()
->back()
->with('success', "Automation \"{$automation->name}\" has been {$status}.");
}
public function runNow(Request $request, Business $business, MarketingAutomation $automation)
{
$this->authorizeForBusiness($business);
$this->ensureAutomationBelongsToBusiness($automation, $business);
if (! $automation->is_active) {
return redirect()
->back()
->with('error', 'Cannot run an inactive automation. Enable it first.');
}
// Dispatch the job
RunMarketingAutomationJob::dispatch($automation->id);
return redirect()
->route('seller.business.marketing.automations.runs.index', [$business, $automation])
->with('success', "Automation \"{$automation->name}\" has been queued to run.");
}
public function destroy(Request $request, Business $business, MarketingAutomation $automation)
{
$this->authorizeForBusiness($business);
$this->ensureAutomationBelongsToBusiness($automation, $business);
$name = $automation->name;
$automation->delete();
return redirect()
->route('seller.business.marketing.automations.index', $business)
->with('success', "Automation \"{$name}\" has been deleted.");
}
protected function authorizeForBusiness(Business $business): void
{
$user = Auth::user();
// Check user has access to this business
if (! $user->businesses->contains($business->id) && ! $user->hasRole('Super Admin')) {
abort(403, 'Unauthorized access to this business.');
}
}
protected function ensureAutomationBelongsToBusiness(MarketingAutomation $automation, Business $business): void
{
if ($automation->business_id !== $business->id) {
abort(404);
}
}
}