feat: Add module gating for Inventory, Marketing, and Analytics

- Created has_inventory flag migration for businesses table
- Created EnsureBusinessHasInventory middleware
- Created EnsureBusinessHasMarketing middleware
- Created EnsureBusinessHasAnalytics middleware
- Updated routes/seller.php to apply middleware to module route groups
- Created docs/MODULE_FEATURE_TIERS.md defining free vs paid features

Module access pattern:
- Inventory: Basic CRUD/movements free, analytics paid
- Marketing: Basic templates free, AI/analytics paid
- Analytics: All features paid

All module routes now properly gated with middleware.
This commit is contained in:
Jon Leopard
2025-11-17 16:09:42 -07:00
parent f05211c924
commit d13184819f
6 changed files with 451 additions and 3 deletions

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureBusinessHasAnalytics
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$business = $request->route('business');
if (! $business || ! $business->has_analytics) {
abort(404, 'Analytics module not enabled for this business');
}
return $next($request);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureBusinessHasInventory
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$business = $request->route('business');
if (! $business || ! $business->has_inventory) {
abort(404, 'Inventory module not enabled for this business');
}
return $next($request);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureBusinessHasMarketing
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$business = $request->route('business');
if (! $business || ! $business->has_marketing) {
abort(404, 'Marketing module not enabled for this business');
}
return $next($request);
}
}

View File

@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('businesses', function (Blueprint $table) {
// Add has_inventory module flag
// Basic inventory features (items, movements) are free
// Advanced features (analytics, forecasting) require upgrade
$table->boolean('has_inventory')->default(false)->after('has_analytics');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('businesses', function (Blueprint $table) {
$table->dropColumn('has_inventory');
});
}
};

View File

