- Added `users_permissions` table for managing user permissions. - Created `PermissionRepository` for handling permission logic. - Refactored `controls\Users::permissions()` to utilize the new database structure. - Introduced AJAX endpoint for saving user permissions. - Enhanced user management UI with permission checkboxes. - Added vacation management template for handling employee absences. - Implemented tests for `PermissionRepository`.
353 lines
9.0 KiB
PHP
353 lines
9.0 KiB
PHP
<?php
|
|
namespace Domain\Users;
|
|
|
|
class VacationRepository
|
|
{
|
|
private $mdb;
|
|
|
|
const TYPE_VACATION = 'urlop_wypoczynkowy';
|
|
const TYPE_SICK = 'chorobowe';
|
|
const TYPE_OTHER = 'inna_nieobecnosc';
|
|
|
|
const TYPES = [
|
|
self::TYPE_VACATION => 'Urlop wypoczynkowy',
|
|
self::TYPE_SICK => 'Chorobowe',
|
|
self::TYPE_OTHER => 'Inna nieobecność'
|
|
];
|
|
|
|
const DEFAULT_DAYS_LIMIT = 26;
|
|
|
|
private static bool $tables_checked = false;
|
|
|
|
public function __construct( $mdb )
|
|
{
|
|
$this -> mdb = $mdb;
|
|
|
|
if ( !self::$tables_checked )
|
|
{
|
|
$this -> ensureTables();
|
|
self::$tables_checked = true;
|
|
}
|
|
}
|
|
|
|
private function ensureTables(): void
|
|
{
|
|
$pdo = $this -> mdb -> pdo;
|
|
|
|
$pdo -> exec( "
|
|
CREATE TABLE IF NOT EXISTS `users_vacations` (
|
|
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
`user_id` INT UNSIGNED NOT NULL,
|
|
`date_from` DATE NOT NULL,
|
|
`date_to` DATE NOT NULL,
|
|
`type` VARCHAR(30) NOT NULL DEFAULT 'urlop_wypoczynkowy',
|
|
`comment` TEXT,
|
|
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
INDEX `idx_user_year` (`user_id`, `date_from`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
" );
|
|
|
|
$pdo -> exec( "
|
|
CREATE TABLE IF NOT EXISTS `users_vacation_limits` (
|
|
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
`user_id` INT UNSIGNED NOT NULL,
|
|
`year` SMALLINT UNSIGNED NOT NULL,
|
|
`days_limit` TINYINT UNSIGNED NOT NULL DEFAULT 26,
|
|
UNIQUE KEY `uq_user_year` (`user_id`, `year`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
" );
|
|
}
|
|
|
|
public function allByYear( int $year, ?int $user_id = null ): array
|
|
{
|
|
$where = [
|
|
'AND' => [
|
|
'date_from[>=]' => $year . '-01-01',
|
|
'date_from[<=]' => $year . '-12-31'
|
|
],
|
|
'ORDER' => [ 'date_from' => 'DESC' ]
|
|
];
|
|
|
|
if ( $user_id )
|
|
$where['AND']['user_id'] = $user_id;
|
|
|
|
return $this -> mdb -> select( 'users_vacations', [
|
|
'[>]users' => [ 'user_id' => 'id' ]
|
|
], [
|
|
'users_vacations.id',
|
|
'users_vacations.user_id',
|
|
'users_vacations.date_from',
|
|
'users_vacations.date_to',
|
|
'users_vacations.type',
|
|
'users_vacations.comment',
|
|
'users.name',
|
|
'users.surname'
|
|
], $where );
|
|
}
|
|
|
|
public function add( int $user_id, string $date_from, string $date_to, string $type, string $comment = '' )
|
|
{
|
|
if ( !isset( self::TYPES[ $type ] ) )
|
|
return false;
|
|
|
|
if ( $date_from > $date_to )
|
|
return false;
|
|
|
|
$this -> mdb -> insert( 'users_vacations', [
|
|
'user_id' => $user_id,
|
|
'date_from' => $date_from,
|
|
'date_to' => $date_to,
|
|
'type' => $type,
|
|
'comment' => $comment
|
|
] );
|
|
|
|
return $this -> mdb -> id();
|
|
}
|
|
|
|
public function getById( int $id ): ?array
|
|
{
|
|
$row = $this -> mdb -> get( 'users_vacations', '*', [ 'id' => $id ] );
|
|
return $row ?: null;
|
|
}
|
|
|
|
public function update( int $id, int $user_id, string $date_from, string $date_to, string $type, string $comment = '' ): bool
|
|
{
|
|
if ( !isset( self::TYPES[ $type ] ) )
|
|
return false;
|
|
|
|
if ( $date_from > $date_to )
|
|
return false;
|
|
|
|
$this -> mdb -> update( 'users_vacations', [
|
|
'user_id' => $user_id,
|
|
'date_from' => $date_from,
|
|
'date_to' => $date_to,
|
|
'type' => $type,
|
|
'comment' => $comment
|
|
], [ 'id' => $id ] );
|
|
|
|
return true;
|
|
}
|
|
|
|
public function delete( int $id ): bool
|
|
{
|
|
$this -> mdb -> delete( 'users_vacations', [ 'id' => $id ] );
|
|
return true;
|
|
}
|
|
|
|
public function getLimit( int $user_id, int $year ): int
|
|
{
|
|
$row = $this -> mdb -> get( 'users_vacation_limits', 'days_limit', [
|
|
'user_id' => $user_id,
|
|
'year' => $year
|
|
] );
|
|
|
|
return $row !== null ? (int) $row : self::DEFAULT_DAYS_LIMIT;
|
|
}
|
|
|
|
public function setLimit( int $user_id, int $year, int $days_limit ): bool
|
|
{
|
|
$exists = $this -> mdb -> has( 'users_vacation_limits', [
|
|
'user_id' => $user_id,
|
|
'year' => $year
|
|
] );
|
|
|
|
if ( $exists )
|
|
{
|
|
$this -> mdb -> update( 'users_vacation_limits', [
|
|
'days_limit' => $days_limit
|
|
], [
|
|
'user_id' => $user_id,
|
|
'year' => $year
|
|
] );
|
|
}
|
|
else
|
|
{
|
|
$this -> mdb -> insert( 'users_vacation_limits', [
|
|
'user_id' => $user_id,
|
|
'year' => $year,
|
|
'days_limit' => $days_limit
|
|
] );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function getLimitsForYear( int $year ): array
|
|
{
|
|
$rows = $this -> mdb -> select( 'users_vacation_limits', '*', [
|
|
'year' => $year
|
|
] );
|
|
|
|
$limits = [];
|
|
foreach ( $rows as $row )
|
|
$limits[ (int) $row['user_id'] ] = (int) $row['days_limit'];
|
|
|
|
return $limits;
|
|
}
|
|
|
|
public static function countBusinessDays( string $date_from, string $date_to ): int
|
|
{
|
|
$start = new \DateTime( $date_from );
|
|
$end = new \DateTime( $date_to );
|
|
$years = range( (int) $start -> format( 'Y' ), (int) $end -> format( 'Y' ) );
|
|
$holidays = self::getPolishHolidays( $years );
|
|
$days = 0;
|
|
|
|
while ( $start <= $end )
|
|
{
|
|
$dow = (int) $start -> format( 'N' );
|
|
if ( $dow <= 5 && !isset( $holidays[ $start -> format( 'Y-m-d' ) ] ) )
|
|
$days++;
|
|
$start -> modify( '+1 day' );
|
|
}
|
|
|
|
return $days;
|
|
}
|
|
|
|
private static function getPolishHolidays( array $years ): array
|
|
{
|
|
$holidays = [];
|
|
|
|
foreach ( $years as $y )
|
|
{
|
|
// Stałe święta ustawowe
|
|
$fixed = [
|
|
"$y-01-01", // Nowy Rok
|
|
"$y-01-06", // Trzech Króli
|
|
"$y-05-01", // Święto Pracy
|
|
"$y-05-03", // Święto Konstytucji
|
|
"$y-08-15", // Wniebowzięcie NMP
|
|
"$y-11-01", // Wszystkich Świętych
|
|
"$y-11-11", // Święto Niepodległości
|
|
"$y-12-25", // Boże Narodzenie
|
|
"$y-12-26", // Drugi dzień Bożego Narodzenia
|
|
];
|
|
|
|
foreach ( $fixed as $d )
|
|
$holidays[ $d ] = true;
|
|
|
|
// Wielkanoc i święta ruchome
|
|
$easter = new \DateTime( date( 'Y-m-d', easter_date( $y ) ) );
|
|
$easter_monday = ( clone $easter ) -> modify( '+1 day' );
|
|
$corpus_christi = ( clone $easter ) -> modify( '+60 days' );
|
|
|
|
$holidays[ $easter -> format( 'Y-m-d' ) ] = true;
|
|
$holidays[ $easter_monday -> format( 'Y-m-d' ) ] = true;
|
|
$holidays[ $corpus_christi -> format( 'Y-m-d' ) ] = true;
|
|
}
|
|
|
|
return $holidays;
|
|
}
|
|
|
|
public function usedDaysByYear( int $year, ?string $type = null ): array
|
|
{
|
|
$where = [
|
|
'AND' => [
|
|
'date_from[>=]' => $year . '-01-01',
|
|
'date_from[<=]' => $year . '-12-31'
|
|
]
|
|
];
|
|
|
|
if ( $type )
|
|
$where['AND']['type'] = $type;
|
|
|
|
$rows = $this -> mdb -> select( 'users_vacations', [ 'user_id', 'date_from', 'date_to' ], $where );
|
|
|
|
$totals = [];
|
|
foreach ( $rows as $row )
|
|
{
|
|
$uid = (int) $row['user_id'];
|
|
if ( !isset( $totals[ $uid ] ) )
|
|
$totals[ $uid ] = 0;
|
|
$totals[ $uid ] += self::countBusinessDays( $row['date_from'], $row['date_to'] );
|
|
}
|
|
|
|
return $totals;
|
|
}
|
|
|
|
public function summaryByYear( int $year, array $users ): array
|
|
{
|
|
$limits = $this -> getLimitsForYear( $year );
|
|
$used_map = $this -> usedDaysByYear( $year, self::TYPE_VACATION );
|
|
$summary = [];
|
|
|
|
foreach ( $users as $u )
|
|
{
|
|
$uid = (int) $u['id'];
|
|
$limit = $limits[ $uid ] ?? self::DEFAULT_DAYS_LIMIT;
|
|
$used = $used_map[ $uid ] ?? 0;
|
|
|
|
$summary[] = [
|
|
'user_id' => $uid,
|
|
'name' => $u['name'] . ' ' . $u['surname'],
|
|
'limit' => $limit,
|
|
'used' => $used,
|
|
'remaining' => $limit - $used
|
|
];
|
|
}
|
|
|
|
return $summary;
|
|
}
|
|
|
|
public function carryoverByYear( int $year, array $users ): array
|
|
{
|
|
// Znajdź najwcześniejszy rok z danymi (urlopy lub limity)
|
|
$min_vacation_year = $this -> mdb -> get( 'users_vacations', 'date_from', [
|
|
'ORDER' => [ 'date_from' => 'ASC' ],
|
|
'LIMIT' => 1
|
|
] );
|
|
$min_limit_year = $this -> mdb -> get( 'users_vacation_limits', 'year', [
|
|
'ORDER' => [ 'year' => 'ASC' ],
|
|
'LIMIT' => 1
|
|
] );
|
|
|
|
$start_year = $year - 1; // domyślnie sprawdzaj przynajmniej rok wstecz
|
|
if ( $min_vacation_year )
|
|
$start_year = min( $start_year, (int) substr( $min_vacation_year, 0, 4 ) );
|
|
if ( $min_limit_year )
|
|
$start_year = min( $start_year, (int) $min_limit_year );
|
|
|
|
if ( $start_year >= $year )
|
|
return [];
|
|
|
|
$carryover = [];
|
|
|
|
foreach ( $users as $u )
|
|
{
|
|
$uid = (int) $u['id'];
|
|
$total_remaining = 0;
|
|
$year_details = [];
|
|
|
|
for ( $y = $start_year; $y < $year; $y++ )
|
|
{
|
|
$limit = $this -> getLimit( $uid, $y );
|
|
$used_map = $this -> usedDaysByYear( $y, self::TYPE_VACATION );
|
|
$used = $used_map[ $uid ] ?? 0;
|
|
$remaining = $limit - $used;
|
|
|
|
if ( $remaining > 0 )
|
|
{
|
|
$total_remaining += $remaining;
|
|
$year_details[] = [
|
|
'year' => $y,
|
|
'remaining' => $remaining
|
|
];
|
|
}
|
|
}
|
|
|
|
if ( $total_remaining > 0 )
|
|
{
|
|
$carryover[] = [
|
|
'user_id' => $uid,
|
|
'name' => $u['name'] . ' ' . $u['surname'],
|
|
'total' => $total_remaining,
|
|
'years' => $year_details
|
|
];
|
|
}
|
|
}
|
|
|
|
return $carryover;
|
|
}
|
|
}
|