2026-10-20 06:45:30 -04:00
<? php
use PhpOffice\PhpSpreadsheet\Spreadsheet ;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx ;
use PhpOffice\PhpSpreadsheet\Cell\DataType ;
class RosterReports extends Base {
2026-10-21 06:53:45 -04:00
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 ;
}
2026-10-20 06:45:30 -04:00
public function ledger () : void {
2026-10-21 06:53:45 -04:00
$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' );
2026-10-20 06:45:30 -04:00
$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 {
2026-10-21 06:53:45 -04:00
$service = new RosterService (); $service -> requireAdministrator (); $id = MaintenanceService :: id ( $_POST [ 'club_serial' ] ?? null );( new TenantAccess ()) -> requireManager ( $id ); $club = $service -> club ( $id ); $this -> exportBudget ( $id );
2026-10-20 06:45:30 -04:00
$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 ;}
2026-10-21 06:53:45 -04:00
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>' );
2026-10-20 06:45:30 -04:00
}
}