335 lines
14 KiB
PHP
335 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Account;
|
|
use App\Models\Expense;
|
|
use App\Models\Staff;
|
|
use App\Models\Branch;
|
|
use App\Models\BranchDocument;
|
|
use Illuminate\Http\Request;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
class ReportController extends Controller
|
|
{
|
|
public function getProfitReport(Request $request)
|
|
{
|
|
$user = Auth::guard('web')->user() ?? Auth::guard('receptionist')->user();
|
|
if (!$user) return response()->json(['message' => 'Unauthenticated'], 401);
|
|
|
|
$branchId = $user->isReceptionist() ? $user->branch_id : $request->query('branch_id');
|
|
$startDate = $request->query('start_date');
|
|
$endDate = $request->query('end_date');
|
|
$page = $request->query('page', 1);
|
|
$perPage = $request->query('per_page', 10);
|
|
|
|
// Base Query from Account table (Ledger)
|
|
$query = Account::query()->with(['accountable', 'branch']);
|
|
|
|
if ($branchId) {
|
|
$query->where('branch_id', $branchId);
|
|
}
|
|
if ($startDate) {
|
|
$query->where('date', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$query->where('date', '<=', $endDate);
|
|
}
|
|
|
|
// Stats from the same filtered query
|
|
$totalCredits = (clone $query)->sum('credit');
|
|
$totalDebits = (clone $query)->sum('debit');
|
|
|
|
// Today's Stats (always based on today's date, but respecting branch filter)
|
|
$todayIncome = Account::where('date', Carbon::today()->toDateString())
|
|
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
|
|
->sum('credit');
|
|
|
|
// Monthly Stats (always based on current month, but respecting branch filter)
|
|
$monthlyExpense = Account::whereBetween('date', [
|
|
Carbon::now()->startOfMonth()->toDateString(),
|
|
Carbon::now()->endOfMonth()->toDateString()
|
|
])
|
|
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
|
|
->sum('debit');
|
|
|
|
// Fetch Paginated Transactions from the same filtered query
|
|
$allTransactions = $query->where(function($q) {
|
|
$q->where('credit', '>', 0)->orWhere('debit', '>', 0);
|
|
})
|
|
->orderBy('date', 'desc')
|
|
->orderBy('time', 'desc')
|
|
->orderBy('id', 'desc')
|
|
->get()
|
|
->map(function($a) {
|
|
$isAdjusted = false;
|
|
$originalAmount = $a->credit > 0 ? $a->credit : $a->debit;
|
|
$remarks = '';
|
|
|
|
if ($a->accountable_type === \App\Models\ProductSale::class && $a->accountable) {
|
|
$originalAmount = $a->accountable->subtotal_amount + $a->accountable->vat_amount;
|
|
$isAdjusted = abs($a->credit - $originalAmount) > 0.01;
|
|
$remarks = $a->accountable->remarks;
|
|
} elseif ($a->accountable_type === \App\Models\Collection::class && $a->accountable) {
|
|
$originalAmount = $a->accountable->items()->sum('subtotal');
|
|
$isAdjusted = $originalAmount > 0 && abs($a->credit - $originalAmount) > 0.01;
|
|
$remarks = $a->accountable->remarks;
|
|
}
|
|
|
|
return [
|
|
'id' => $a->id,
|
|
'date' => $a->date,
|
|
'time' => $a->time || '00:00:00',
|
|
'type' => $a->credit > 0 ? 'Income' : 'Expense',
|
|
'category' => $a->type,
|
|
'description' => $a->description,
|
|
'amount' => $a->credit > 0 ? $a->credit : $a->debit,
|
|
'branch' => $a->branch->name ?? 'N/A',
|
|
'is_adjusted' => $isAdjusted,
|
|
'original_amount' => $originalAmount,
|
|
'remarks' => $remarks
|
|
];
|
|
});
|
|
|
|
$totalTransactions = $allTransactions->count();
|
|
$paginatedTransactions = $allTransactions->forPage($page, $perPage)->values();
|
|
|
|
$lowStockCount = \App\Models\Product::query();
|
|
if ($branchId) {
|
|
$lowStockCount->where('branch_id', $branchId);
|
|
}
|
|
$lowStockCount = $lowStockCount->whereRaw('current_stock <= reorder_level')->count();
|
|
|
|
// Calculate 6-month trend for Dashboard table/chart
|
|
$trend = [];
|
|
for ($i = 5; $i >= 0; $i--) {
|
|
$monthStart = Carbon::now()->subMonths($i)->startOfMonth();
|
|
$monthEnd = Carbon::now()->subMonths($i)->endOfMonth();
|
|
|
|
$monthIncome = Account::query()
|
|
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
|
|
->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()])
|
|
->sum('credit');
|
|
|
|
$monthExpense = Account::query()
|
|
->when($branchId, fn($q) => $q->where('branch_id', $branchId))
|
|
->whereBetween('date', [$monthStart->toDateString(), $monthEnd->toDateString()])
|
|
->sum('debit');
|
|
|
|
$trend[] = [
|
|
'month' => $monthStart->format('M'),
|
|
'income' => round($monthIncome, 2),
|
|
'expense' => round($monthExpense, 2),
|
|
'profit' => round($monthIncome - $monthExpense, 2),
|
|
'status' => ($monthIncome - $monthExpense) >= 0 ? 'Profit' : 'Loss'
|
|
];
|
|
}
|
|
|
|
return response()->json([
|
|
'total_income' => $totalCredits,
|
|
'total_expense' => $totalDebits,
|
|
'today_income' => $todayIncome,
|
|
'monthly_expense' => $monthlyExpense,
|
|
'net_profit' => $totalCredits - $totalDebits,
|
|
'low_stock_count' => $lowStockCount,
|
|
'transactions' => $paginatedTransactions,
|
|
'pagination' => [
|
|
'total' => $totalTransactions,
|
|
'per_page' => (int)$perPage,
|
|
'current_page' => (int)$page,
|
|
'last_page' => ceil($totalTransactions / $perPage)
|
|
],
|
|
'trend' => $trend
|
|
]);
|
|
}
|
|
|
|
public function getExpiryReminders(Request $request)
|
|
{
|
|
$today = Carbon::today();
|
|
$user = Auth::guard('web')->user() ?? Auth::guard('receptionist')->user();
|
|
if (!$user) return response()->json(['message' => 'Unauthenticated'], 401);
|
|
|
|
$branchId = $user->isReceptionist() ? $user->branch_id : $request->query('branch_id');
|
|
$startDate = $request->query('start_date');
|
|
$endDate = $request->query('end_date');
|
|
|
|
$staffDocsQuery = \App\Models\StaffDocument::with('staff.branch')
|
|
->whereNotNull('expiry_date');
|
|
|
|
if ($branchId) {
|
|
$staffDocsQuery->whereHas('staff', function($q) use ($branchId) {
|
|
$q->where('branch_id', $branchId);
|
|
});
|
|
}
|
|
|
|
if ($startDate) {
|
|
$staffDocsQuery->where('expiry_date', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$staffDocsQuery->where('expiry_date', '<=', $endDate);
|
|
}
|
|
|
|
$staffDocs = $staffDocsQuery->get()
|
|
->filter(function($doc) use ($today) {
|
|
$expiryDate = Carbon::parse($doc->expiry_date)->startOfDay();
|
|
$reminderDays = (int)($doc->reminder_days ?? 30);
|
|
return $today->startOfDay()->greaterThanOrEqualTo($expiryDate->copy()->subDays($reminderDays));
|
|
})
|
|
->map(function($doc) use ($today) {
|
|
$expiryDate = Carbon::parse($doc->expiry_date);
|
|
$reminderDays = (int)($doc->reminder_days ?? 30);
|
|
$reminderStartedOn = $expiryDate->copy()->subDays($reminderDays);
|
|
|
|
return [
|
|
'type' => 'Staff',
|
|
'entity_name' => $doc->staff->full_name ?? 'N/A',
|
|
'branch_name' => $doc->staff->branch->name ?? 'Global',
|
|
'document_name' => $doc->name,
|
|
'document_number' => $doc->document_number,
|
|
'reminder_started_on' => $reminderStartedOn->format('d/m/Y'),
|
|
'expiry_date' => $doc->expiry_date,
|
|
'days_left' => (int)$today->diffInDays($expiryDate, false)
|
|
];
|
|
});
|
|
|
|
// Branch Document Reminders
|
|
$branchDocsQuery = BranchDocument::with('branch')
|
|
->whereNotNull('expiry_date');
|
|
|
|
if ($branchId) {
|
|
$branchDocsQuery->where('branch_id', $branchId);
|
|
}
|
|
|
|
if ($startDate) {
|
|
$branchDocsQuery->where('expiry_date', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$branchDocsQuery->where('expiry_date', '<=', $endDate);
|
|
}
|
|
|
|
$branchReminders = $branchDocsQuery->get()
|
|
->filter(function($doc) use ($today) {
|
|
$expiryDate = Carbon::parse($doc->expiry_date);
|
|
$reminderDays = (int)($doc->reminder_days ?? 30);
|
|
return $today->greaterThanOrEqualTo($expiryDate->copy()->subDays($reminderDays));
|
|
})
|
|
->map(function($doc) use ($today) {
|
|
$expiryDate = Carbon::parse($doc->expiry_date);
|
|
$reminderDays = (int)($doc->reminder_days ?? 30);
|
|
$reminderStartedOn = $expiryDate->copy()->subDays($reminderDays);
|
|
|
|
return [
|
|
'type' => 'Branch',
|
|
'entity_name' => $doc->branch->name ?? 'N/A',
|
|
'branch_name' => $doc->branch->name ?? 'Global',
|
|
'document_name' => $doc->name,
|
|
'document_number' => $doc->document_number,
|
|
'reminder_started_on' => $reminderStartedOn->format('d/m/Y'),
|
|
'expiry_date' => $doc->expiry_date,
|
|
'days_left' => (int)$today->diffInDays($expiryDate, false)
|
|
];
|
|
});
|
|
|
|
return response()->json([
|
|
'reminders' => $staffDocs->concat($branchReminders)->sortBy('expiry_date')->values()
|
|
]);
|
|
}
|
|
|
|
public function getInvestmentReport(Request $request)
|
|
{
|
|
$user = Auth::guard('web')->user() ?? Auth::guard('receptionist')->user();
|
|
if (!$user) return response()->json(['message' => 'Unauthenticated'], 401);
|
|
|
|
$branchId = $user->isReceptionist() ? $user->branch_id : $request->query('branch_id');
|
|
$startDate = $request->query('start_date');
|
|
$endDate = $request->query('end_date');
|
|
|
|
$query = \App\Models\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);
|
|
});
|
|
});
|
|
}
|
|
|
|
if ($startDate) {
|
|
$query->where('investment_date', '>=', $startDate);
|
|
}
|
|
if ($endDate) {
|
|
$query->where('investment_date', '<=', $endDate);
|
|
}
|
|
|
|
$investors = $query->get();
|
|
$reportData = [];
|
|
$currentMonth = Carbon::now()->startOfMonth();
|
|
|
|
$totalInvested = 0;
|
|
$totalROIReturned = 0;
|
|
|
|
foreach ($investors as $investor) {
|
|
$investmentDate = Carbon::parse($investor->investment_date)->startOfMonth();
|
|
|
|
// Get all actual payouts made to this investor
|
|
$totalPaid = \App\Models\InvestorPayout::where('investor_id', $investor->id)->sum('amount');
|
|
$returnsEarned = $totalPaid;
|
|
|
|
$tempMonth = $investmentDate->copy();
|
|
$periodMonths = 1;
|
|
if ($investor->roi_period === 'Quarterly') $periodMonths = 3;
|
|
if ($investor->roi_period === 'Yearly') $periodMonths = 12;
|
|
|
|
$totalPending = 0;
|
|
$carryOver = 0;
|
|
|
|
// Calculate how much should have been paid up to now
|
|
while ($tempMonth->lessThan($currentMonth)) {
|
|
$monthKey = $tempMonth->format('F Y');
|
|
|
|
$baseAmount = round($investor->roi_type === 'Percentage'
|
|
? ($investor->investment_amount * ($investor->roi_value / 100))
|
|
: ($investor->roi_value ?? 0), 2);
|
|
|
|
$expected = $baseAmount + $carryOver;
|
|
|
|
if ($totalPaid >= $expected && $expected > 0) {
|
|
$totalPaid -= $expected;
|
|
$carryOver = 0;
|
|
} else {
|
|
$paidTowardsThis = $totalPaid;
|
|
$remains = $expected - $paidTowardsThis;
|
|
$totalPaid = 0;
|
|
$carryOver = $remains;
|
|
}
|
|
$tempMonth->addMonths($periodMonths);
|
|
}
|
|
|
|
$totalPending = $carryOver; // The leftover carryover is the net pending amount
|
|
$totalInvested += $investor->investment_amount;
|
|
$totalROIReturned += $returnsEarned;
|
|
|
|
$reportData[] = [
|
|
'investor_id' => $investor->id,
|
|
'investor_name' => $investor->name,
|
|
'investment_date' => $investor->investment_date,
|
|
'investment_amount' => $investor->investment_amount,
|
|
'roi_type' => $investor->roi_type,
|
|
'roi_value' => $investor->roi_value,
|
|
'roi_period' => $investor->roi_period,
|
|
'returns_earned' => $returnsEarned,
|
|
'total_pending' => $totalPending
|
|
];
|
|
}
|
|
|
|
return response()->json([
|
|
'total_invested' => $totalInvested,
|
|
'total_roi_returned' => $totalROIReturned,
|
|
'investors' => collect($reportData)->sortByDesc('investment_date')->values()
|
|
]);
|
|
}
|
|
}
|