feat: Implement module permissions system with database-driven access control
- 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`.
This commit is contained in:
67
autoload/Domain/Users/PermissionRepository.php
Normal file
67
autoload/Domain/Users/PermissionRepository.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace Domain\Users;
|
||||
|
||||
class PermissionRepository
|
||||
{
|
||||
const MODULES = [ 'tasks', 'projects', 'finances', 'wiki', 'crm', 'work_time' ];
|
||||
|
||||
const DEFAULTS = [
|
||||
'tasks' => 1,
|
||||
'projects' => 1,
|
||||
'finances' => 0,
|
||||
'wiki' => 1,
|
||||
'crm' => 0,
|
||||
'work_time' => 1
|
||||
];
|
||||
|
||||
private $mdb;
|
||||
|
||||
public function __construct( $mdb = null )
|
||||
{
|
||||
if ( $mdb )
|
||||
$this -> mdb = $mdb;
|
||||
else if ( isset( $GLOBALS['mdb'] ) )
|
||||
$this -> mdb = $GLOBALS['mdb'];
|
||||
else
|
||||
$this -> mdb = null;
|
||||
}
|
||||
|
||||
public static function defaults()
|
||||
{
|
||||
return self::DEFAULTS;
|
||||
}
|
||||
|
||||
public function byUserId( $user_id )
|
||||
{
|
||||
if ( !$this -> mdb )
|
||||
return self::defaults();
|
||||
|
||||
$row = $this -> mdb -> get( 'users_permissions', '*', [ 'user_id' => (int)$user_id ] );
|
||||
|
||||
if ( !$row )
|
||||
return self::defaults();
|
||||
|
||||
$result = [];
|
||||
foreach ( self::MODULES as $module )
|
||||
$result[ $module ] = isset( $row[ $module ] ) ? (int)$row[ $module ] : 0;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function save( $user_id, array $modules )
|
||||
{
|
||||
if ( !$this -> mdb )
|
||||
return;
|
||||
|
||||
$data = [];
|
||||
foreach ( self::MODULES as $module )
|
||||
$data[ $module ] = isset( $modules[ $module ] ) ? (int)(bool)$modules[ $module ] : 0;
|
||||
|
||||
$existing = $this -> mdb -> get( 'users_permissions', 'user_id', [ 'user_id' => (int)$user_id ] );
|
||||
|
||||
if ( $existing )
|
||||
$this -> mdb -> update( 'users_permissions', $data, [ 'user_id' => (int)$user_id ] );
|
||||
else
|
||||
$this -> mdb -> insert( 'users_permissions', array_merge( [ 'user_id' => (int)$user_id ], $data ) );
|
||||
}
|
||||
}
|
||||
352
autoload/Domain/Users/VacationRepository.php
Normal file
352
autoload/Domain/Users/VacationRepository.php
Normal file
@@ -0,0 +1,352 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user