Files
hub/app/Http/Controllers/Seller/Management/UsageBillingController.php
kelly 6d64d9527a feat: implement Management Suite core features
Bank Account Management:
- BankAccountsController with full CRUD operations
- BankAccountService for account management logic
- Bank account views (index, create, edit)
- GL account linking for cash accounts

Bank Transfers:
- BankTransfersController with approval workflow
- BankTransfer model with status management
- Inter-business transfer support
- Transfer views (index, create, show)

Plaid Integration Infrastructure:
- PlaidItem, PlaidAccount, PlaidTransaction models
- PlaidIntegrationService with 10+ methods (stubbed API)
- BankReconciliationController with match/learn flows
- Bank match rules for auto-categorization

Journal Entry Automation:
- JournalEntryService for automatic JE creation
- Bill approval creates expense entries
- Payment completion creates cash entries
- Inter-company Due To/Due From entries

AR Enhancements:
- ArService with getArSummary and getTopArAccounts
- Finance dashboard AR section with drill-down stats
- Credit hold and at-risk tracking
- Top AR accounts table with division column

UI/Navigation:
- Updated SuiteMenuResolver with new menu items
- Removed Usage & Billing from sidebar (moved to Owner)
- Brand Manager Suite menu items added
- Vendors page shows divisions using each vendor

Models and Migrations:
- BankAccount, BankTransfer, BankMatchRule models
- PlaidItem, PlaidAccount, PlaidTransaction models
- Credit hold fields on ArCustomer
- journal_entry_id on ap_bills and ap_payments
2025-12-07 00:34:53 -07:00

194 lines
7.2 KiB
PHP