@@ -0,0 +1,336 @@
# Module Feature Tiers
This document defines which features are available in the free tier vs. paid tiers for each module.
## Philosophy
- **Core functionality** that enables basic operations should be free
- **Advanced features**, analytics, automation, and AI-powered tools are paid
- Businesses should be able to perform essential tasks without upgrading
---
## Inventory Module
**Module Flag**: `has_inventory` (default: `false`)
### Free Tier
- ✅ Inventory Items CRUD (create, read, update, delete)
- ✅ Basic movements (receipt, adjustment, transfer, sale, return, waste)
- ✅ Movement history/audit trail
- ✅ Low stock alerts
- ✅ Expiration alerts
- ✅ Quarantine management
- ✅ Location tracking (warehouse/bin)
- ✅ Lot/batch tracking
### Paid Tier (Premium Inventory)
- 💎 Advanced analytics dashboard
- 💎 Inventory forecasting
- 💎 Automated reordering (PO generation)
- 💎 FIFO/LIFO cost analysis reports
- 💎 Multi-location transfer optimization
- 💎 Custom alert rules
- 💎 Inventory variance reports
- 💎 Stock valuation reports
**Implementation Note**: Controllers should check feature access within methods for paid features, not at route level.
---
## Marketing Module
**Module Flag**: `has_marketing` (default: `false`)
### Free Tier
- ✅ Basic email templates (plain text + simple HTML)
- ✅ Template library (view/duplicate)
- ✅ Manual broadcast creation
- ✅ Basic audience lists
- ✅ Send history
### Paid Tier (Premium Marketing)
- 💎 AI-powered content generation (via `AIContentService`)
- 💎 MJML advanced email templates
- 💎 Automated campaigns (drip sequences)
- 💎 A/B testing
- 💎 Advanced segmentation
- 💎 Engagement analytics
- 💎 Click tracking & heatmaps
- 💎 SMS broadcasts (requires SMS gateway integration)
- 💎 Template analytics (open rates, click rates)
**Implementation Note**: Free tier can create/send broadcasts, but analytics views require paid tier.
---
## Analytics Module
**Module Flag**: `has_analytics` (default: `false`)
### Free Tier
- ❌ **None** - All analytics features are premium
### Paid Tier (Premium Analytics)
- 💎 Product view tracking
- 💎 Buyer engagement scoring
- 💎 High-intent buyer detection
- 💎 Click tracking across platform
- 💎 Session analytics
- 💎 Email interaction tracking
- 💎 Sales analytics dashboard
- 💎 Product performance metrics
- 💎 Marketing campaign ROI
- 💎 Buyer intelligence reports
- 💎 Cross-module reporting
- 💎 Real-time notifications (via Reverb)
**Implementation Note**: Analytics module is entirely premium. All routes require `has_analytics` flag.
---
## Manufacturing Module
**Module Flag**: `has_manufacturing` (default: `false`)
### Free Tier
- ❌ **None** - Manufacturing is entirely optional
### Paid Tier (Manufacturing Module)
- 💎 Department management
- 💎 Work orders
- 💎 Conversion tracking (washing, pressing, extraction)
- 💎 Batch genealogy
- 💎 Wash reports
- 💎 Yield tracking
- 💎 Quality grading
- 💎 Equipment tracking
- 💎 Processing metrics dashboard
**Implementation Note**: Entire module requires `has_manufacturing` flag at route level.
---
## Processing Module
**Module Flag**: `has_processing` (default: `false`)
### Free Tier
- ❌ **None** - Processing is entirely optional
### Paid Tier (Processing Module)
- 💎 Department-based conversions
- 💎 Idle fresh frozen inventory tracking
- 💎 Stage 1 & Stage 2 wash reports
- 💎 Yield percentage calculations
- 💎 Strain performance analytics
- 💎 Daily performance reports
- 💎 Equipment assignment
- 💎 Operator tracking
**Implementation Note**: Similar to Manufacturing, entire module is gated.
---
## Compliance Module
**Module Flag**: `has_compliance` (default: `false`)
### Free Tier
- ✅ Basic batch tracking
- ✅ Manual COA uploads
- ✅ Expiration date tracking
### Paid Tier (Compliance Module)
- 💎 METRC integration
- 💎 Automated regulatory reporting
- 💎 Lab results integration
- 💎 Quarantine workflows
- 💎 Compliance audit trails
- 💎 License management
- 💎 Regulatory alerts
**Implementation Note**: Module not yet implemented. Basic compliance features (COA, batches) exist in core product.
---
## Implementation Guidelines
### Route-Level Gating
```php
// Entire module requires flag
Route::prefix('inventory')->name('inventory.')
->middleware(\App\Http\Middleware\EnsureBusinessHasInventory::class)
->group(function () {
// All inventory routes
});
```
### Controller-Level Feature Checks
```php
// Within free-tier route, check for paid features
public function analytics(Request $request, Business $business)
{
// Check if business has premium inventory analytics
if (!$business->has_premium_inventory_analytics) {
return redirect()->route('seller.business.plans')
->with('error', 'Inventory analytics requires Premium Inventory upgrade');
}
// Show analytics
}
```
### View-Level Feature Flags
```blade
@if($business->has_inventory)
<a href="{{ route('seller.business.inventory.dashboard', $business) }}">
Inventory
</a>
@if($business->has_premium_inventory_analytics)
<a href="{{ route('seller.business.inventory.analytics', $business) }}">
Advanced Analytics
</a>
@endif
@endif
```
---
## Upgrade Paths
### Suggested Pricing Structure (Recommendations)
| Module | Free Tier | Basic Paid | Premium Paid |
|--------|-----------|------------|--------------|
| Inventory | Core CRUD | - | Full Analytics |
| Marketing | Basic Templates | - | AI + Analytics |
| Analytics | None | - | Full Suite |
| Manufacturing | None | Full Module | - |
| Processing | None | Full Module | - |
| Compliance | Basic COA | Full Module | - |
### Module Combinations
**Starter Package** (Free):
- Basic inventory tracking
- Basic marketing templates
- Core product/order management
**Growth Package** ($X/month):
- Premium Inventory (analytics + forecasting)
- Premium Marketing (AI + automation)
- Manufacturing OR Processing module
**Enterprise Package** ($Y/month):
- All Growth features
- Premium Analytics
- Compliance module
- Custom integrations
- Priority support
---
## Database Schema
### Module Flags (businesses table)
```php
$table->boolean('has_inventory')->default(false);
$table->boolean('has_marketing')->default(false);
$table->boolean('has_analytics')->default(false);
$table->boolean('has_manufacturing')->default(false);
$table->boolean('has_processing')->default(false);
$table->boolean('has_compliance')->default(false);
```
### Potential Premium Flags (Future)
```php
// If we want granular paid feature control
$table->boolean('has_premium_inventory_analytics')->default(false);
$table->boolean('has_premium_marketing_ai')->default(false);
```
**Note**: For MVP, using the main module flags is sufficient. Premium sub-features can be gated via plan tier checks in code.
---
## Testing Module Access
### Seeder Configuration
```php
// In DevSeeder or ModuleSeeder
$business->update([
'has_inventory' => true, // Enable for testing
'has_marketing' => true,
'has_analytics' => false, // Disable to test gating
]);
```
### Manual Testing
1. **Enable Module**: Set `has_inventory = true` in database
2. **Test Access**: Navigate to `/s/{business}/inventory` - should work
3. **Disable Module**: Set `has_inventory = false`
4. **Test Gate**: Navigate to `/s/{business}/inventory` - should 404
---
## Filament Admin Controls
Super admins should be able to enable/disable modules per business via Filament:
```php
// In BusinessResource.php
Forms\Components\Section::make('Module Access')
->schema([
Forms\Components\Toggle::make('has_inventory')
->label('Inventory Module'),
Forms\Components\Toggle::make('has_marketing')
->label('Marketing Module'),
Forms\Components\Toggle::make('has_analytics')
->label('Analytics Module (Premium)'),
Forms\Components\Toggle::make('has_manufacturing')
->label('Manufacturing Module'),
Forms\Components\Toggle::make('has_processing')
->label('Processing Module'),
Forms\Components\Toggle::make('has_compliance')
->label('Compliance Module'),
]);
```
---
## Future Considerations
### Self-Service Upgrade Flow
1. User sees locked feature/module
2. Click "Upgrade" button
3. Redirect to `/s/{business}/plans-and-billing`
4. Show available plans with features
5. Stripe checkout integration
6. Automatic flag enablement on payment success
### Trial Periods
```php
$table->timestamp('inventory_trial_ends_at')->nullable();
$table->timestamp('marketing_trial_ends_at')->nullable();
// Check in middleware
if ($business->inventory_trial_ends_at && $business->inventory_trial_ends_at->isPast()) {
abort(403, 'Inventory module trial has expired');
}
```
---
**Document Last Updated**: 2025-11-17
**Status**: Active - Implemented with PR #53 merge

