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
137 lines
5.3 KiB
PHP
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];
|
|
}
|
|
}
|