| Server IP : 188.114.96.3 / Your IP : 216.73.216.143 Web Server : Apache System : Linux sv058.home.net.pl 6.8.2 husak_dirty_pages_fix #1 SMP PREEMPT_DYNAMIC Tue Apr 2 22:06:51 CEST 2024 x86_64 User : ( 3194769) PHP Version : 8.5.12 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /polana/includes/ |
Upload File : |
<?php
declare(strict_types=1);
function ensure_work_shifts_schema(): void
{
db()->exec(
"CREATE TABLE IF NOT EXISTS work_shifts (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(32) NOT NULL,
name VARCHAR(80) NOT NULL,
start_time TIME DEFAULT NULL,
end_time TIME DEFAULT NULL,
active TINYINT(1) NOT NULL DEFAULT 1,
sort_order INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_work_shift_code (code),
INDEX idx_work_shift_active_sort (active, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci"
);
db()->exec("UPDATE work_shifts SET active=0 WHERE code<>'all_day'");
$stmt = db()->prepare(
"INSERT INTO work_shifts (code,name,start_time,end_time,active,sort_order)
VALUES ('all_day','Cały dzień',NULL,NULL,1,10)
ON DUPLICATE KEY UPDATE name='Cały dzień',start_time=NULL,end_time=NULL,active=1,sort_order=10"
);
$stmt->execute();
}
function work_shifts(bool $activeOnly = true): array
{
ensure_work_shifts_schema();
$stmt = db()->query("SELECT * FROM work_shifts WHERE code='all_day' LIMIT 1");
$row = $stmt->fetch();
return $row ? [$row] : [];
}
function work_shift_by_code(string $code): ?array
{
ensure_work_shifts_schema();
if (trim($code) !== 'all_day') return null;
$stmt = db()->query("SELECT * FROM work_shifts WHERE code='all_day' LIMIT 1");
return $stmt->fetch() ?: null;
}
function work_shift_default_code(): string { return 'all_day'; }
function work_shift_label(string $code): string { return 'Cały dzień'; }
function work_shift_resolve(string $value, ?string $fallback = null): string { return 'all_day'; }
function work_shift_slug(string $value): string { return 'all_day'; }
function work_shift_save(?int $id, string $name, ?string $start, ?string $end, bool $active, int $sortOrder): array
{
ensure_work_shifts_schema();
return work_shift_by_code('all_day') ?: [];
}
function ensure_schedule_schema(): void
{
ensure_work_shifts_schema();
db()->exec(
"CREATE TABLE IF NOT EXISTS staff_schedule (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
work_date DATE NOT NULL,
user_id INT UNSIGNED NOT NULL,
shift_code VARCHAR(32) NOT NULL DEFAULT 'all_day',
start_time TIME DEFAULT NULL,
end_time TIME DEFAULT NULL,
created_by_user_id INT UNSIGNED DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_staff_schedule_day_user (work_date, user_id),
INDEX idx_staff_schedule_date (work_date),
INDEX idx_staff_schedule_date_shift (work_date, shift_code),
INDEX idx_staff_schedule_user (user_id, work_date),
CONSTRAINT fk_staff_schedule_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT fk_staff_schedule_created_by FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci"
);
$columns = db()->query('SHOW COLUMNS FROM staff_schedule')->fetchAll();
$names = array_map(static fn(array $row): string => (string)$row['Field'], $columns);
if (!in_array('shift_code',$names,true)) db()->exec("ALTER TABLE staff_schedule ADD COLUMN shift_code VARCHAR(32) NOT NULL DEFAULT 'all_day' AFTER user_id");
if (!in_array('start_time',$names,true)) db()->exec('ALTER TABLE staff_schedule ADD COLUMN start_time TIME DEFAULT NULL AFTER shift_code');
if (!in_array('end_time',$names,true)) db()->exec('ALTER TABLE staff_schedule ADD COLUMN end_time TIME DEFAULT NULL AFTER start_time');
if (!in_array('updated_at',$names,true)) db()->exec('ALTER TABLE staff_schedule ADD COLUMN updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at');
$indexes = db()->query('SHOW INDEX FROM staff_schedule')->fetchAll();
$indexNames = array_values(array_unique(array_column($indexes,'Key_name')));
if (!in_array('idx_staff_schedule_date_shift',$indexNames,true)) db()->exec('ALTER TABLE staff_schedule ADD INDEX idx_staff_schedule_date_shift (work_date, shift_code)');
}
function schedule_manager_roles(): array { return ['admin','editor','manager']; }
function schedule_staff_roles(): array { return ['waiter','kitchen']; }
function schedule_is_manager(?array $user = null): bool
{
$user ??= admin_user();
return $user && in_array((string)$user['role'],schedule_manager_roles(),true);
}
function require_schedule_access(): array
{
require_login();
$user=admin_user();
if(!$user || !in_array((string)$user['role'],array_merge(schedule_manager_roles(),schedule_staff_roles()),true)) {
http_response_code(403); exit('Brak dostępu do grafiku.');
}
return $user;
}
function schedule_staff(bool $activeOnly = true): array
{
ensure_schedule_schema();
$sql="SELECT id,username,name,role,active FROM users WHERE role IN ('waiter','kitchen')";
if($activeOnly) $sql.=' AND active=1';
$sql.=" ORDER BY FIELD(role,'waiter','kitchen'),COALESCE(name,username),username";
return db()->query($sql)->fetchAll();
}
function schedule_normalize_time(?string $value): ?string
{
$value=trim((string)$value);
if($value==='') return null;
if(!preg_match('/^(?:[01]\\d|2[0-3]):[0-5]\\d$/',$value)) throw new RuntimeException('Nieprawidłowa godzina: '.$value);
return $value.':00';
}
function schedule_time_label(?string $start, ?string $end): string
{
$start=$start?substr($start,0,5):''; $end=$end?substr($end,0,5):'';
if($start!==''&&$end!=='') return $start.'–'.$end;
if($start!=='') return 'od '.$start;
if($end!=='') return 'do '.$end;
return 'cały dzień';
}
function schedule_user_ids_for_date(string $date): array
{
ensure_schedule_schema();
$stmt=db()->prepare("SELECT s.user_id FROM staff_schedule s JOIN users u ON u.id=s.user_id WHERE s.work_date=? AND u.active=1 ORDER BY FIELD(u.role,'waiter','kitchen'),COALESCE(u.name,u.username),u.username");
$stmt->execute([$date]);
return array_map('intval',array_column($stmt->fetchAll(),'user_id'));
}
function schedule_user_ids_for_date_shift(string $date, string $shiftCode): array
{
return schedule_user_ids_for_date($date);
}
function schedule_entries_for_date(string $date, ?int $onlyUserId = null, ?string $shiftCode = null): array
{
ensure_schedule_schema();
$params=[$date]; $where='s.work_date=? AND u.active=1';
if($onlyUserId!==null){$where.=' AND s.user_id=?';$params[]=$onlyUserId;}
$stmt=db()->prepare("SELECT s.user_id,'all_day' AS shift_code,s.start_time,s.end_time,u.username,u.name,u.role,'Cały dzień' AS shift_name FROM staff_schedule s JOIN users u ON u.id=s.user_id WHERE $where ORDER BY FIELD(u.role,'waiter','kitchen'),COALESCE(u.name,u.username),u.username");
$stmt->execute($params); return $stmt->fetchAll();
}
function schedule_people_for_date(string $date): array { return schedule_entries_for_date($date); }
function schedule_month_map(string $month, ?int $onlyUserId = null): array
{
ensure_schedule_schema();
$from=$month.'-01'; $to=(new DateTimeImmutable($from))->modify('last day of this month')->format('Y-m-d');
$params=[$from,$to]; $where='s.work_date BETWEEN ? AND ?';
if($onlyUserId!==null){$where.=' AND s.user_id=?';$params[]=$onlyUserId;}
$stmt=db()->prepare("SELECT s.work_date,s.user_id,'all_day' AS shift_code,s.start_time,s.end_time,u.username,u.name,u.role,'Cały dzień' AS shift_name,10 AS shift_sort FROM staff_schedule s JOIN users u ON u.id=s.user_id WHERE $where ORDER BY s.work_date,FIELD(u.role,'waiter','kitchen'),COALESCE(u.name,u.username),u.username");
$stmt->execute($params); $map=[]; foreach($stmt->fetchAll() as $row)$map[$row['work_date']][]=$row; return $map;
}
function schedule_save_day(string $date, array $entries, int $actorUserId): void
{
ensure_schedule_schema();
$dateObj=DateTimeImmutable::createFromFormat('!Y-m-d',$date);
if(!$dateObj||$dateObj->format('Y-m-d')!==$date) throw new RuntimeException('Nieprawidłowa data.');
$normalized=[];
foreach($entries as $entry){
if(!is_array($entry))continue; $userId=(int)($entry['user_id']??0); if($userId<1)continue;
$normalized[$userId]=['user_id'=>$userId,'shift_code'=>'all_day','start_time'=>schedule_normalize_time($entry['start_time']??null),'end_time'=>schedule_normalize_time($entry['end_time']??null)];
}
if($normalized){
$ids=array_keys($normalized);$ph=implode(',',array_fill(0,count($ids),'?'));
$stmt=db()->prepare("SELECT id FROM users WHERE active=1 AND role IN ('waiter','kitchen') AND id IN ($ph)");$stmt->execute($ids);$valid=array_map('intval',array_column($stmt->fetchAll(),'id'));
foreach(array_keys($normalized) as $id)if(!in_array((int)$id,$valid,true))unset($normalized[$id]);
}
$pdo=db();$pdo->beginTransaction();
try{
$pdo->prepare('DELETE FROM staff_schedule WHERE work_date=?')->execute([$date]);
if($normalized){
$insert=$pdo->prepare('INSERT INTO staff_schedule (work_date,user_id,shift_code,start_time,end_time,created_by_user_id) VALUES (?,?,?,?,?,?)');
foreach($normalized as $e)$insert->execute([$date,$e['user_id'],'all_day',$e['start_time'],$e['end_time'],$actorUserId]);
}
$pdo->commit();
}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
}
function schedule_copy_day(string $sourceDate,string $targetDate,int $actorUserId): void
{
$entries=[];foreach(schedule_entries_for_date($sourceDate) as $row)$entries[]=['user_id'=>(int)$row['user_id'],'start_time'=>$row['start_time']?substr((string)$row['start_time'],0,5):'','end_time'=>$row['end_time']?substr((string)$row['end_time'],0,5):''];
schedule_save_day($targetDate,$entries,$actorUserId);
}
function schedule_copy_previous_week(string $dateInTargetWeek,int $actorUserId): void
{
$target=new DateTimeImmutable($dateInTargetWeek);$targetMonday=$target->modify('monday this week');$sourceMonday=$targetMonday->modify('-7 days');
for($i=0;$i<7;$i++)schedule_copy_day($sourceMonday->modify('+'.$i.' days')->format('Y-m-d'),$targetMonday->modify('+'.$i.' days')->format('Y-m-d'),$actorUserId);
}
function schedule_upcoming_for_user(int $userId,int $limit=8): array
{
ensure_schedule_schema();$limit=max(1,min(31,$limit));$stmt=db()->prepare("SELECT work_date FROM staff_schedule WHERE user_id=? AND work_date>=CURDATE() ORDER BY work_date ASC LIMIT $limit");$stmt->execute([$userId]);return array_column($stmt->fetchAll(),'work_date');
}
function schedule_upcoming_shifts_for_user(int $userId,int $limit=14): array
{
ensure_schedule_schema();$limit=max(1,min(62,$limit));$stmt=db()->prepare("SELECT work_date,'all_day' AS shift_code,start_time,end_time,'Cały dzień' AS shift_name FROM staff_schedule WHERE user_id=? AND work_date>=CURDATE() ORDER BY work_date ASC LIMIT $limit");$stmt->execute([$userId]);return $stmt->fetchAll();
}
function schedule_copy_week_to_next(string $dateInSourceWeek, int $actorUserId): int
{
ensure_schedule_schema();
$date = new DateTimeImmutable($dateInSourceWeek);
$monday = $date->modify('monday this week');
$copied = 0;
for ($i=0; $i<7; $i++) {
$source = $monday->modify('+' . $i . ' days')->format('Y-m-d');
$target = $monday->modify('+' . ($i+7) . ' days')->format('Y-m-d');
schedule_copy_day($source, $target, $actorUserId);
$copied++;
}
return $copied;
}
function schedule_minimums(): array
{
return [
'waiter' => max(0, min(20, (int)setting('schedule_min_waiters','1'))),
'kitchen' => max(0, min(20, (int)setting('schedule_min_kitchen','1'))),
];
}
function schedule_reservation_counts_for_month(string $month): array
{
ensure_reservations_schema();
$from = $month . '-01';
$to = (new DateTimeImmutable($from))->modify('last day of this month')->format('Y-m-d');
$stmt = db()->prepare(
"SELECT reservation_date,COUNT(*) AS cnt
FROM reservations
WHERE reservation_date BETWEEN ? AND ? AND status NOT IN ('rejected','cancelled')
GROUP BY reservation_date"
);
$stmt->execute([$from,$to]);
$out=[];
foreach($stmt->fetchAll() as $row) $out[(string)$row['reservation_date']] = (int)$row['cnt'];
return $out;
}
function schedule_staffing_status(array $people, int $reservationCount = 0): array
{
$min = schedule_minimums();
$waiters = count(array_filter($people, static fn(array $p): bool => ($p['role'] ?? '') === 'waiter'));
$kitchen = count(array_filter($people, static fn(array $p): bool => ($p['role'] ?? '') === 'kitchen'));
$missing=[];
if ($waiters < $min['waiter']) $missing[]='kelnerzy';
if ($kitchen < $min['kitchen']) $missing[]='kuchnia';
if (!$people && $reservationCount < 1) return ['level'=>'none','waiters'=>$waiters,'kitchen'=>$kitchen,'missing'=>[]];
if (!$people && $reservationCount > 0) return ['level'=>'danger','waiters'=>0,'kitchen'=>0,'missing'=>['brak obsady']];
return ['level'=>$missing ? 'warning' : 'ok','waiters'=>$waiters,'kitchen'=>$kitchen,'missing'=>$missing];
}