233 lines
8.6 KiB
PHP
233 lines
8.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Reimbursements;
|
|
|
|
use App\Models\ReimbursementBatch;
|
|
use Illuminate\Support\Facades\DB;
|
|
use RuntimeException;
|
|
|
|
class ReimbursementExportService
|
|
{
|
|
public function __construct(private ReimbursementRecipientService $recipients) {}
|
|
|
|
public function buildUnderProcessingCsv(): array
|
|
{
|
|
$builder = DB::table('expenses')
|
|
->select('expenses.*', 'u.firstname as purchaser_firstname', 'u.lastname as purchaser_lastname')
|
|
->leftJoin('users as u', 'u.id', '=', 'expenses.purchased_by')
|
|
->where('expenses.status', 'approved')
|
|
->whereNull('expenses.reimbursement_id')
|
|
->where('expenses.category', '!=', 'Donation')
|
|
->orderByDesc('expenses.created_at');
|
|
|
|
$records = $builder->get();
|
|
$grouped = [];
|
|
|
|
foreach ($records as $row) {
|
|
$personId = (int) ($row->purchased_by ?? 0);
|
|
$name = trim(($row->purchaser_firstname ?? '').' '.($row->purchaser_lastname ?? ''));
|
|
if ($name === '') {
|
|
$name = $row->purchaser_firstname ?? $row->purchaser_lastname ?? 'Unknown';
|
|
}
|
|
$key = $personId > 0 ? 'id_'.$personId : 'name_'.strtolower($name);
|
|
if (! isset($grouped[$key])) {
|
|
$grouped[$key] = [
|
|
'name' => $name ?: 'Unknown',
|
|
'total' => 0.0,
|
|
'vendors' => [],
|
|
'descriptions' => [],
|
|
];
|
|
}
|
|
|
|
$amount = (float) ($row->amount ?? 0);
|
|
$grouped[$key]['total'] += $amount;
|
|
|
|
$vendor = trim((string) ($row->retailor ?? ''));
|
|
if ($vendor !== '' && ! in_array($vendor, $grouped[$key]['vendors'], true)) {
|
|
$grouped[$key]['vendors'][] = $vendor;
|
|
}
|
|
|
|
$desc = trim((string) ($row->description ?? ''));
|
|
if ($desc !== '' && ! in_array($desc, $grouped[$key]['descriptions'], true)) {
|
|
$grouped[$key]['descriptions'][] = $desc;
|
|
}
|
|
}
|
|
|
|
$rows = [];
|
|
$rows[] = ['Person', 'Total Amount', 'Vendors', 'Descriptions'];
|
|
foreach ($grouped as $entry) {
|
|
$rows[] = [
|
|
$entry['name'],
|
|
number_format($entry['total'], 2, '.', ''),
|
|
implode('; ', $entry['vendors']),
|
|
implode('; ', $entry['descriptions']),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'filename' => 'under_processing_expenses.csv',
|
|
'rows' => $rows,
|
|
];
|
|
}
|
|
|
|
public function buildProcessedCsv(array $filters): array
|
|
{
|
|
$builder = DB::table('reimbursements')
|
|
->select('reimbursements.*', 'u.firstname as reimb_firstname', 'u.lastname as reimb_lastname')
|
|
->leftJoin('users as u', 'u.id', '=', 'reimbursements.reimbursed_to');
|
|
|
|
if (! empty($filters['semester'])) {
|
|
$builder->where('reimbursements.semester', $filters['semester']);
|
|
}
|
|
if (! empty($filters['status'])) {
|
|
$builder->where('reimbursements.status', $filters['status']);
|
|
}
|
|
if (! empty($filters['user_id'])) {
|
|
$builder->where('reimbursements.reimbursed_to', $filters['user_id']);
|
|
}
|
|
if (! empty($filters['school_year'])) {
|
|
$builder->where('reimbursements.school_year', $filters['school_year']);
|
|
}
|
|
|
|
$records = $builder->orderByDesc('reimbursements.created_at')->get();
|
|
$rows = [];
|
|
$rows[] = ['Amount', 'Reimbursed To', 'Status', 'Method', 'Check #', 'School Year', 'Semester', 'Date'];
|
|
|
|
foreach ($records as $row) {
|
|
$hasName = trim(($row->reimb_firstname ?? '').' '.($row->reimb_lastname ?? '')) !== '';
|
|
if (! $hasName) {
|
|
$label = $this->recipients->specialRecipientLabel($row->reimbursed_to ?? null);
|
|
if ($label !== null) {
|
|
$row->reimb_firstname = $label;
|
|
$row->reimb_lastname = '';
|
|
}
|
|
}
|
|
|
|
$rows[] = [
|
|
$row->amount,
|
|
trim(($row->reimb_firstname ?? '').' '.($row->reimb_lastname ?? '')),
|
|
$row->status,
|
|
$row->reimbursement_method ?? '-',
|
|
$row->check_number ?? '-',
|
|
$row->school_year ?? '-',
|
|
$row->semester ?? '-',
|
|
$row->created_at,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'filename' => 'reimbursements_export.csv',
|
|
'rows' => $rows,
|
|
];
|
|
}
|
|
|
|
public function buildBatchCsv(int $batchId): array
|
|
{
|
|
$batch = ReimbursementBatch::query()->find($batchId);
|
|
if (! $batch) {
|
|
throw new RuntimeException('Batch not found.');
|
|
}
|
|
|
|
$batchLabel = trim((string) ($batch->title ?? '')) ?: ('Batch #'.$batchId);
|
|
$receiptRows = $this->fetchBatchReceiptRows($batchId);
|
|
|
|
$rows = [];
|
|
$this->writeBatchCsvMetadata($rows, $batchLabel, $batch->toArray());
|
|
$this->writeBatchCsvSummary($rows, $receiptRows, $this->formatCsvDate($batch->closed_at ?? null));
|
|
|
|
return [
|
|
'filename' => sprintf('batch_%d_items.csv', $batchId),
|
|
'rows' => $rows,
|
|
];
|
|
}
|
|
|
|
private function fetchBatchReceiptRows(int $batchId, array $receiptIds = []): array
|
|
{
|
|
$builder = DB::table('reimbursement_batch_items as bi')
|
|
->select('e.id as expense_id', 'e.retailor', 'e.amount', 'e.description', 'e.receipt_path as receipt_filename', 'e.date_of_purchase as purchase_date', 'u.firstname as purchaser_firstname', 'u.lastname as purchaser_lastname')
|
|
->join('expenses as e', 'e.id', '=', 'bi.expense_id')
|
|
->leftJoin('users as u', 'u.id', '=', 'e.purchased_by')
|
|
->where('bi.batch_id', $batchId);
|
|
|
|
if (! empty($receiptIds)) {
|
|
$builder->whereIn('e.id', $receiptIds);
|
|
}
|
|
|
|
$rows = $builder->get()->toArray();
|
|
|
|
return array_values(array_filter($rows, static function ($row) {
|
|
return ! empty($row->receipt_filename);
|
|
}));
|
|
}
|
|
|
|
private function writeBatchCsvMetadata(array &$rows, string $batchLabel, array $batch): void
|
|
{
|
|
$closedAtRaw = $batch['closed_at'] ?? null;
|
|
$rows[] = ['Batch Title', $batchLabel];
|
|
$rows[] = ['School Year', $batch['school_year'] ?? '', 'Semester', $batch['semester'] ?? '', 'Closed At', $this->formatCsvDate($closedAtRaw)];
|
|
$rows[] = [];
|
|
$rows[] = ['Amount', 'Credit', 'Paid', 'Balance', 'Date', 'By Who', 'Vendor', 'Description'];
|
|
}
|
|
|
|
private function writeBatchCsvSummary(array &$rows, array $receiptRows, string $totalsDate = ''): void
|
|
{
|
|
$receiptTotalsByRecipient = [];
|
|
foreach ($receiptRows as $row) {
|
|
$recipientName = trim(($row->purchaser_firstname ?? '').' '.($row->purchaser_lastname ?? ''));
|
|
if ($recipientName === '') {
|
|
$recipientName = 'Unknown';
|
|
}
|
|
$vendor = trim((string) ($row->retailor ?? ''));
|
|
if ($vendor === '') {
|
|
$vendor = 'Vendor N/A';
|
|
}
|
|
$dateText = $this->formatCsvDate($row->purchase_date ?? null);
|
|
$descriptionValue = trim((string) ($row->description ?? ''));
|
|
$descriptionParts = $descriptionValue !== '' ? [$descriptionValue] : ['Receipt'];
|
|
$amountValue = (float) ($row->amount ?? 0);
|
|
$receiptTotalsByRecipient[$recipientName] = ($receiptTotalsByRecipient[$recipientName] ?? 0.0) + $amountValue;
|
|
$rows[] = [
|
|
number_format($amountValue, 2, '.', ''),
|
|
'',
|
|
number_format($amountValue, 2, '.', ''),
|
|
'0.00',
|
|
$dateText,
|
|
$recipientName,
|
|
$vendor,
|
|
implode(' | ', $descriptionParts),
|
|
];
|
|
}
|
|
|
|
if (! empty($receiptTotalsByRecipient)) {
|
|
$rows[] = [];
|
|
$rows[] = ['Totals by Recipient', '', '', '', '', '', '', ''];
|
|
foreach ($receiptTotalsByRecipient as $recipient => $total) {
|
|
$formatted = number_format($total, 2, '.', '');
|
|
$rows[] = [
|
|
$formatted,
|
|
'',
|
|
$formatted,
|
|
'0.00',
|
|
$totalsDate,
|
|
$recipient,
|
|
'',
|
|
'',
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
private function formatCsvDate(?string $value): string
|
|
{
|
|
if (! $value) {
|
|
return '';
|
|
}
|
|
$timestamp = strtotime($value);
|
|
if ($timestamp === false) {
|
|
return '';
|
|
}
|
|
|
|
return date('m-d-Y', $timestamp);
|
|
}
|
|
}
|