diff --git a/app/Http/Controllers/Pos/LocationController.php b/app/Http/Controllers/Pos/LocationController.php index bba3ebd..4aaf4df 100644 --- a/app/Http/Controllers/Pos/LocationController.php +++ b/app/Http/Controllers/Pos/LocationController.php @@ -55,7 +55,7 @@ class LocationController extends Controller if (! $this->subscriptions->canAddLocation($account, $owner)) { return redirect()->route('pos.pro.index') - ->with('upsell', 'Multi-branch POS requires Ladill POS Pro or Business.'); + ->with('upsell', $this->subscriptions->locationLimitMessage($account)); } $data = $request->validate([ diff --git a/app/Services/Pos/SubscriptionService.php b/app/Services/Pos/SubscriptionService.php index a00f885..b13d019 100644 --- a/app/Services/Pos/SubscriptionService.php +++ b/app/Services/Pos/SubscriptionService.php @@ -111,6 +111,32 @@ class SubscriptionService return PosLocation::owned($ownerRef)->count(); } + /** + * Max branches for the account's plan. null = unlimited. + */ + public function maxLocations(User $user): ?int + { + if (! $this->gatingActive()) { + return null; + } + + $key = $this->planKey($user); + + if ($key === 'free') { + return (int) config('pos.free.max_locations', 1); + } + + if ($key === ProSubscription::PLAN_ENTERPRISE) { + $max = config('pos.plans.enterprise.max_locations'); + + return $max === null ? null : (int) $max; + } + + $max = config('pos.plans.pro.max_locations', 3); + + return $max === null ? null : (int) $max; + } + public function canUseMultiLocation(User $user): bool { return $this->isPro($user); @@ -118,11 +144,29 @@ class SubscriptionService public function canAddLocation(User $user, string $ownerRef): bool { - if (! $this->canUseMultiLocation($user)) { - return $this->locationCount($ownerRef) < (int) config('pos.free.max_locations', 1); + $max = $this->maxLocations($user); + + if ($max === null) { + return true; } - return true; + return $this->locationCount($ownerRef) < $max; + } + + public function locationLimitMessage(User $user): string + { + $key = $this->planKey($user); + $max = $this->maxLocations($user); + + if ($key === 'free') { + return 'Your Free plan allows 1 branch. Upgrade to Pro for up to 3 branches, or Business for unlimited.'; + } + + if ($key === ProSubscription::PLAN_PRO && $max !== null) { + return "Your Pro plan allows up to {$max} branches. Upgrade to Business for unlimited locations."; + } + + return 'Branch limit reached for your current plan.'; } public function canManageTeam(User $user): bool diff --git a/config/pos.php b/config/pos.php index 8daa1af..4653a7d 100644 --- a/config/pos.php +++ b/config/pos.php @@ -19,8 +19,16 @@ return [ ], 'plans' => [ - 'pro' => ['price_minor' => (int) env('POS_PRO_PRICE_MINOR', 79000)], - 'enterprise' => ['price_minor' => (int) env('POS_ENTERPRISE_PRICE_MINOR', 249000)], + 'pro' => [ + 'price_minor' => (int) env('POS_PRO_PRICE_MINOR', 79000), + // null = unlimited + 'max_locations' => (int) env('POS_PRO_MAX_LOCATIONS', 3), + ], + 'enterprise' => [ + 'price_minor' => (int) env('POS_ENTERPRISE_PRICE_MINOR', 249000), + // null = unlimited branches + 'max_locations' => null, + ], ], 'prepaid_months' => [6, 12, 24], diff --git a/resources/views/pos/branches/index.blade.php b/resources/views/pos/branches/index.blade.php index 5d06486..969bf15 100644 --- a/resources/views/pos/branches/index.blade.php +++ b/resources/views/pos/branches/index.blade.php @@ -52,6 +52,14 @@

Multi-branch POS is available on Pro or Business.

+ @else + @if (! $canAddBranch) +

+ You've reached the branch limit on Pro (up to 3). + Upgrade to Business + for unlimited locations. +

+ @endif @endunless diff --git a/resources/views/pos/pro/index.blade.php b/resources/views/pos/pro/index.blade.php index e682b10..f55f413 100644 --- a/resources/views/pos/pro/index.blade.php +++ b/resources/views/pos/pro/index.blade.php @@ -61,6 +61,7 @@ @@ -81,7 +82,7 @@
diff --git a/resources/views/pos/settings/_branches-section.blade.php b/resources/views/pos/settings/_branches-section.blade.php index efcf7ed..64bd0ed 100644 --- a/resources/views/pos/settings/_branches-section.blade.php +++ b/resources/views/pos/settings/_branches-section.blade.php @@ -47,6 +47,14 @@ Pro or Business. Your account is limited to one branch.

+ @else + @if (! empty($canManageBranches) && ! $canAddBranch) +

+ You've reached the branch limit on Pro (up to 3). + Upgrade to Business + for unlimited locations. +

+ @endif @endunless
diff --git a/tests/Feature/PosMultiBranchTest.php b/tests/Feature/PosMultiBranchTest.php index e8ddc2b..e5265f7 100644 --- a/tests/Feature/PosMultiBranchTest.php +++ b/tests/Feature/PosMultiBranchTest.php @@ -47,6 +47,20 @@ class PosMultiBranchTest extends TestCase ]); } + private function activateBusiness(User $user): void + { + ProSubscription::create([ + 'user_id' => $user->id, + 'plan' => ProSubscription::PLAN_ENTERPRISE, + 'status' => ProSubscription::STATUS_ACTIVE, + 'price_minor' => 24900, + 'currency' => 'GHS', + 'auto_renew' => true, + 'started_at' => now(), + 'current_period_end' => now()->addMonth(), + ]); + } + protected function setUp(): void { parent::setUp(); @@ -104,6 +118,58 @@ class PosMultiBranchTest extends TestCase $this->assertSame(2, PosLocation::owned($owner->public_id)->count()); } + public function test_pro_account_cannot_add_fourth_branch(): void + { + $owner = $this->owner(); + $this->activatePro($owner); + + foreach (['Main', 'Mall', 'Airport'] as $i => $name) { + PosLocation::create([ + 'owner_ref' => $owner->public_id, + 'name' => $name, + 'currency' => 'GHS', + 'is_default' => $i === 0, + ]); + } + + $this->assertSame(3, PosLocation::owned($owner->public_id)->count()); + + $this->actingAs($owner) + ->post(route('pos.branches.store'), [ + 'name' => 'Fourth branch', + 'currency' => 'GHS', + 'service_style' => 'retail', + ]) + ->assertRedirect(route('pos.pro.index')); + + $this->assertSame(3, PosLocation::owned($owner->public_id)->count()); + } + + public function test_business_account_can_add_beyond_three_branches(): void + { + $owner = $this->owner(); + $this->activateBusiness($owner); + + foreach (['Main', 'Mall', 'Airport'] as $i => $name) { + PosLocation::create([ + 'owner_ref' => $owner->public_id, + 'name' => $name, + 'currency' => 'GHS', + 'is_default' => $i === 0, + ]); + } + + $this->actingAs($owner) + ->post(route('pos.branches.store'), [ + 'name' => 'Fourth branch', + 'currency' => 'GHS', + 'service_style' => 'retail', + ]) + ->assertRedirect(route('pos.settings').'#branches'); + + $this->assertSame(4, PosLocation::owned($owner->public_id)->count()); + } + public function test_cashier_is_limited_to_assigned_branch_sales(): void { $owner = $this->owner();