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
116 lines
4.2 KiB
PHP
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'
|
|
));
|
|
}
|
|
}
|