Files
huntingclubroster/classes/RosterReports.php
T

53 lines
6.0 KiB
PHP
Raw Normal View History

<?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
class RosterReports extends Base {
private function exportBudget(int $club,bool $financial=false): void {
(new TenantAccess())->requireManager($club);
(new ClubResources())->consume($club,'export');
$db=$this->connect();
if($financial){
BoundedList::exportGuard($db,'SELECT (SELECT COUNT(*) FROM club_dues WHERE club_serial=?)+(SELECT COUNT(*) FROM club_payments p JOIN club_dues d ON d.due_serial=p.due_serial WHERE d.club_serial=?)+(SELECT COUNT(*) FROM club_due_adjustments a JOIN club_dues d ON d.due_serial=a.due_serial WHERE d.club_serial=?)',[$club,$club,$club]);
}else BoundedList::exportGuard($db,'SELECT COUNT(*) FROM club_memberships WHERE club_serial=?',[$club]);
BoundedList::$exporting=true;
}
public function ledger(): void {
$service=new RosterService();$service->requireAdministrator();$club=MaintenanceService::id($_POST['club_serial']??null);(new TenantAccess())->requireManager($club);$name=$service->club($club)['club_name'];
$this->exportBudget($club,true);$history=(new LedgerService())->history($club);header('Content-Type: text/csv; charset=UTF-8');header('Content-Disposition: attachment; filename="club-financial-history.csv"');$file=fopen('php://output','w');
$write=static function(array $row)use($file){fputcsv($file,array_map(static fn($v)=>self::csvText((string)$v),$row),',','"','');};
$write(['Record type','Club','Record ID','Dues ID','Member','Amount cents (USD)','Date','Method','Reference','Recorded by','Status / reason']);
foreach($service->dues($club) as $due)$write(['dues_total',$name,$due['due_serial'],$due['due_serial'],$due['user_first_name'].' '.$due['user_last_name'],$due['amount_cents'],'','','',$due['recorded_by']??'Historical','Paid cents '.$due['paid_cents'].'; balance cents '.($due['amount_cents']-$due['paid_cents'])]);
foreach($history['payments'] as $p)$write(['payment',$name,$p['payment_serial'],$p['due_serial'],$p['member_name'],$p['amount_cents'],$p['paid_on'],$p['payment_method'],$p['payment_reference'],$p['actor']??'Historical',$p['reversal_serial']?'Reversed: '.$p['reversal_reason'].' by '.$p['reversal_actor'].' on '.$p['reversed_at']:'Received']);
foreach($history['adjustments'] as $a)$write(['dues_adjustment',$name,$a['adjustment_serial'],$a['due_serial'],$a['member_name'],$a['amount_cents'],$a['created_at'],'','',$a['actor'],$a['reason']]);
fclose($file);exit;
}
private function rows(): array {
$service=new RosterService();$service->requireAdministrator();$id=MaintenanceService::id($_POST['club_serial']??null);(new TenantAccess())->requireManager($id);$club=$service->club($id);$this->exportBudget($id);
$rows=[['Club','Username','First name','Last name','Email','Membership','Joined on']];
foreach($service->members($id) as $member)$rows[]=[$club['club_name'],$member['user_name'],$member['user_first_name'],$member['user_last_name'],$member['user_email'],$member['membership_status'],$member['joined_on']];
return $rows;
}
public static function csvText(string $value): string {return preg_match('/\A[\s]*[=+@\-]/u',$value)||preg_match('/\A[\t\r\n]/',$value)?"'".$value:$value;}
public function csv(): void {
$rows=$this->rows();header('Content-Type: text/csv; charset=UTF-8');header('Content-Disposition: attachment; filename="club-roster.csv"');$file=fopen('php://output','w');
foreach($rows as $row)fputcsv($file,array_map(static fn($cell)=>self::csvText((string)$cell),$row),',','"','');fclose($file);exit;
}
public function excel(): void {
$rows=$this->rows();$book=new Spreadsheet();$sheet=$book->getActiveSheet();$sheet->setTitle('Club roster');
foreach($rows as $r=>$row)foreach($row as $c=>$cell)$sheet->setCellValueExplicit([$c+1,$r+1],(string)$cell,DataType::TYPE_STRING);
$sheet->getStyle('A1:G1')->getFont()->setBold(true);$sheet->freezePane('A2');foreach(range('A','G') as $column)$sheet->getColumnDimension($column)->setAutoSize(true);
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');header('Content-Disposition: attachment; filename="club-roster.xlsx"');(new Xlsx($book))->save('php://output');exit;
}
public function pdf(): void {
$rows=$this->rows();$pdf=new TCPDF('L');$pdf->SetCreator(self::APPLICATION_NAME);$pdf->SetTitle('Club roster');$pdf->setPrintHeader(false);$pdf->setPrintFooter(false);$pdf->AddPage();$pdf->SetFont('helvetica','',9);
$html='<h1>Club roster</h1><table border="1" cellpadding="4">';foreach($rows as $r=>$row){$html.='<tr>';foreach($row as $cell)$html.=($r===0?'<th>':'<td>').htmlspecialchars((string)$cell,ENT_QUOTES,'UTF-8').($r===0?'</th>':'</td>');$html.='</tr>';}$html.='</table>';$pdf->writeHTML($html);$pdf->Output('club-roster.pdf','D');exit;
}
public function catalog(): void {
$service=new RosterService();$service->requireAdministrator();$html='<div class="container-fluid py-3"><h1>Roster reports</h1>';
if(!$service->ready()){$this->displayContent($html.'<p>Install the roster migration before exporting reports.</p></div>');return;}
foreach($service->clubs(BoundedList::page('clubs'),true) as $club){if(!$service->manages((int)$club['club_serial']))continue;$html.='<section class="card mb-3"><div class="card-body"><h2 class="h4">'.htmlspecialchars($club['club_name'],ENT_QUOTES,'UTF-8').'</h2><div class="d-flex gap-2">';foreach(['csv'=>'CSV','excel'=>'Excel','pdf'=>'PDF'] as $method=>$label)$html.='<form method="post" action="main.php"><input type="hidden" name="action" value="RosterReports.'.$method.'"><input type="hidden" name="club_serial" value="'.(int)$club['club_serial'].'"><input type="hidden" name="csrf_token" value="'.$this->csrfToken().'"><button class="btn btn-primary">Download '.$label.'</button></form>';$html.='</div></div></section>';}
$this->displayContent($html.BoundedList::navigation('clubs','RosterReports.catalog').'</div>');
}
}