PYBS (Personel Yönetim Bilgi Sistemi)
<?php
// modules/admin-mesai-ekle.php
session_start();
require_once '../config/db.php';
require_once '../config/functions.php';
// Sadece Yetkililer
yetkiKontrol(['root', 'muhasebe', 'insan_kaynaklari']);
include '../includes/header.php';
include '../includes/menu.php';
$mesaj = '';
$kullanici_id = $_SESSION['kullanici_id'];
$hedef = 'tekil'; // Varsayılan: Tekil
// Resmi Tatil Listesi
$tatiller = $pdo->query("SELECT tarih FROM resmi_tatiller")->fetchAll(PDO::FETCH_COLUMN);
$tatiller_json = json_encode($tatiller);
// SAAT SEÇENEKLERİ (tam ve buçuk)
$zaman_secenekleri = "";
for ($saat = 0; $saat < 24; $saat++) {
foreach (['00', '30'] as $dakika) {
$deger = sprintf('%02d:%s', $saat, $dakika);
$zaman_secenekleri .= "<option value='$deger'>$deger</option>";
}
}
// İŞLEM
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
csrfKontrol($_POST['csrf_token']);
$hedef = $_POST['hedef_kitle'];
$eklenecekler = [];
if ($hedef == 'toplu') {
$eklenecekler = $pdo->query("SELECT id FROM kullanicilar WHERE durum=1 AND rol != 'root'")->fetchAll(PDO::FETCH_COLUMN);
} elseif ($hedef == 'coklu') {
$eklenecekler = $_POST['personel_id_coklu'] ?? [];
} elseif ($hedef == 'tekil') {
if (!empty($_POST['personel_id_tekil'])) {
$eklenecekler[] = $_POST['personel_id_tekil'];
}
}
$tarih = $_POST['tarih'];
$bas_saat = $_POST['baslangic_saati'];
$bit_saat = $_POST['bitis_saati'];
$mesai_turu = $_POST['mesai_turu'];
$aciklama = guvenlik($_POST['aciklama']);
// Brüt Süre Hesapla
$t1 = strtotime($bas_saat);
$t2 = strtotime($bit_saat);
if ($t2 < $t1) $t2 += 24 * 60 * 60;
$fark = ($t2 - $t1) / 3600;
// Mola Düş (Otomatik)
$mola = 0;
if ($fark > 7.5) $mola = 1; elseif ($fark > 4) $mola = 0.5;
$net_saat_brut = $fark - $mola;
if($net_saat_brut < 0) $net_saat_brut = 0;
// Döngü ile Ekle
$sayac = 0;
$hata_mesajlari = [];
$sql = "INSERT INTO mesai_hareketleri (calisan_id, hedef_yonetici_id, onaylayan_id, tarih, baslangic_saati, bitis_saati, toplam_saat, mesai_turu, aciklama, durum, onaylanma_tarihi)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'onaylandi', NOW())";
$stmt = $pdo->prepare($sql);
if (!empty($eklenecekler)) {
// Personel verilerini topluca çek
$personel_data_stmt = $pdo->prepare("SELECT id, ad, soyad, gunluk_calisma_saati FROM kullanicilar WHERE id IN (" . implode(',', $eklenecekler) . ")");
$personel_data_stmt->execute();
// HATA DÜZELTME: FETCH_KEY_PAIR hatası giderildi, ID'ye göre gruplayarak ilişkisel dizi çekildi.
$personel_bilgileri = $personel_data_stmt->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_GROUP);
foreach ($eklenecekler as $pid) {
$pid = (int)$pid;
$personel_info = $personel_bilgileri[$pid][0] ?? null;
if (!$personel_info) continue; // Personel bulunamazsa atla
$personel_ad = $personel_info['ad'] . ' ' . $personel_info['soyad'];
$gunluk_mesai = (float)($personel_info['gunluk_calisma_saati'] ?? 9.00); // Varsayılan 9 saat
// 1. Kontrol: Onaylanmış mevcut mesai kaydı var mı?
$kontrol_mesai = $pdo->prepare("SELECT count(*) FROM mesai_hareketleri WHERE calisan_id = ? AND tarih = ? AND durum = 'onaylandi'");
$kontrol_mesai->execute([$pid, $tarih]);
// 2. Kontrol: Onaylanmış TAM GÜN izin kaydı var mı?
$kontrol_izin_tam = $pdo->prepare("
SELECT count(*) FROM izin_talepleri
WHERE calisan_id = ?
AND durum = 'onaylandi'
AND ? BETWEEN DATE(baslangic_tarihi) AND DATE(bitis_tarihi)
AND izin_turu != 'saatlik'
");
$kontrol_izin_tam->execute([$pid, $tarih]);
$hata = false;
if ($kontrol_mesai->fetchColumn() > 0) {
$hata_mesajlari[] = "({$personel_ad}) $tarih tarihinde zaten onaylanmış <strong>MESAİ</strong> kaydı var.";
$hata = true;
} elseif ($kontrol_izin_tam->fetchColumn() > 0) {
$hata_mesajlari[] = "({$personel_ad}) $tarih tarihinde onaylanmış <strong>TAM GÜN İZİN</strong> kaydı var. Mesai eklenemez.";
$hata = true;
}
// Eğer tam gün veya mesai çakışması yoksa, saatlik izin borcunu hesapla
if (!$hata) {
// 3. Saatlik İzin Borcunu Hesapla
$stmt_saatlik_izin = $pdo->prepare("
SELECT SUM(saatlik_sure) as toplam_izin
FROM izin_talepleri
WHERE calisan_id = ?
AND durum = 'onaylandi'
AND DATE(baslangic_tarihi) = ?
AND izin_turu = 'saatlik'
");
$stmt_saatlik_izin->execute([$pid, $tarih]);
$toplam_saatlik_izin = (float)$stmt_saatlik_izin->fetchColumn() ?? 0.0;
$net_mesai_kayit = $net_saat_brut;
// Eğer saatlik izin varsa ve bu mesai borç kapatmak için kullanılıyorsa
if ($toplam_saatlik_izin > 0) {
// Yeni mesai süresi = Girilen süre - (Borç olan izin süresi)
$gercek_fazla_mesai = $net_saat_brut - $toplam_saatlik_izin;
if ($gercek_fazla_mesai > 0) {
$net_mesai_kayit = round($gercek_fazla_mesai, 2);
// İzin borcunun kapatıldığı bilgisini uyarı olarak ekle
$hata_mesajlari[] = "({$personel_ad}) $tarih tarihinde {$toplam_saatlik_izin} saatlik izin borcu kapatıldı. Fazla mesai süresi: **{$net_mesai_kayit} Saat** olarak kaydedildi.";
} else {
// Fazla mesai, izin borcunu tamamen kapatıyorsa ve geriye bir şey kalmıyorsa, mesaiyi kaydetme
$hata_mesajlari[] = "({$personel_ad}) $tarih tarihinde {$toplam_saatlik_izin} saatlik izin borcu olduğu için, girilen {$net_saat_brut} saat mesai kaydı tamamen izin borcunu kapatmıştır. Fazla mesai **0.0 Saat** olarak kaydedilmedi.";
$hata = true; // <-- KAYDI ENGELLE
}
}
if (!$hata) {
try {
$gercek_mesai_turu = $mesai_turu;
if ($mesai_turu === 'vardiya_gece') {
$gercek_mesai_turu = 'fazla_mesai';
}
// Güncellenmiş net mesai süresini kullan
$stmt->execute([$pid, $kullanici_id, $kullanici_id, $tarih, $bas_saat, $bit_saat, $net_mesai_kayit, $gercek_mesai_turu, $aciklama]);
$sayac++;
} catch (PDOException $e) {
$hata_mesajlari[] = "({$personel_ad}) Veritabanı Hatası: " . $e->getMessage();
}
}
}
}
}
$mesaj_html = "";
if ($sayac > 0) {
$mesaj_html .= '<div class="alert alert-success border-success border-5 border-start shadow-sm">
<h5 class="alert-heading fw-bold"><i class="fas fa-check-circle me-2"></i>Başarılı!</h5>
Toplam <strong>'.$sayac.'</strong> personele mesai girişi yapıldı.
</div>';
}
if (!empty($hata_mesajlari)) {
// Hata mesajlarını modalda göstermek için hazırlıyoruz
$hata_listesi = '<ul>';
foreach ($hata_mesajlari as $hata) {
$hata_listesi .= '<li>'.$hata.'</li>';
}
$hata_listesi .= '</ul>';
// Mesajı modalı tetikleyecek şekilde ayarlıyoruz
$mesaj_html .= '
<div class="alert alert-warning border-warning border-5 border-start shadow-sm">
<h5 class="alert-heading fw-bold"><i class="fas fa-exclamation-triangle me-2"></i>Mesai İşlemi Uyarıları/Detayları!</h5>
Bazı personel için süreler ayarlandı/kayıt engellendi. Detaylar için aşağıdaki butona tıklayın:
<button type="button" class="btn btn-sm btn-warning mt-2" data-bs-toggle="modal" data-bs-target="#hataModal">
<i class="fas fa-search me-1"></i> Detayları Gör
</button>
</div>
<div class="modal fade" id="hataModal" tabindex="-1" aria-labelledby="hataModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title" id="hataModalLabel"><i class="fas fa-exclamation-triangle me-2"></i> Mesai İşlemi Detayları</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="fw-bold">Aşağıdaki personel için izin/mesai çakışması kontrol edildi. Mesai süreleri ayarlandı veya kayıt engellendi:</p>
<div class="alert alert-light border-warning">
'.$hata_listesi.'
</div>
<p class="small text-muted">Tam gün izin veya mevcut mesai kaydı olanlara işlem yapılmamıştır.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Kapat</button>
</div>
</div>
</div>
</div>';
}
if ($sayac == 0 && empty($hata_mesajlari)) {
$mesaj_html .= '<div class="alert alert-danger">Lütfen işlem yapılacak personel seçin.</div>';
}
$mesaj = $mesaj_html;
}
// Personel Listesi
$personeller = $pdo->query("SELECT id, ad, soyad FROM kullanicilar WHERE durum=1 AND rol != 'root' ORDER BY ad ASC")->fetchAll();
?>
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<title>Yönetici Mesai Girişi</title>
<style>
body { background-color: #f8f9fa; }
.card { border: none; border-radius: 1.5rem; overflow: hidden; }
.card-header { background: linear-gradient(135deg, #0d6efd, #0dcaf0); color: white; }
.form-control, .form-select { border-radius: 0.75rem; }
.btn-primary { border-radius: 0.75rem; padding: 0.85rem; font-weight: 600; }
.btn-check:checked + .btn-outline-primary { background-color: #0d6efd; border-color: #0d6efd; }
.alert-important { border-left-width: 6px; }
@media (max-width: 576px) {
.container-fluid { padding: 1rem; }
h3 { font-size: 1.4rem; }
}
</style>
</head>
<body>
<div class="container-fluid py-4 py-md-5">
<div class="row justify-content-center">
<div class="col-12 col-lg-10 col-xl-8">
<h3 class="mb-4 text-center text-primary fw-bold">
<i class="fas fa-clock me-2"></i>Yönetici Mesai Girişi
</h3>
<div class="alert alert-info alert-important mb-4" role="alert">
<h6 class="alert-heading fw-bold"><i class="fas fa-info-circle me-2"></i>Bilgi</h6>
Bu sayfadan yapılan mesai girişleri <strong>otomatik onaylı</strong> olarak sisteme işlenir.<br>
Aynı güne onaylı tam gün izin veya mevcut mesai kaydı olanlara işlem **yapılmaz**. Saatlik izin borcu varsa, girilen fazla mesai süresi öncelikle bu borcu kapatır.
</div>
<div class="card shadow-lg">
<div class="card-header py-4 text-center">
<h5 class="mb-0 text-white fw-bold">Mesai Bilgilerini Giriniz</h5>
</div>
<div class="card-body p-4 p-md-5">
<?php echo $mesaj; ?>
<form method="POST" id="mesaiForm" novalidate>
<input type="hidden" name="csrf_token" value="<?php echo csrfTokenOlustur(); ?>">
<div class="mb-4">
<label class="form-label fw-semibold">
<i class="fas fa-users me-2 text-primary"></i>Kime Mesai Eklenecek? <span class="text-danger">*</span>
</label>
<div class="btn-group w-100" role="group" aria-label="Hedef kitle">
<input type="radio" class="btn-check" name="hedef_kitle" id="toplu" value="toplu" onchange="togglePersonel()">
<label class="btn btn-outline-primary rounded-start" for="toplu">Toplu (Tüm Personel)</label>
<input type="radio" class="btn-check" name="hedef_kitle" id="coklu" value="coklu" onchange="togglePersonel()">
<label class="btn btn-outline-primary" for="coklu">Çoklu Seçim</label>
<input type="radio" class="btn-check" name="hedef_kitle" id="tekil" value="tekil" checked onchange="togglePersonel()">
<label class="btn btn-outline-primary rounded-end" for="tekil">Tek Personel</label>
</div>
</div>
<div class="mb-4" id="tekilDiv">
<label for="personel_id_tekil" class="form-label fw-semibold">
<i class="fas fa-user me-2 text-primary"></i>Personel Seçiniz
</label>
<select name="personel_id_tekil" id="personel_id_tekil" class="form-select form-select-lg">
<option value="">Seçiniz...</option>
<?php foreach($personeller as $p): ?>
<option value="<?php echo $p['id']; ?>">
<?php echo htmlspecialchars($p['ad'].' '.$p['soyad']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-4 d-none" id="cokluDiv">
<label for="personel_id_coklu" class="form-label fw-semibold">
<i class="fas fa-users-cog me-2 text-primary"></i>Personelleri Seçiniz (Çoklu)
</label>
<select name="personel_id_coklu[]" id="personel_id_coklu" class="form-select form-select-lg" multiple size="8">
<?php foreach($personeller as $p): ?>
<option value="<?php echo $p['id']; ?>">
<?php echo htmlspecialchars($p['ad'].' '.$p['soyad']); ?>
</option>
<?php endforeach; ?>
</select>
<div class="form-text mt-2">
<i class="fas fa-info-circle me-2"></i>
Birden fazla seçim için <kbd>Ctrl</kbd> (Windows) veya <kbd>Cmd</kbd> (Mac) tuşunu basılı tutun.
</div>
</div>
<hr class="my-5">
<div class="row g-3 mb-4">
<div class="col-md-6">
<label for="tarih" class="form-label fw-semibold">
<i class="fas fa-calendar-day me-2 text-primary"></i>Tarih <span class="text-danger">*</span>
</label>
<input type="date" name="tarih" id="mesaiTarihInput" class="form-control form-control-lg"
value="<?php echo date('Y-m-d'); ?>" required onchange="mesaiTuruOtoSec()">
</div>
<div class="col-md-6">
<label for="mesai_turu" class="form-label fw-semibold">
<i class="fas fa-tags me-2 text-primary"></i>Mesai Türü <span class="text-danger">*</span>
</label>
<select name="mesai_turu" id="mesaiTuruSelect" class="form-select form-select-lg bg-light fw-bold">
<option value="fazla_mesai">Fazla Mesai (Hafta İçi / Cumartesi)</option>
<option value="resmi_tatil_mesaisi">Resmi Tatil / Bayram (2.0x)</option>
<option value="vardiya_gece">Vardiya Girişi (18:00 - 03:00)</option>
</select>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label for="baslangic_saati" class="form-label fw-semibold">
<i class="fas fa-clock me-2 text-primary"></i>Başlangıç Saati <span class="text-danger">*</span>
</label>
<select name="baslangic_saati" id="baslangic_saati" class="form-select form-select-lg" required>
<option value="">Seçiniz</option>
<?php echo $zaman_secenekleri; ?>
</select>
</div>
<div class="col-md-6">
<label for="bitis_saati" class="form-label fw-semibold">
<i class="far fa-clock me-2 text-primary"></i>Bitiş Saati <span class="text-danger">*</span>
</label>
<select name="bitis_saati" id="bitis_saati" class="form-select form-select-lg" required>
<option value="">Seçiniz</option>
<?php echo $zaman_secenekleri; ?>
</select>
</div>
</div>
<div class="mb-4">
<label for="aciklama" class="form-label fw-semibold">
<i class="fas fa-comment-dots me-2 text-primary"></i>Açıklama (İsteğe Bağlı)
</label>
<input type="text" name="aciklama" id="aciklama" class="form-control form-control-lg"
placeholder="Örn: Ek üretim vardiyası, bayram mesaisi...">
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg shadow">
<i class="fas fa-save me-2"></i> KAYDET VE İŞLE
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
const resmiTatiller = <?php echo $tatiller_json; ?>;
function togglePersonel() {
const hedef = document.querySelector('input[name="hedef_kitle"]:checked').value;
const tekilDiv = document.getElementById('tekilDiv');
const cokluDiv = document.getElementById('cokluDiv');
const tekilSelect = document.getElementById('personel_id_tekil');
const cokluSelect = document.getElementById('personel_id_coklu');
tekilDiv.classList.toggle('d-none', hedef !== 'tekil');
cokluDiv.classList.toggle('d-none', hedef !== 'coklu');
// Required ayarı
if (hedef === 'tekil') {
tekilSelect.required = true;
cokluSelect.required = false;
} else if (hedef === 'coklu') {
tekilSelect.required = false;
cokluSelect.required = true;
} else { // toplu
tekilSelect.required = false;
cokluSelect.required = false;
}
}
function mesaiTuruOtoSec() {
const tarihInput = document.getElementById('mesaiTarihInput');
const mesaiSelect = document.getElementById('mesaiTuruSelect');
const tarih = tarihInput.value;
if (!tarih) {
mesaiSelect.value = 'fazla_mesai';
return;
}
const date = new Date(tarih + 'T00:00:00');
const day = date.getDay(); // 0=Pazar, 6=Cumartesi
let tur = 'fazla_mesai';
if (resmiTatiller.includes(tarih) || day === 0) {
tur = 'resmi_tatil_mesaisi';
}
mesaiSelect.value = tur;
}
// Sayfa yüklendiğinde
document.addEventListener('DOMContentLoaded', () => {
togglePersonel();
mesaiTuruOtoSec();
// PHP'den gelen hatayı kontrol et ve modalı tetikle
const hataModal = document.getElementById('hataModal');
if (hataModal) {
const bsHataModal = new bootstrap.Modal(hataModal);
bsHataModal.show();
}
});
</script>
<?php include '../includes/footer.php'; ?>
</body>
</html>
DEMO SÜRÜMÜDÜR TAM SÜRÜM İÇİN İLETİŞİM KURUN
📖 PYBS (Personel Yönetim Bilgi Sistemi) Kullanım Kılavuzu
🚀 Proje Tanımı
PYBS, personel bilgilerini, izinleri, maaş bordrolarını ve performans değerlendirmelerini merkezi ve dijital bir platformda yönetmek için tasarlanmış kapsamlı bir Personel Yönetim Bilgi Sistemi'dir. Amacımız, İnsan Kaynakları (İK) süreçlerini otomatikleştirerek verimliliği artırmak ve veri tutarlılığını sağlamaktır.
✨ Temel Özellikler
Personel Yönetimi: Çalışanların kişisel, iletişim ve görev bilgilerini kaydetme/güncelleme.
İzin Yönetimi: Çalışanların izin taleplerini oluşturma, onaylama/reddetme ve kalan izin haklarını takip etme.
Performans Değerlendirme: Yöneticilerin ve çalışanların performans hedeflerini belirlemesi ve değerlendirmeleri kaydetmesi.
Bordro Entegrasyonu: Maaş ve avans bilgilerini kaydetme ve bordro çıktılarını oluşturma (Harici sistemlerle entegrasyon potansiyeli).
Raporlama: İK yöneticileri için özet ve detaylı personel, izin ve bordro raporları oluşturma.
💻 Son Kullanıcı Kullanımı🔑 Giriş Yapma
Demo için kullanıcı adı : test.test
Demo için şifre : 123456
Demo hesabında root / yonetici vb yetki yoktur.
Tam sürüm için iletişime geçin.
Sistem "Ramsa Makine" tarafından aktif olarak kullanılmaktadır
Meta Veri (Özet)
İşyeri çalışanlarının maaş, fazla mesai ve puantaj ile bordro takip, kontrol ve raporlama sistemi