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

137 lines
5.3 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 OperationsController 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 operations data
$operations = $this->collectOperationsData($businessIds);
return view('seller.management.operations.index', [
'business' => $business,
'divisions' => $divisions,
'selectedDivision' => $selectedDivision,
'includeChildren' => $includeChildren,
'operations' => $operations,
]);
}
protected function collectOperationsData(array $businessIds): array
{
$today = Carbon::today();
$startOfMonth = Carbon::now()->startOfMonth();
$startOfWeek = Carbon::now()->startOfWeek();
// Order stats
$orderStats = DB::table('orders')
->whereIn('business_id', $businessIds)
->select([
DB::raw('COUNT(CASE WHEN status = \'pending\' THEN 1 END) as pending_orders'),
DB::raw('COUNT(CASE WHEN status = \'processing\' THEN 1 END) as processing_orders'),
DB::raw('COUNT(CASE WHEN status = \'completed\' AND created_at >= \''.$startOfMonth->toDateString().'\' THEN 1 END) as completed_this_month'),
DB::raw('COUNT(CASE WHEN created_at >= \''.$startOfWeek->toDateString().'\' THEN 1 END) as orders_this_week'),
])
->first();
// Product stats
$productStats = DB::table('products')
->join('brands', 'products.brand_id', '=', 'brands.id')
->whereIn('brands.business_id', $businessIds)
->select([
DB::raw('COUNT(*) as total_products'),
DB::raw('COUNT(CASE WHEN products.is_active = true THEN 1 END) as active_products'),
DB::raw('COUNT(CASE WHEN products.stock_quantity <= products.low_stock_threshold AND products.stock_quantity > 0 THEN 1 END) as low_stock_products'),
DB::raw('COUNT(CASE WHEN products.stock_quantity = 0 THEN 1 END) as out_of_stock_products'),
])
->first();
// Customer stats
$customerStats = DB::table('contacts')
->whereIn('business_id', $businessIds)
->where('is_customer', true)
->select([
DB::raw('COUNT(*) as total_customers'),
DB::raw('COUNT(CASE WHEN created_at >= \''.$startOfMonth->toDateString().'\' THEN 1 END) as new_this_month'),
])
->first();
// Bill stats
$billStats = DB::table('ap_bills')
->whereIn('business_id', $businessIds)
->select([
DB::raw('COUNT(CASE WHEN status = \'pending\' THEN 1 END) as pending_bills'),
DB::raw('COUNT(CASE WHEN status = \'approved\' THEN 1 END) as approved_bills'),
DB::raw('COUNT(CASE WHEN status = \'overdue\' THEN 1 END) as overdue_bills'),
DB::raw('SUM(CASE WHEN status IN (\'pending\', \'approved\') THEN total_amount ELSE 0 END) as pending_amount'),
])
->first();
// Expense stats
$expenseStats = DB::table('expenses')
->whereIn('business_id', $businessIds)
->select([
DB::raw('COUNT(CASE WHEN status = \'pending\' THEN 1 END) as pending_expenses'),
DB::raw('SUM(CASE WHEN status = \'pending\' THEN amount ELSE 0 END) as pending_amount'),
])
->first();
// Recent activity
$recentOrders = DB::table('orders')
->join('businesses', 'orders.business_id', '=', 'businesses.id')
->whereIn('orders.business_id', $businessIds)
->orderByDesc('orders.created_at')
->limit(5)
->select(['orders.*', 'businesses.name as business_name'])
->get();
return [
'orders' => $orderStats,
'products' => $productStats,
'customers' => $customerStats,
'bills' => $billStats,
'expenses' => $expenseStats,
'recent_orders' => $recentOrders,
];
}
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];
}
}