user() ?? Auth::guard('receptionist')->user(); $query = Investor::with('branches'); if ($user && $user->isReceptionist()) { $query->where(function($q) use ($user) { $q->where('applicable_to_all_branches', true) ->orWhereHas('branches', function($bq) use ($user) { $bq->where('branches.id', $user->branch_id); }); }); } return response()->json($query->get()); } public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'investment_date' => 'required|date', 'investment_amount' => 'required|numeric', 'applicable_to_all_branches' => 'required|boolean', 'roi_type' => 'required|string|in:Percentage,Fixed Amount', 'roi_value' => 'nullable|numeric', 'roi_period' => 'nullable|string', 'branch_ids' => 'nullable|array', 'branch_ids.*' => 'exists:branches,id', 'security_proof_document' => 'nullable|file|mimes:pdf,png,jpg,jpeg|max:10240', ]); if ($request->hasFile('security_proof_document')) { $path = $request->file('security_proof_document')->store('investor_docs', 'public'); $validated['security_proof_document'] = $path; } $investor = Investor::create($validated); // Log to Accounts Account::create([ 'date' => Carbon::now()->toDateString(), 'time' => Carbon::now()->toTimeString(), 'branch_id' => $investor->applicable_to_all_branches ? null : $investor->branches->first()?->id, 'credit' => $investor->investment_amount, 'debit' => 0, 'type' => 'investor', 'accountable_id' => $investor->id, 'accountable_type' => Investor::class, 'description' => "Initial investment from {$investor->name}" ]); if (!$validated['applicable_to_all_branches'] && isset($validated['branch_ids'])) { $investor->branches()->attach($validated['branch_ids']); } return response()->json($investor->load('branches'), 201); } public function show($id) { $investor = Investor::with('branches')->findOrFail($id); $user = Auth::guard('web')->user() ?? Auth::guard('receptionist')->user(); if ($user && $user->isReceptionist()) { $isLinked = $investor->applicable_to_all_branches || $investor->branches->contains($user->branch_id); if (!$isLinked) { return response()->json(['message' => 'Unauthorized'], 403); } } return response()->json($investor); } public function update(Request $request, $id) { $investor = Investor::findOrFail($id); $user = Auth::guard('web')->user() ?? Auth::guard('receptionist')->user(); if ($user && $user->isReceptionist()) { $isLinked = $investor->applicable_to_all_branches || $investor->branches->contains($user->branch_id); if (!$isLinked) { return response()->json(['message' => 'Unauthorized'], 403); } } $validated = $request->validate([ 'name' => 'required|string|max:255', 'investment_date' => 'required|date', 'investment_amount' => 'required|numeric', 'applicable_to_all_branches' => 'required|boolean', 'roi_type' => 'required|string|in:Percentage,Fixed Amount', 'roi_value' => 'nullable|numeric', 'roi_period' => 'nullable|string', 'branch_ids' => 'nullable|array', 'branch_ids.*' => 'exists:branches,id', 'security_proof_document' => 'nullable|file|mimes:pdf,png,jpg,jpeg|max:10240', ]); if ($request->hasFile('security_proof_document')) { if ($investor->security_proof_document) { Storage::disk('public')->delete($investor->security_proof_document); } $path = $request->file('security_proof_document')->store('investor_docs', 'public'); $validated['security_proof_document'] = $path; } $investor->update($validated); if ($validated['applicable_to_all_branches']) { $investor->branches()->detach(); } elseif (isset($validated['branch_ids'])) { $investor->branches()->sync($validated['branch_ids']); } return response()->json($investor->load('branches')); } public function destroy($id) { $investor = Investor::findOrFail($id); $user = Auth::guard('web')->user() ?? Auth::guard('receptionist')->user(); if ($user && $user->isReceptionist()) { return response()->json(['message' => 'Unauthorized'], 403); } // Delete associated Account records (investments) Account::where('accountable_id', $investor->id) ->where('accountable_type', Investor::class) ->delete(); // Also delete associated payouts and their account entries $payouts = \App\Models\InvestorPayout::where('investor_id', $id)->get(); foreach ($payouts as $payout) { Account::where('accountable_id', $payout->id) ->where('accountable_type', \App\Models\InvestorPayout::class) ->delete(); $payout->delete(); } // Detach branches first to avoid foreign key issues $investor->branches()->detach(); if ($investor->security_proof_document) { Storage::disk('public')->delete($investor->security_proof_document); } $investor->delete(); return response()->json(['message' => 'Investor and associated financial records deleted successfully']); } public function getPayouts($id) { $investor = Investor::findOrFail($id); $user = Auth::guard('web')->user() ?? Auth::guard('receptionist')->user(); if ($user && $user->isReceptionist()) { $isLinked = $investor->applicable_to_all_branches || $investor->branches->contains($user->branch_id); if (!$isLinked) { return response()->json(['message' => 'Unauthorized'], 403); } } $payouts = \App\Models\InvestorPayout::where('investor_id', $id)->orderBy('payout_date', 'desc')->get(); return response()->json($payouts); } public function getROIPayoutStatus($id) { $investor = Investor::findOrFail($id); $user = Auth::guard('web')->user() ?? Auth::guard('receptionist')->user(); if ($user && $user->isReceptionist()) { $isLinked = $investor->applicable_to_all_branches || $investor->branches->contains($user->branch_id); if (!$isLinked) { return response()->json(['message' => 'Unauthorized'], 403); } } $investmentDate = Carbon::parse($investor->investment_date)->startOfMonth(); $currentMonth = Carbon::now()->startOfMonth(); // Fetch all payouts for this investor $payouts = \App\Models\InvestorPayout::where('investor_id', $id)->get(); $status = []; $tempMonth = $investmentDate->copy(); $periodMonths = 1; if ($investor->roi_period === 'Quarterly') $periodMonths = 3; if ($investor->roi_period === 'Yearly') $periodMonths = 12; $carryOver = 0; $canSettleFound = false; while ($tempMonth->lessThanOrEqualTo($currentMonth)) { $monthKey = $tempMonth->format('F Y'); $baseROI = round($investor->roi_type === 'Percentage' ? ($investor->investment_amount * ($investor->roi_value / 100)) : ($investor->roi_value ?? 0), 2); $targetAmount = $baseROI + $carryOver; // Check if there is a payout recorded for this specific month $monthPayouts = $payouts->filter(function($p) use ($monthKey) { return $p->payout_month === $monthKey; }); if ($monthPayouts->isNotEmpty()) { $paidForThisMonth = $monthPayouts->sum('amount'); $status[] = [ 'month' => $monthKey, 'status' => 'Paid', 'base_amount' => $baseROI, 'carry_from_previous' => $carryOver, 'target_amount' => $targetAmount, 'paid' => $paidForThisMonth, 'amount' => 0, 'can_settle' => false ]; // Carry forward the difference $carryOver = round($targetAmount - $paidForThisMonth, 2); } else { $canSettle = false; if (!$canSettleFound) { $canSettle = true; $canSettleFound = true; } $status[] = [ 'month' => $monthKey, 'status' => 'Pending', 'base_amount' => $baseROI, 'carry_from_previous' => $carryOver, 'target_amount' => $targetAmount, 'paid' => 0, 'amount' => $targetAmount, 'can_settle' => $canSettle ]; // No carry over update here, it stays until this month is settled // Actually, if we don't settle Jan, Feb's target should eventually include Jan's? // The user says "Older months must be settled first", so we don't need to accumulate carry automatically // across pending months, because you can't skip. } $tempMonth->addMonths($periodMonths); } return response()->json(array_reverse($status)); } public function settleROIPayout(Request $request, $id) { $investor = Investor::findOrFail($id); $user = Auth::guard('web')->user() ?? Auth::guard('receptionist')->user(); if ($user && $user->isReceptionist()) { $isLinked = $investor->applicable_to_all_branches || $investor->branches->contains($user->branch_id); if (!$isLinked) { return response()->json(['message' => 'Unauthorized'], 403); } } $validated = $request->validate([ 'payout_month' => 'required|string', 'amount' => 'required|numeric', 'payout_date' => 'required|date', 'payment_method' => 'nullable|string', 'remarks' => 'nullable|string' ]); $payout = \App\Models\InvestorPayout::create(array_merge($validated, [ 'investor_id' => $id, 'status' => 'Paid' ])); // Log to Accounts Account::create([ 'date' => $validated['payout_date'], 'time' => Carbon::now()->toTimeString(), 'branch_id' => $investor->applicable_to_all_branches ? null : $investor->branches->first()?->id, 'credit' => 0, 'debit' => $validated['amount'], 'type' => 'payout', 'accountable_id' => $payout->id, 'accountable_type' => \App\Models\InvestorPayout::class, 'description' => "ROI Payout for {$investor->name} - {$validated['payout_month']}" ]); // Log to Expenses $roiCategory = \App\Models\ExpenseCategory::where('name', 'ROI Payout')->first(); \App\Models\Expense::create([ 'date' => $validated['payout_date'], 'branch_id' => $investor->applicable_to_all_branches ? null : $investor->branches->first()?->id, 'expense_category_id' => $roiCategory ? $roiCategory->id : 1, 'expense_type' => 'Account', 'amount' => $validated['amount'], 'remarks' => "ROI Payout to {$investor->name} for {$validated['payout_month']}" ]); return response()->json(['message' => 'ROI settled successfully', 'payout' => $payout]); } public function getAllPendingROIs(Request $request) { $user = Auth::guard('web')->user() ?? Auth::guard('receptionist')->user(); $branchId = $user && $user->isReceptionist() ? $user->branch_id : $request->query('branch_id'); $query = Investor::with('branches'); if ($branchId) { $query->where(function($q) use ($branchId) { $q->where('applicable_to_all_branches', true) ->orWhereHas('branches', function($bq) use ($branchId) { $bq->where('branches.id', $branchId); }); }); } $investors = $query->get(); $pending = []; $currentMonth = Carbon::now()->startOfMonth(); foreach ($investors as $investor) { $investmentDate = Carbon::parse($investor->investment_date)->startOfMonth(); $payouts = \App\Models\InvestorPayout::where('investor_id', $investor->id)->get(); $tempMonth = $investmentDate->copy(); $periodMonths = 1; if ($investor->roi_period === 'Quarterly') $periodMonths = 3; if ($investor->roi_period === 'Yearly') $periodMonths = 12; $investorPendingMonths = []; $carryOver = 0; while ($tempMonth->lessThanOrEqualTo($currentMonth)) { $monthKey = $tempMonth->format('F Y'); $baseROI = round($investor->roi_type === 'Percentage' ? ($investor->investment_amount * ($investor->roi_value / 100)) : ($investor->roi_value ?? 0), 2); $targetAmount = $baseROI + $carryOver; $monthPayouts = $payouts->filter(function($p) use ($monthKey) { return $p->payout_month === $monthKey; }); if ($monthPayouts->isNotEmpty()) { $paidForThisMonth = $monthPayouts->sum('amount'); $carryOver = round($targetAmount - $paidForThisMonth, 2); } else { $investorPendingMonths[] = [ 'payout_month' => $monthKey, 'base_amount' => $baseROI, 'carry_from_previous' => $carryOver, 'amount' => $targetAmount ]; // If a month is missing, we stop accumulating carry for the global view // until that month is settled, to avoid confusing numbers. // Or should we? Let's stay consistent with getROIPayoutStatus. $carryOver = 0; } $tempMonth->addMonths($periodMonths); } if (!empty($investorPendingMonths)) { $totalPending = array_sum(array_column($investorPendingMonths, 'amount')); $pending[] = [ 'investor_id' => $investor->id, 'investor_name' => $investor->name, 'investment_amount' => $investor->investment_amount, 'roi_percentage' => $investor->roi_value, 'roi_type' => $investor->roi_type, 'roi_period' => $investor->roi_period, 'pending_count' => count($investorPendingMonths), 'total_pending' => $totalPending, 'pending_months' => $investorPendingMonths ]; } } return response()->json($pending); } public function storePayout(Request $request, $id) { // ... (keep existing storePayout or just use settleROI if we want unity) // I'll keep it for now but settleROI is more complete with accounts/expenses return $this->settleROIPayout($request, $id); } }