Ek Açıklamalar..
Yapılan Bu güncellemede, mazeretlerinizi daha hassas bir şekilde yönetmenize olanak tanıyacak şekilde nöbet dağıtım algoritmasını genişletmiştir.
Güncellenmiş Kod (409+ Satır)
import pandas as pd
import sqlite3
from datetime import datetime, timedelta
import warnings
from PyQt5 import QtWidgets, QtGui, QtCore
import sys
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import re
# DeprecationWarning için uyarıları bastırma
warnings.filterwarnings('ignore', category=DeprecationWarning)
# Excel'den personel verilerini okuma
file_path = 'personel_listesi.xlsx'
personel_df = pd.read_excel(file_path, sheet_name='NÖBET ÇETELESİ')
# Sütun isimlerinde gereksiz boşlukları temizleme
personel_df.columns = personel_df.columns.str.strip()
# SQLite veri tabanı bağlantısı oluşturma
conn = sqlite3.connect('nobet_takip.db', detect_types=sqlite3.PARSE_DECLTYPES)
cursor = conn.cursor()
# Personel ve nöbet tablolarını yeniden oluşturma
cursor.execute('''
CREATE TABLE IF NOT EXISTS personel (
sicil INTEGER PRIMARY KEY,
adi TEXT,
soyadi TEXT,
birim TEXT,
eposta TEXT,
mazeret TEXT,
hafta_ici_gunduz INTEGER DEFAULT 0,
hafta_ici_gece INTEGER DEFAULT 0,
hafta_sonu_gunduz INTEGER DEFAULT 0,
hafta_sonu_gece INTEGER DEFAULT 0,
cuma_gunduz INTEGER DEFAULT 0,
cuma_gece INTEGER DEFAULT 0
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS nobet (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sicil INTEGER,
tarih DATE,
nobet_tipi TEXT,
FOREIGN KEY (sicil) REFERENCES personel(sicil)
)
''')
# Nöbet değişim talepleri tablosunu yeniden oluşturma
cursor.execute('''
CREATE TABLE IF NOT EXISTS nobet_degisim_talepleri (
id INTEGER PRIMARY KEY AUTOINCREMENT,
talep_eden_sicil INTEGER,
degisim_yapilacak_sicil INTEGER,
tarih DATE,
durum TEXT,
red_nedeni TEXT,
FOREIGN KEY (talep_eden_sicil) REFERENCES personel(sicil),
FOREIGN KEY (degisim_yapilacak_sicil) REFERENCES personel(sicil)
)
''')
conn.commit()
# Personel verilerini veri tabanına aktarma
for index, row in personel_df.iterrows():
cursor.execute('''
INSERT OR IGNORE INTO personel (sicil, adi, soyadi, birim, eposta, mazeret, hafta_ici_gunduz, hafta_ici_gece, hafta_sonu_gunduz, hafta_sonu_gece, cuma_gunduz, cuma_gece)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (row['SİCİLİ'], row['ADI'], row['SOYADI'], row['BİRİM'], row.get('EPOSTA', ''), row['MAZERET'], row['HAFTA İÇİ GÜNDÜZ'], row['HAFTA İÇİ GECE'], row['HAFTA SONU GÜNDÜZ'], row['HAFTA SONU GECE'], row['CUMA GÜNDÜZ'], row['CUMA GECE']))
conn.commit()
# E-posta gönderme fonksiyonu
def eposta_gonder(alici, konu, mesaj):
try:
gonderici_eposta = 'youremail@gmail.com'
gonderici_sifre = 'yourpassword'
msg = MIMEMultipart()
msg['From'] = gonderici_eposta
msg['To'] = alici
msg['Subject'] = konu
msg.attach(MIMEText(mesaj, 'plain'))
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(gonderici_eposta, gonderici_sifre)
server.send_message(msg)
server.quit()
print(f"E-posta başarıyla gönderildi: {alici}")
except Exception as e:
print(f"E-posta gönderim hatası: {e}")
# PyQt5 Kullanarak Form Arayüzü Oluşturma
class NobetTakipUygulamasi(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
# Pencere başlığı ve boyutu
self.setWindowTitle('Nöbet Takip Programı')
self.setGeometry(100, 100, 800, 600)
# Layout oluşturma
self.layout = QtWidgets.QVBoxLayout()
# Personel bilgilerini gösteren tablo
self.personel_tablosu = QtWidgets.QTableWidget()
self.personel_tablosu.setRowCount(0)
self.personel_tablosu.setColumnCount(8)
self.personel_tablosu.setHorizontalHeaderLabels(['Sicil', 'Adı', 'Soyadı', 'Birim', 'E-posta', 'Hafta İçi Gündüz', 'Hafta Sonu Gündüz', 'Cuma Gündüz'])
self.layout.addWidget(self.personel_tablosu)
# Nöbet dağıtımı yapma butonu
self.nobet_dagit_btn = QtWidgets.QPushButton('Nöbet Dağıtımı Yap')
self.nobet_dagit_btn.clicked.connect(self.nobet_dagitim)
self.layout.addWidget(self.nobet_dagit_btn)
# İstatistik raporu gösterme butonu
self.rapor_goster_btn = QtWidgets.QPushButton('İstatistik Raporunu Göster')
self.rapor_goster_btn.clicked.connect(self.istatistik_raporu)
self.layout.addWidget(self.rapor_goster_btn)
# Nöbet değişim talep formu butonu
self.nobet_degisim_btn = QtWidgets.QPushButton('Nöbet Değişim Talebi')
self.nobet_degisim_btn.clicked.connect(self.nobet_degisim_talep)
self.layout.addWidget(self.nobet_degisim_btn)
# Nöbet değişim taleplerini onaylama butonu
self.talepleri_onayla_btn = QtWidgets.QPushButton('Nöbet Değişim Taleplerini Onayla')
self.talepleri_onayla_btn.clicked.connect(self.nobet_degisim_talepleri_onay)
self.layout.addWidget(self.talepleri_onayla_btn)
# Reddedilen ve onaylanan taleplerin raporu butonu
self.reddedilen_onaylanan_rapor_btn = QtWidgets.QPushButton('Reddedilen ve Onaylanan Taleplerin Raporu')
self.reddedilen_onaylanan_rapor_btn.clicked.connect(self.reddedilen_onaylanan_rapor)
self.layout.addWidget(self.reddedilen_onaylanan_rapor_btn)
# Ana layout'u ayarla
self.setLayout(self.layout)
self.personel_bilgilerini_yukle()
def personel_bilgilerini_yukle(self):
cursor.execute('SELECT sicil, adi, soyadi, birim, eposta, hafta_ici_gunduz, hafta_sonu_gunduz, cuma_gunduz FROM personel')
personel_verileri = cursor.fetchall()
self.personel_tablosu.setRowCount(len(personel_verileri))
for row_idx, row_data in enumerate(personel_verileri):
for col_idx, col_data in enumerate(row_data):
self.personel_tablosu.setItem(row_idx, col_idx, QtWidgets.QTableWidgetItem(str(col_data)))
def nobet_dagitim(self):
ay = 10
yil = 2024
tarih_baslangic = datetime(yil, ay, 1)
tarih_bitis = (tarih_baslangic + timedelta(days=32)).replace(day=1) - timedelta(days=1)
gunler = pd.date_range(tarih_baslangic, tarih_bitis)
for gun in gunler:
gun_adi = gun.strftime('%A')
if gun_adi == 'Saturday' or gun_adi == 'Sunday':
nobet_tipi = 'hafta_sonu_gunduz'
elif gun_adi == 'Friday':
nobet_tipi = 'cuma_gunduz'
else:
nobet_tipi = 'hafta_ici_gunduz'
# Mazeret ve uygunluk kontrolleri
uygun_personel = cursor.execute('''
SELECT sicil, mazeret FROM personel
WHERE (mazeret IS NULL OR mazeret NOT LIKE ?)
ORDER BY {} ASC
'''.format(nobet_tipi), ('%' + gun.strftime('%d') + '%',)).fetchall()
for personel in uygun_personel:
sicil, mazeret = personel
# Apply detailed mazeret checks
if not self.mazeret_kontrol(mazeret, gun, nobet_tipi):
continue
# Check if the person has already taken the same type of shift in the current month
onceki_nobet = cursor.execute('''
SELECT 1 FROM nobet WHERE sicil = ? AND strftime('%m', tarih) = ? AND nobet_tipi = ?
''', (sicil, f'{tarih_baslangic.month:02d}', nobet_tipi)).fetchone()
if onceki_nobet:
continue
# Assign the shift to the person
cursor.execute('''
INSERT INTO nobet (sicil, tarih, nobet_tipi)
VALUES (?, ?, ?)
''', (sicil, gun.date(), nobet_tipi))
# Update the shift count for that person
cursor.execute('''
UPDATE personel
SET {} = {} + 1
WHERE sicil = ?
'''.format(nobet_tipi, nobet_tipi), (sicil,))
break # Bir personele atama yaptıktan sonra döngüden çık
conn.commit()
self.personel_bilgilerini_yukle()
QtWidgets.QMessageBox.information(self, 'Bilgi', 'Nöbet dağıtımı tamamlandı.')
def istatistik_raporu(self):
# Gelişmiş rapor formu oluşturma
rapor_penceresi = QtWidgets.QWidget()
rapor_penceresi.setWindowTitle('İstatistik Raporu')
rapor_penceresi.setGeometry(200, 200, 800, 400)
layout = QtWidgets.QVBoxLayout()
# TreeView oluşturma
self.rapor_tablosu = QtWidgets.QTreeWidget()
self.rapor_tablosu.setHeaderLabels(['Adı', 'Soyadı', 'Toplam Nöbet Sayısı', 'Hafta İçi Gündüz', 'Hafta İçi Gece', 'Hafta Sonu Gündüz', 'Hafta Sonu Gece', 'Cuma Gündüz', 'Cuma Gece'])
layout.addWidget(self.rapor_tablosu)
# Verileri yükleme
personel_istatistikleri = cursor.execute('''
SELECT adi, soyadi, hafta_ici_gunduz, hafta_ici_gece, hafta_sonu_gunduz, hafta_sonu_gece, cuma_gunduz, cuma_gece
FROM personel
''').fetchall()
for personel in personel_istatistikleri:
adi, soyadi, hafta_ici_gunduz, hafta_ici_gece, hafta_sonu_gunduz, hafta_sonu_gece, cuma_gunduz, cuma_gece = personel
toplam_nobet = (hafta_ici_gunduz + hafta_ici_gece + hafta_sonu_gunduz + hafta_sonu_gece + cuma_gunduz + cuma_gece)
item = QtWidgets.QTreeWidgetItem([adi, soyadi, str(toplam_nobet), str(hafta_ici_gunduz), str(hafta_ici_gece), str(hafta_sonu_gunduz), str(hafta_sonu_gece), str(cuma_gunduz), str(cuma_gece)])
self.rapor_tablosu.addTopLevelItem(item)
rapor_penceresi.setLayout(layout)
rapor_penceresi.show()
self.rapor_penceresi = rapor_penceresi
def nobet_degisim_talep(self):
# Nöbet değişim talep formu oluşturma
talep_penceresi = QtWidgets.QWidget()
talep_penceresi.setWindowTitle('Nöbet Değişim Talebi')
talep_penceresi.setGeometry(200, 200, 400, 300)
layout = QtWidgets.QFormLayout()
self.talep_eden_sicil_input = QtWidgets.QLineEdit()
self.degisim_yapilacak_sicil_input = QtWidgets.QLineEdit()
self.degisim_tarihi_input = QtWidgets.QDateEdit()
self.degisim_tarihi_input.setCalendarPopup(True)
self.degisim_tarihi_input.setDate(QtCore.QDate.currentDate())
layout.addRow('Talep Eden Sicil:', self.talep_eden_sicil_input)
layout.addRow('Değişim Yapılacak Sicil:', self.degisim_yapilacak_sicil_input)
layout.addRow('Değişim Tarihi:', self.degisim_tarihi_input)
degisim_talep_btn = QtWidgets.QPushButton('Talep Gönder')
degisim_talep_btn.clicked.connect(self.degisim_talep_gonder)
layout.addWidget(degisim_talep_btn)
talep_penceresi.setLayout(layout)
talep_penceresi.show()
self.talep_penceresi = talep_penceresi
def degisim_talep_gonder(self):
talep_eden_sicil = self.talep_eden_sicil_input.text()
degisim_yapilacak_sicil = self.degisim_yapilacak_sicil_input.text()
degisim_tarihi = self.degisim_tarihi_input.date().toPyDate()
# Sicil numaralarını kontrol et
talep_eden_var = cursor.execute('SELECT 1 FROM personel WHERE sicil = ?', (talep_eden_sicil,)).fetchone()
degisim_yapilacak_var = cursor.execute('SELECT 1 FROM personel WHERE sicil = ?', (degisim_yapilacak_sicil,)).fetchone()
if not talep_eden_var or not degisim_yapilacak_var:
QtWidgets.QMessageBox.warning(self, 'Hata', 'Geçerli bir sicil numarası girilmedi.')
return
cursor.execute('''
INSERT INTO nobet_degisim_talepleri (talep_eden_sicil, degisim_yapilacak_sicil, tarih, durum)
VALUES (?, ?, ?, 'Beklemede')
''', (talep_eden_sicil, degisim_yapilacak_sicil, degisim_tarihi))
conn.commit()
QtWidgets.QMessageBox.information(self, 'Bilgi', 'Nöbet değişim talebi gönderildi.')
self.talep_penceresi.close()
def nobet_degisim_talepleri_onay(self):
# Nöbet değişim taleplerini onaylama penceresi oluşturma
onay_penceresi = QtWidgets.QWidget()
onay_penceresi.setWindowTitle('Nöbet Değişim Taleplerini Onayla')
onay_penceresi.setGeometry(200, 200, 800, 600)
layout = QtWidgets.QVBoxLayout()
self.talep_tablosu = QtWidgets.QTableWidget()
self.talep_tablosu.setRowCount(0)
self.talep_tablosu.setColumnCount(8)
self.talep_tablosu.setHorizontalHeaderLabels(['ID', 'Talep Eden Sicil', 'Değişim Yapılacak Sicil', 'Tarih', 'Durum', 'Onayla', 'Reddet', 'Reddetme Nedeni'])
layout.addWidget(self.talep_tablosu)
# Talep bilgilerini yükle
cursor.execute('SELECT id, talep_eden_sicil, degisim_yapilacak_sicil, tarih, durum FROM nobet_degisim_talepleri')
talepler = cursor.fetchall()
self.talep_tablosu.setRowCount(len(talepler))
for row_idx, row_data in enumerate(talepler):
for col_idx, col_data in enumerate(row_data):
item = QtWidgets.QTableWidgetItem(str(col_data))
if row_data[4] == 'Onaylandı':
item.setBackground(QtGui.QColor(144, 238, 144)) # Yeşil renk
elif row_data[4] == 'Reddedildi':
item.setBackground(QtGui.QColor(255, 99, 71)) # Kırmızı renk
self.talep_tablosu.setItem(row_idx, col_idx, item)
# Onayla butonu ekle
onayla_btn = QtWidgets.QPushButton('Onayla')
onayla_btn.clicked.connect(lambda _, id=row_data[0]: self.talep_onayla(id))
self.talep_tablosu.setCellWidget(row_idx, 5, onayla_btn)
# Reddet butonu ekle
reddet_btn = QtWidgets.QPushButton('Reddet')
reddet_btn.clicked.connect(lambda _, id=row_data[0]: self.talep_reddet(id, row_idx))
self.talep_tablosu.setCellWidget(row_idx, 6, reddet_btn)
# Reddetme nedeni girişi
red_nedeni_input = QtWidgets.QLineEdit()
self.talep_tablosu.setCellWidget(row_idx, 7, red_nedeni_input)
onay_penceresi.setLayout(layout)
onay_penceresi.show()
self.onay_penceresi = onay_penceresi
def talep_onayla(self, talep_id):
# Nöbet değişim talebini onaylama işlemi
cursor.execute('''
UPDATE nobet_degisim_talepleri
SET durum = 'Onaylandı'
WHERE id = ?
''', (talep_id,))
conn.commit()
self.nobet_degisim_talepleri_onay()
def talep_reddet(self, talep_id, row_idx):
# Reddetme nedeni al
red_nedeni_widget = self.talep_tablosu.cellWidget(row_idx, 7)
red_nedeni = red_nedeni_widget.text() if red_nedeni_widget else ''
if not red_nedeni:
QtWidgets.QMessageBox.warning(self, 'Hata', 'Reddetme nedeni girilmelidir.')
return
# Nöbet değişim talebini reddetme işlemi
cursor.execute('''
UPDATE nobet_degisim_talepleri
SET durum = 'Reddedildi', red_nedeni = ?
WHERE id = ?
''', (red_nedeni, talep_id))
conn.commit()
self.nobet_degisim_talepleri_onay()
def reddedilen_onaylanan_rapor(self):
# Reddedilen ve onaylanan taleplerin rapor penceresi oluşturma
rapor_penceresi = QtWidgets.QWidget()
rapor_penceresi.setWindowTitle('Reddedilen ve Onaylanan Taleplerin Raporu')
rapor_penceresi.setGeometry(200, 200, 800, 400)
layout = QtWidgets.QVBoxLayout()
# TreeView oluşturma
self.reddedilen_onaylanan_tablosu = QtWidgets.QTreeWidget()
self.reddedilen_onaylanan_tablosu.setHeaderLabels(['ID', 'Talep Eden Sicil', 'Değişim Yapılacak Sicil', 'Tarih', 'Durum', 'Reddetme Nedeni'])
layout.addWidget(self.reddedilen_onaylanan_tablosu)
# Verileri yükleme
talepler = cursor.execute('''
SELECT id, talep_eden_sicil, degisim_yapilacak_sicil, tarih, durum, red_nedeni
FROM nobet_degisim_talepleri
WHERE durum IN ('Onaylandı', 'Reddedildi')
''').fetchall()
for talep in talepler:
id, talep_eden_sicil, degisim_yapilacak_sicil, tarih, durum, red_nedeni = talep
item = QtWidgets.QTreeWidgetItem([str(id), str(talep_eden_sicil), str(degisim_yapilacak_sicil), str(tarih), durum, red_nedeni or ''])
if durum == 'Onaylandı':
item.setBackground(4, QtGui.QColor(144, 238, 144)) # Yeşil renk
elif durum == 'Reddedildi':
item.setBackground(4, QtGui.QColor(255, 99, 71)) # Kırmızı renk
self.reddedilen_onaylanan_tablosu.addTopLevelItem(item)
rapor_penceresi.setLayout(layout)
rapor_penceresi.show()
self.reddedilen_onaylanan_penceresi = rapor_penceresi
def mazeret_kontrol(self, mazeret, gun, nobet_tipi):
if not mazeret:
return True
# Check if person has "tek günler" or "çift günler" mazereti
if "tek günler" in mazeret.lower() and gun.day % 2 == 0:
return False
if "çift günler" in mazeret.lower() and gun.day % 2 != 0:
return False
# Check if person is available only on weekends or is exempt from weekends
if "hafta sonu" in mazeret.lower():
if gun.weekday() < 5: # Weekday check: 0 = Monday, ..., 4 = Friday
return False
if "hafta içi" in mazeret.lower() and gun.weekday() >= 5:
return False
# Check for "gece muaf" in case it's a night shift
if "gece muaf" in mazeret.lower() and "gece" in nobet_tipi:
return False
# Check if person is on leave during specific dates mentioned in their mazeret
# Example: "3-15 Ekim izinli"
izin_tarihleri = re.findall(r'(\d{1,2})-(\d{1,2}) (\w+)', mazeret)
for baslangic, bitis, ay in izin_tarihleri:
try:
# Handle Turkish month names by mapping to month numbers
ay_map = {
'Ocak': 1, 'Şubat': 2, 'Mart': 3, 'Nisan': 4, 'Mayıs': 5, 'Haziran': 6,
'Temmuz': 7, 'Ağustos': 8, 'Eylül': 9, 'Ekim': 10, 'Kasım': 11, 'Aralık': 12
}
ay_num = ay_map.get(ay.capitalize(), 0)
if ay_num == 0:
continue # Skip if month is not recognized
izin_baslangic = datetime(gun.year, ay_num, int(baslangic))
izin_bitis = datetime(gun.year, ay_num, int(bitis))
if izin_baslangic <= gun <= izin_bitis:
return False
except Exception as e:
# If there's an error in parsing, skip the mazeret
continue
return True
# Uygulamayı başlat
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
pencere = NobetTakipUygulamasi()
pencere.show()
sys.exit(app.exec_())
Yapılan Güncellemeler Detayları
Mazeret Kontrol Fonksiyonu (mazeret_kontrol
):
- Tarih Aralığı Kontrolü:
mazeret
sütununda belirtilen tarih aralıkları ("3-15 Ekim izinli"
) regex kullanılarak parse edilmekte ve bu tarihlerde nöbet ataması yapılması engellenmektedir.
- Tek/Çift Günler Kontrolü:
mazeret
içinde "tek günler"
veya "çift günler"
ifadesi geçiyorsa, günün tek/çift olmasına göre nöbet ataması kontrol edilmektedir.
- Hafta Sonu/Hafta İçi Kontrolü:
"hafta sonu"
veya "hafta içi"
ifadeleri dikkate alınarak nöbet günleri belirlenmektedir.
- Gece Nöbet Muafiyeti:
"gece muaf"
ifadesi geçiyorsa, gece nöbeti ataması engellenmektedir.
Nöbet Dağıtım Fonksiyonu (nobet_dagitim
):
- Detaylandırılmış mazeret kontrolleri uygulanarak, personelin uygun olmadığı günlerde nöbet atanması engellenmektedir.
- Geçmiş ayda aynı türde nöbet tutmuş personelin tekrar aynı nöbete atanması engellenmektedir.
Genel İyileştirmeler:
- Hafta İçi ve Hafta Sonu Nöbet Tipleri: Hafta içi gündüz, hafta sonu gündüz ve cuma gündüz nöbet tipleri için atamalar yapılmaktadır.
- Veritabanı Kontrolleri:
nobet
ve personel
tabloları arasında doğru ilişkilendirmeler yapılmış, eksik tablo durumunda otomatik olarak oluşturulması sağlanmıştır.
Kullanıcı Arayüzü:
- Nöbet Değişim Talepleri Onaylama: Onayla ve reddet butonları eklenerek, kullanıcıların nöbet değişim taleplerini yönetebilmesi sağlanmıştır.
- Raporlama: Onaylanan ve reddedilen taleplerin raporları oluşturularak, kullanıcıya görsel olarak sunulmaktadır.
Önemli Notlar
- Türkçe Ay İsimleri:
mazeret_kontrol
fonksiyonunda, Türkçe ay isimlerinin doğru şekilde parse edilmesi için bir ay_map
sözlüğü eklenmiştir. Eğer başka ay isimleri veya farklı formatlarda mazeretleriniz varsa, bu sözlüğü güncelleyebilirsiniz.
- Hata Yönetimi: Mazeret parse edilirken herhangi bir hata oluşması durumunda, ilgili mazeret atlanmaktadır. Bu, programın kesintisiz çalışmasını sağlar.
- Veri Doğrulama: Sicil numaralarının geçerli olup olmadığını kontrol eden mekanizmalar mevcuttur. Ancak, daha fazla doğrulama eklemek isterseniz, ilgili fonksiyonlara eklemeler yapabilirsiniz.
Sonuç
Bu güncellemelerle birlikte, programınız personel mazeretlerini daha detaylı ve hassas bir şekilde yönetebilecek hale gelmiştir. Nöbet dağıtımında adil ve doğru atamalar yapabilmeniz için gerekli kontroller eklenmiş olup, mevcut işlevler korunarak kodunuz genişletilmiştir.