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:
26
app/Http/Middleware/EnsureBusinessHasAnalytics.php
Normal file
26
app/Http/Middleware/EnsureBusinessHasAnalytics.php
Normal 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);
|
||||
}
|
||||
}
|
||||
26
app/Http/Middleware/EnsureBusinessHasInventory.php
Normal file
26
app/Http/Middleware/EnsureBusinessHasInventory.php
Normal 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);
|
||||
}
|
||||
}
|
||||
26
app/Http/Middleware/EnsureBusinessHasMarketing.php
Normal file
26
app/Http/Middleware/EnsureBusinessHasMarketing.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
336
docs/MODULE_FEATURE_TIERS.md
Normal file
336
docs/MODULE_FEATURE_TIERS.md
Normal 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
|
||||
@@ -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');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user