';
}
private static function field(string $name,string $label,string $type='text',string $value='',int $max=128): string {
$id=$name.'_'.bin2hex(random_bytes(3));
return '';
}
public function help(): void {
$this->displayContent('
Using your club workspace
Open Clubs and dues to view clubs assigned to your account. Club rosters and your season dues are available inside each club.
Your Super System Administrator creates clubs and assigns club administrators. Club administrators invite members, add seasons, record dues and payments, and answer tickets only in their clubs. Members can view club information and their own dues, and submit tickets. Accept invitations through Invitations after verifying your email.
');
}
public function home(): void {
$service=new RosterService();$html='
';
if($p['reversal_serial'])$html.='Reversed: '.self::h($p['reversal_reason'].' by '.$p['reversal_actor'].' on '.$p['reversed_at']);
elseif($admin&&$active)$html.='Reverse incorrect payment
This preserves the original entry and restores the amount owed. It does not send a refund.
'.self::h($a['member_name']).': $'.number_format($a['amount_cents']/100,2).' adjustment; corrected total $'.number_format($a['corrected_amount_cents']/100,2).' — '.self::h($a['reason'].' ('.$a['actor'].', '.$a['created_at'].')').'
';
return $html.'
'.BoundedList::navigation('adjustments');
}
public function createClub(): void {
(new TenantAccess())->requireSuper();$service=new RosterService();
if(($_SERVER['REQUEST_METHOD']??'GET')==='POST'){
$this->requireCsrf();$club=$service->saveClub(0,$_POST['club_name']??'');
$_SESSION['roster_notice']='Club created.';header('Location: main.php?Xaction=Roster.workspace&club_serial='.$club,true,303);return;
}
$form=str_replace('value="Roster.workspace"','value="Roster.createClub"',$this->form('club'));
$this->displayContent('
Create club
Create a separate club, then assign its administrator from the club roster.
'.$form.self::field('club_name','Club name').'Back to clubs');
}
public function workspace(): void {
$service=new RosterService();if(!$service->ready()){$this->home();return;}
$clubRaw=$_POST['club_serial']??$_GET['club_serial']??'';$club=($clubRaw===''||$clubRaw==='0')?0:MaintenanceService::id($clubRaw);
if(($_SERVER['REQUEST_METHOD']??'GET')==='POST' && ($_POST['step']??'')!==''){
$service->requireAdministrator();$this->requireCsrf();
switch($_POST['step']){
case 'club':$club=$service->saveClub($club,$_POST['club_name']??'',$_POST['club_status']??'active');break;
case 'member':$service->saveMember($club,empty($_POST['membership_serial'])?0:MaintenanceService::id($_POST['membership_serial']),$_POST['identifier']??'',$_POST['membership_status']??'active',$_POST['joined_on']??'',$_POST['membership_role']??'member');break;
case 'invite':$result=(new ClubInvitations())->invite($club,$_POST['invite_email']??'',$_POST['membership_role']??'member');$_SESSION['club_notice']=$result['message'].' '.$result['warning'];break;
case 'revokeInvitation':(new ClubInvitations())->revoke($club,MaintenanceService::id($_POST['invitation_serial']??null));$_SESSION['club_notice']='Invitation revoked.';break;
case 'season':$service->addSeason($club,$_POST['season_name']??'',$_POST['starts_on']??'',$_POST['ends_on']??'');break;
case 'charge':$service->charge($club,MaintenanceService::id($_POST['membership_serial']??null),MaintenanceService::id($_POST['season_serial']??null),$_POST['amount']??'');break;
case 'payment':$service->payment($club,MaintenanceService::id($_POST['due_serial']??null),$_POST['amount']??'',$_POST['paid_on']??'',$_POST['request_key']??'',$_POST['payment_method']??'unspecified',$_POST['payment_reference']??'');break;
case 'adjust':(new LedgerService())->adjust($club,MaintenanceService::id($_POST['due_serial']??null),$_POST['corrected_amount']??'',$_POST['reason']??'',$_POST['request_key']??'');break;
case 'reverse':(new LedgerService())->reverse($club,MaintenanceService::id($_POST['payment_serial']??null),$_POST['reason']??'',$_POST['request_key']??'');break;
default:throw new AppException('Unknown club action.');
}
$_SESSION['roster_notice']='Changes saved.';
$redirect='main.php?Xaction=Roster.workspace&club_serial='.$club;
if(($_SESSION[self::AJAX]??false)&&($_SERVER['HTTP_X_HCR_MODAL']??'')==='1'){
header('Content-Type: application/json');echo json_encode(['ok'=>true,'redirect'=>$redirect]);return;
}
header('Location: '.$redirect,true,303);exit;
}
$clubs=$service->clubs(BoundedList::page('clubs'));
if(!$club && $clubs)$club=(int)$clubs[0]['club_serial'];
if(!$club){$this->displayContent('
Clubs and memberships
No clubs are available. Accept an invitation to join your club.
'.(($super||$member['membership_role']!=='administrator')?$this->form('member',$club).''.($super?'':'').'':'Club administrator (managed by Super System Administrator)').'
The recipient signs up or signs in using this email, verifies it, and accepts under Invitations. This does not change their login or any other club membership.
';
foreach((new ClubInvitations())->forClub($club,BoundedList::page('invitations')) as $invite){$html.='