<?php
namespace App\Http\Controllers\Seller\Management;
use App\Http\Controllers\Controller;
use App\Models\Business;
use App\Support\ManagementDivisionFilter;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class UsageBillingController extends Controller
{
use ManagementDivisionFilter;
public function index(Request $request, Business $business)
{
$this->requireManagementSuite($business);
$divisions = $this->getChildDivisionsIfAny($business);
$selectedDivision = $this->getSelectedDivision($request, $business);
$includeChildren = $this->shouldIncludeChildren($request);
$businessIds = $this->getBusinessIdsForScope($business, $selectedDivision, $includeChildren);
// Collect usage data
$usage = $this->collectUsageData($business, $businessIds);
return view('seller.management.usage-billing.index', [
'business' => $business,
'divisions' => $divisions,
'selectedDivision' => $selectedDivision,
'includeChildren' => $includeChildren,
'usage' => $usage,
]);
}
protected function collectUsageData(Business $parentBusiness, array $businessIds): array
{
$startOfMonth = Carbon::now()->startOfMonth();
$endOfMonth = Carbon::now()->endOfMonth();
// Get suite limits from config
$defaults = config('suites.defaults.sales_suite', []);
// Count active brands
$brandCount = DB::table('brands')
->whereIn('business_id', $businessIds)
->where('is_active', true)
->count();
// Count active products (SKUs)
$skuCount = DB::table('products')
->join('brands', 'products.brand_id', '=', 'brands.id')
->whereIn('brands.business_id', $businessIds)
->where('products.is_active', true)
->count();
// Count messages sent this month
$messageCount = DB::table('messages')
->whereIn('business_id', $businessIds)
->whereBetween('created_at', [$startOfMonth, $endOfMonth])
->count();
// Count menu sends this month
$menuSendCount = DB::table('menu_sends')
->whereIn('business_id', $businessIds)
->whereBetween('created_at', [$startOfMonth, $endOfMonth])
->count();
// Count CRM contacts
$contactCount = DB::table('contacts')
->whereIn('business_id', $businessIds)
->count();
// Calculate limits based on number of brands
$brandLimit = $parentBusiness->brand_limit ?? $defaults['brand_limit'] ?? 1;
$skuLimitPerBrand = $defaults['sku_limit_per_brand'] ?? 15;
$messageLimitPerBrand = $defaults['message_limit_per_brand'] ?? 500;
$menuLimitPerBrand = $defaults['menu_limit_per_brand'] ?? 100;
$contactLimitPerBrand = $defaults['contact_limit_per_brand'] ?? 1000;
$totalSkuLimit = $brandCount * $skuLimitPerBrand;
$totalMessageLimit = $brandCount * $messageLimitPerBrand;
$totalMenuLimit = $brandCount * $menuLimitPerBrand;
$totalContactLimit = $brandCount * $contactLimitPerBrand;
// Is enterprise plan?
$isEnterprise = $parentBusiness->is_enterprise_plan ?? false;
// Get suites enabled
$enabledSuites = $this->getEnabledSuites($parentBusiness);
// Usage by division
$usageByDivision = [];
if (count($businessIds) > 1) {
$usageByDivision = DB::table('businesses')
->whereIn('businesses.id', $businessIds)
->leftJoin('brands', 'brands.business_id', '=', 'businesses.id')
->leftJoin('products', 'products.brand_id', '=', 'brands.id')
->select(
'businesses.id',
'businesses.name',
DB::raw('COUNT(DISTINCT brands.id) as brand_count'),
DB::raw('COUNT(DISTINCT products.id) as sku_count')
)
->groupBy('businesses.id', 'businesses.name')
->get();
}
return [
'brands' => [
'current' => $brandCount,
'limit' => $isEnterprise ? null : $brandLimit,
'percentage' => $brandLimit > 0 ? min(100, ($brandCount / $brandLimit) * 100) : 0,
],
'skus' => [
'current' => $skuCount,
'limit' => $isEnterprise ? null : $totalSkuLimit,
'percentage' => $totalSkuLimit > 0 ? min(100, ($skuCount / $totalSkuLimit) * 100) : 0,
],
'messages' => [
'current' => $messageCount,
'limit' => $isEnterprise ? null : $totalMessageLimit,
'percentage' => $totalMessageLimit > 0 ? min(100, ($messageCount / $totalMessageLimit) * 100) : 0,
],
'menu_sends' => [
'current' => $menuSendCount,
'limit' => $isEnterprise ? null : $totalMenuLimit,
'percentage' => $totalMenuLimit > 0 ? min(100, ($menuSendCount / $totalMenuLimit) * 100) : 0,
],
'contacts' => [
'current' => $contactCount,
'limit' => $isEnterprise ? null : $totalContactLimit,
'percentage' => $totalContactLimit > 0 ? min(100, ($contactCount / $totalContactLimit) * 100) : 0,
],
'is_enterprise' => $isEnterprise,
'enabled_suites' => $enabledSuites,
'usage_by_division' => $usageByDivision,
'billing_period' => [
'start' => $startOfMonth->format('M j, Y'),
'end' => $endOfMonth->format('M j, Y'),
],
];
}
protected function getEnabledSuites(Business $business): array
{
$suites = [];
if ($business->hasSalesSuite()) {
$suites[] = ['name' => 'Sales Suite', 'key' => 'sales'];
}
if ($business->hasProcessingSuite()) {
$suites[] = ['name' => 'Processing Suite', 'key' => 'processing'];
}
if ($business->hasManufacturingSuite()) {
$suites[] = ['name' => 'Manufacturing Suite', 'key' => 'manufacturing'];
}
if ($business->hasDeliverySuite()) {
$suites[] = ['name' => 'Delivery Suite', 'key' => 'delivery'];
}
if ($business->hasManagementSuite()) {
$suites[] = ['name' => 'Management Suite', 'key' => 'management'];
}
if ($business->hasDispensarySuite()) {
$suites[] = ['name' => 'Dispensary Suite', 'key' => 'dispensary'];
}
return $suites;
}
protected function getBusinessIdsForScope(Business $business, ?Business $selectedDivision, bool $includeChildren): array
{
if ($selectedDivision) {
if ($includeChildren) {
return $selectedDivision->divisions()->pluck('id')
->prepend($selectedDivision->id)
->toArray();
}
return [$selectedDivision->id];
}
if ($includeChildren && $business->hasChildBusinesses()) {
return $business->divisions()->pluck('id')
->prepend($business->id)
->toArray();
}
return [$business->id];
}
}