Files
hub/app/Http/Controllers/Seller/Management/FinanceController.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

116 lines
4.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers\Seller\Management;
use App\Http\Controllers\Controller;
use App\Models\Business;
use App\Services\Accounting\ArService;
use App\Services\Accounting\FinanceAnalyticsService;
use App\Support\ManagementDivisionFilter;
use Illuminate\Http\Request;
class FinanceController extends Controller
{
use ManagementDivisionFilter;
public function __construct(
protected FinanceAnalyticsService $analyticsService,
protected ArService $arService
) {}
public function apAging(Request $request, Business $business)
{
$aging = $this->analyticsService->getAPAging($business);
$byDivision = $this->analyticsService->getAPBreakdownByDivision($business);
$byVendor = $this->analyticsService->getAPBreakdownByVendor($business);
$isParent = $this->analyticsService->isParentCompany($business);
return view('seller.management.finance.ap-aging', compact('business', 'aging', 'byDivision', 'byVendor', 'isParent'));
}
public function cashForecast(Request $request, Business $business)
{
$days = $request->integer('days', 30);
$days = in_array($days, [7, 14, 30]) ? $days : 30;
$forecast = $this->analyticsService->getCashForecast($business, $days);
$isParent = $this->analyticsService->isParentCompany($business);
return view('seller.management.finance.cash-forecast', compact('business', 'forecast', 'days', 'isParent'));
}
public function divisionRollup(Request $request, Business $business)
{
if (! $this->analyticsService->isParentCompany($business)) {
abort(403, 'Only parent companies can view divisional rollups.');
}
$divisions = $this->analyticsService->getDivisionRollup($business);
$totals = [
// AP Totals
'ap_outstanding' => $divisions->sum('ap_outstanding'),
'ap_overdue' => $divisions->sum('ap_overdue'),
'ytd_payments' => $divisions->sum('ytd_payments'),
'pending_approval' => $divisions->sum('pending_approval'),
// AR Totals
'ar_total' => $divisions->sum('ar_total'),
'ar_overdue' => $divisions->sum('ar_overdue'),
'ar_at_risk' => $divisions->sum('ar_at_risk'),
'ar_on_hold' => $divisions->sum('ar_on_hold'),
];
return view('seller.management.finance.divisions', compact('business', 'divisions', 'totals'));
}
public function vendorSpend(Request $request, Business $business)
{
$isParent = $this->analyticsService->isParentCompany($business);
$divisions = collect();
$selectedDivisionId = null;
$selectedDivision = null;
if ($isParent) {
$divisions = $this->analyticsService->getChildBusinesses($business);
$divisionIdParam = $request->get('division_id');
if ($divisionIdParam && $divisionIdParam !== 'all') {
$selectedDivisionId = (int) $divisionIdParam;
$selectedDivision = $divisions->firstWhere('id', $selectedDivisionId);
if (! $selectedDivision) {
$selectedDivisionId = null;
}
}
}
$spend = $this->analyticsService->getVendorSpend($business, $selectedDivisionId);
return view('seller.management.finance.vendor-spend', compact(
'business', 'spend', 'isParent', 'divisions', 'selectedDivisionId', 'selectedDivision'
));
}
public function index(Request $request, Business $business)
{
$isParent = $this->analyticsService->isParentCompany($business);
$includeChildren = $isParent;
// AP Data
$aging = $this->analyticsService->getAPAging($business);
$forecast = $this->analyticsService->getCashForecast($business, 7);
// AR Data
$arSummary = $this->arService->getArSummary($business, $includeChildren);
$topArAccounts = $this->arService->getTopArAccounts($business, 5, $includeChildren);
return view('seller.management.finance.index', compact(
'business',
'isParent',
'aging',
'forecast',
'arSummary',
'topArAccounts'
));
}
}