View File

@@ -380,7 +380,8 @@ Route::prefix('s')->name('seller.')->middleware('seller')->group(function () {
// Inventory Module (Optional)
// Flag: has_inventory
// Features: Inventory tracking, movements, alerts, warehouse management
Route::prefix('inventory')->name('inventory.')->group(function () {
// Note: Basic features (items, movements) are free; advanced analytics are paid
Route::prefix('inventory')->name('inventory.')->middleware(\App\Http\Middleware\EnsureBusinessHasInventory::class)->group(function () {
// Inventory Dashboard
Route::get('/', [\App\Http\Controllers\Seller\Inventory\DashboardController::class, 'index'])->name('dashboard');
@@ -427,7 +428,8 @@ Route::prefix('s')->name('seller.')->middleware('seller')->group(function () {
// Marketing Module (Optional)
// Flag: has_marketing
// Features: Social media management, campaigns, email marketing
Route::prefix('marketing')->name('marketing.')->group(function () {
// Note: Basic templates are free; AI content and advanced analytics are paid
Route::prefix('marketing')->name('marketing.')->middleware(\App\Http\Middleware\EnsureBusinessHasMarketing::class)->group(function () {
// Broadcast Management (Email campaigns)
Route::prefix('broadcasts')->name('broadcasts.')->group(function () {
Route::get('/', [\App\Http\Controllers\Seller\Marketing\BroadcastController::class, 'index'])->name('index');
@@ -455,7 +457,8 @@ Route::prefix('s')->name('seller.')->middleware('seller')->group(function () {
// Flag: has_analytics
// Features: Business intelligence, cross-module reporting, executive dashboards
// URL: /s/{business}/buyer-intelligence/*
Route::prefix('buyer-intelligence')->name('buyer-intelligence.')->middleware('module.analytics')->group(function () {
// Note: All analytics features are premium/paid
Route::prefix('buyer-intelligence')->name('buyer-intelligence.')->middleware(\App\Http\Middleware\EnsureBusinessHasAnalytics::class)->group(function () {
// Main analytics dashboard (overview)
Route::get('/', [\App\Http\Controllers\Seller\Marketing\Analytics\AnalyticsDashboardController::class, 'index'])->name('index');