-
Notifications
You must be signed in to change notification settings - Fork 16.9k
Description
#!/usr/bin/env python3
-- coding: utf-8 --
"""
Livestock Manager - Sheep & Goat & Feed Management System
Single-file prototype with SQLite + PySide6 GUI.
Features (first version):
- Animals registry (with automatic age calculation).
- Breeding records (with expected lambing/kidding date).
- Vaccinations & health log (basic).
- Feed items & feed logs (with automatic stock deduction).
- Tasks & alerts (simple to-do list).
- Settings panel (key/value, including gestation days).
- Basic tabs for: Dashboard, Herd, Breeding, Health, Feed, Pasture/Barn, Finance, Tasks, Settings.
This is a starting point that you can extend.
"""
import sys
import os
import sqlite3
from datetime import datetime, timedelta, date
from PySide6 import QtCore, QtGui, QtWidgets
APP_TITLE = "Livestock Manager"
DB_FILE = "livestock_manager.db"
---------------------- Database helpers ----------------------
def get_connection():
conn = sqlite3.connect(DB_FILE)
conn.row_factory = sqlite3.Row
return conn
def init_db():
conn = get_connection()
cur = conn.cursor()
# Animals
cur.execute("""
CREATE TABLE IF NOT EXISTS animals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tag TEXT UNIQUE,
species TEXT,
breed TEXT,
sex TEXT,
birth_date TEXT,
mother_tag TEXT,
father_tag TEXT,
origin TEXT,
status TEXT,
weight REAL,
notes TEXT
)
""")
# Breeding
cur.execute("""
CREATE TABLE IF NOT EXISTS breedings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
female_tag TEXT,
male_tag TEXT,
date TEXT,
expected_lambing_date TEXT,
method TEXT,
notes TEXT
)
""")
# Vaccinations / Health (basic)
cur.execute("""
CREATE TABLE IF NOT EXISTS vaccinations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
animal_tag TEXT,
date TEXT,
vaccine_name TEXT,
dose TEXT,
next_due_date TEXT,
notes TEXT
)
""")
cur.execute("""
CREATE TABLE IF NOT EXISTS health_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
animal_tag TEXT,
date TEXT,
event_type TEXT,
diagnosis TEXT,
treatment TEXT,
notes TEXT
)
""")
# Feed items and logs
cur.execute("""
CREATE TABLE IF NOT EXISTS feed_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
feed_type TEXT,
unit TEXT,
quantity REAL,
unit_cost REAL,
notes TEXT
)
""")
cur.execute("""
CREATE TABLE IF NOT EXISTS feed_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT,
feed_item_id INTEGER,
group_name TEXT,
num_heads INTEGER,
amount_per_head REAL,
total_amount REAL,
notes TEXT
)
""")
# Pasture & barns (structure only, can be extended later)
cur.execute("""
CREATE TABLE IF NOT EXISTS pastures (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
area REAL,
crop_type TEXT,
status TEXT,
notes TEXT
)
""")
cur.execute("""
CREATE TABLE IF NOT EXISTS pasture_usage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pasture_id INTEGER,
group_name TEXT,
start_date TEXT,
end_date TEXT,
num_heads INTEGER,
notes TEXT
)
""")
cur.execute("""
CREATE TABLE IF NOT EXISTS barns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
capacity INTEGER,
notes TEXT
)
""")
# Finance (structure)
cur.execute("""
CREATE TABLE IF NOT EXISTS sales (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT,
buyer TEXT,
animal_tag TEXT,
weight REAL,
price REAL,
notes TEXT
)
""")
cur.execute("""
CREATE TABLE IF NOT EXISTS purchases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT,
seller TEXT,
animal_tag TEXT,
weight REAL,
price REAL,
notes TEXT
)
""")
# Tasks
cur.execute("""
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
due_date TEXT,
completed INTEGER,
notes TEXT
)
""")
# Settings (key/value)
cur.execute("""
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT
)
""")
# Default settings
# Gestation days for sheep/goats (approx)
cur.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("gestation_days_sheep", "150"))
cur.execute("INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)", ("gestation_days_goat", "150"))
conn.commit()
conn.close()
def get_setting(key, default=None):
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT value FROM settings WHERE key = ?", (key,))
row = cur.fetchone()
conn.close()
if row:
return row["value"]
return default
def set_setting(key, value):
conn = get_connection()
cur = conn.cursor()
cur.execute("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", (key, value))
conn.commit()
conn.close()
---------------------- Utility functions ----------------------
def parse_date(text):
if not text:
return None
try:
return datetime.strptime(text, "%Y-%m-%d").date()
except ValueError:
return None
def format_date(d):
if not d:
return ""
if isinstance(d, (datetime, date)):
return d.strftime("%Y-%m-%d")
return str(d)
def calculate_age(birth_date_str):
d = parse_date(birth_date_str)
if not d:
return ""
today = date.today()
years = today.year - d.year - ((today.month, today.day) < (d.month, d.day))
months = (today.year - d.year) * 12 + today.month - d.month
return f"{years} سنة / {months} شهر"
---------------------- Dialogs ----------------------
class AnimalDialog(QtWidgets.QDialog):
def init(self, parent=None, data=None):
super().init(parent)
self.setWindowTitle("حيوان جديد" if data is None else "تعديل بيانات الحيوان")
self.data = data or {}
form = QtWidgets.QFormLayout(self)
self.tag_edit = QtWidgets.QLineEdit(self.data.get("tag", ""))
self.species_combo = QtWidgets.QComboBox()
self.species_combo.addItems(["غنم", "ماعز"])
if self.data.get("species"):
idx = self.species_combo.findText(self.data["species"])
if idx >= 0:
self.species_combo.setCurrentIndex(idx)
self.breed_edit = QtWidgets.QLineEdit(self.data.get("breed", ""))
self.sex_combo = QtWidgets.QComboBox()
self.sex_combo.addItems(["أنثى", "ذكر"])
if self.data.get("sex"):
idx = self.sex_combo.findText(self.data["sex"])
if idx >= 0:
self.sex_combo.setCurrentIndex(idx)
self.birth_date_edit = QtWidgets.QDateEdit()
self.birth_date_edit.setCalendarPopup(True)
self.birth_date_edit.setDisplayFormat("yyyy-MM-dd")
if self.data.get("birth_date"):
d = parse_date(self.data["birth_date"])
if d:
self.birth_date_edit.setDate(d)
else:
self.birth_date_edit.setDate(date.today())
self.mother_tag_edit = QtWidgets.QLineEdit(self.data.get("mother_tag", ""))
self.father_tag_edit = QtWidgets.QLineEdit(self.data.get("father_tag", ""))
self.origin_edit = QtWidgets.QLineEdit(self.data.get("origin", ""))
self.status_combo = QtWidgets.QComboBox()
self.status_combo.addItems(["في القطيع", "مباع", "نافق", "مذبوح", "تسمين", "فحل"])
if self.data.get("status"):
idx = self.status_combo.findText(self.data["status"])
if idx >= 0:
self.status_combo.setCurrentIndex(idx)
self.weight_spin = QtWidgets.QDoubleSpinBox()
self.weight_spin.setRange(0, 500)
self.weight_spin.setDecimals(2)
self.weight_spin.setValue(float(self.data.get("weight", 0) or 0))
self.notes_edit = QtWidgets.QPlainTextEdit(self.data.get("notes", ""))
form.addRow("رقم الحيوان:", self.tag_edit)
form.addRow("النوع:", self.species_combo)
form.addRow("السلالة:", self.breed_edit)
form.addRow("الجنس:", self.sex_combo)
form.addRow("تاريخ الميلاد:", self.birth_date_edit)
form.addRow("رقم الأم:", self.mother_tag_edit)
form.addRow("رقم الأب:", self.father_tag_edit)
form.addRow("المصدر:", self.origin_edit)
form.addRow("الحالة:", self.status_combo)
form.addRow("الوزن (كغم):", self.weight_spin)
form.addRow("ملاحظات:", self.notes_edit)
btn_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
btn_box.accepted.connect(self.accept)
btn_box.rejected.connect(self.reject)
form.addRow(btn_box)
def get_data(self):
return {
"tag": self.tag_edit.text().strip(),
"species": self.species_combo.currentText(),
"breed": self.breed_edit.text().strip(),
"sex": self.sex_combo.currentText(),
"birth_date": self.birth_date_edit.date().toString("yyyy-MM-dd"),
"mother_tag": self.mother_tag_edit.text().strip(),
"father_tag": self.father_tag_edit.text().strip(),
"origin": self.origin_edit.text().strip(),
"status": self.status_combo.currentText(),
"weight": self.weight_spin.value(),
"notes": self.notes_edit.toPlainText().strip(),
}
class BreedingDialog(QtWidgets.QDialog):
def init(self, parent=None, data=None):
super().init(parent)
self.setWindowTitle("تسجيل تزاوج" if data is None else "تعديل تزاوج")
self.data = data or {}
form = QtWidgets.QFormLayout(self)
self.female_tag_edit = QtWidgets.QLineEdit(self.data.get("female_tag", ""))
self.male_tag_edit = QtWidgets.QLineEdit(self.data.get("male_tag", ""))
self.date_edit = QtWidgets.QDateEdit()
self.date_edit.setCalendarPopup(True
)
self.date_edit.setDisplayFormat("yyyy-MM-dd")
if self.data.get("date"):
d = parse_date(self.data["date"])
if d:
self.date_edit.setDate(d)
else:
self.date_edit.setDate(date.today())
self.method_edit = QtWidgets.QLineEdit(self.data.get("method", "طبيعي"))
self.notes_edit = QtWidgets.QPlainTextEdit(self.data.get("notes", ""))
form.addRow("رقم الأنثى:", self.female_tag_edit)
form.addRow("رقم الذكر:", self.male_tag_edit)
form.addRow("تاريخ التزاوج:", self.date_edit)
form.addRow("طريقة التلقيح:", self.method_edit)
form.addRow("ملاحظات:", self.notes_edit)
btn_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
btn_box.accepted.connect(self.accept)
btn_box.rejected.connect(self.reject)
form.addRow(btn_box)
def get_data(self):
return {
"female_tag": self.female_tag_edit.text().strip(),
"male_tag": self.male_tag_edit.text().strip(),
"date": self.date_edit.date().toString("yyyy-MM-dd"),
"method": self.method_edit.text().strip(),
"notes": self.notes_edit.toPlainText().strip(),
}
class VaccinationDialog(QtWidgets.QDialog):
def init(self, parent=None, data=None):
super().init(parent)
self.setWindowTitle("تسجيل لقاح" if data is None else "تعديل لقاح")
self.data = data or {}
form = QtWidgets.QFormLayout(self)
self.animal_tag_edit = QtWidgets.QLineEdit(self.data.get("animal_tag", ""))
self.date_edit = QtWidgets.QDateEdit()
self.date_edit.setCalendarPopup(True)
self.date_edit.setDisplayFormat("yyyy-MM-dd")
if self.data.get("date"):
d = parse_date(self.data["date"])
if d:
self.date_edit.setDate(d)
else:
self.date_edit.setDate(date.today())
self.vaccine_name_edit = QtWidgets.QLineEdit(self.data.get("vaccine_name", ""))
self.dose_edit = QtWidgets.QLineEdit(self.data.get("dose", ""))
self.next_due_edit = QtWidgets.QDateEdit()
self.next_due_edit.setCalendarPopup(True)
self.next_due_edit.setDisplayFormat("yyyy-MM-dd")
if self.data.get("next_due_date"):
d = parse_date(self.data["next_due_date"])
if d:
self.next_due_edit.setDate(d)
else:
# default next dose after 12 months
self.next_due_edit.setDate(date.today().replace(year=date.today().year + 1))
self.notes_edit = QtWidgets.QPlainTextEdit(self.data.get("notes", ""))
form.addRow("رقم الحيوان:", self.animal_tag_edit)
form.addRow("تاريخ اللقاح:", self.date_edit)
form.addRow("اسم اللقاح:", self.vaccine_name_edit)
form.addRow("الجرعة:", self.dose_edit)
form.addRow("موعد الجرعة القادمة:", self.next_due_edit)
form.addRow("ملاحظات:", self.notes_edit)
btn_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
btn_box.accepted.connect(self.accept)
btn_box.rejected.connect(self.reject)
form.addRow(btn_box)
def get_data(self):
return {
"animal_tag": self.animal_tag_edit.text().strip(),
"date": self.date_edit.date().toString("yyyy-MM-dd"),
"vaccine_name": self.vaccine_name_edit.text().strip(),
"dose": self.dose_edit.text().strip(),
"next_due_date": self.next_due_edit.date().toString("yyyy-MM-dd"),
"notes": self.notes_edit.toPlainText().strip(),
}
class FeedItemDialog(QtWidgets.QDialog):
def init(self, parent=None, data=None):
super().init(parent)
self.setWindowTitle("عنصر علف جديد" if data is None else "تعديل عنصر علف")
self.data = data or {}
form = QtWidgets.QFormLayout(self)
self.name_edit = QtWidgets.QLineEdit(self.data.get("name", ""))
self.feed_type_edit = QtWidgets.QLineEdit(self.data.get("feed_type", ""))
self.unit_edit = QtWidgets.QLineEdit(self.data.get("unit", "كغم"))
self.quantity_spin = QtWidgets.QDoubleSpinBox()
self.quantity_spin.setRange(0, 1000000)
self.quantity_spin.setDecimals(3)
self.quantity_spin.setValue(float(self.data.get("quantity", 0) or 0))
self.unit_cost_spin = QtWidgets.QDoubleSpinBox()
self.unit_cost_spin.setRange(0, 1000000)
self.unit_cost_spin.setDecimals(3)
self.unit_cost_spin.setValue(float(self.data.get("unit_cost", 0) or 0))
self.notes_edit = QtWidgets.QPlainTextEdit(self.data.get("notes", ""))
form.addRow("اسم العلف:", self.name_edit)
form.addRow("النوع:", self.feed_type_edit)
form.addRow("وحدة القياس:", self.unit_edit)
form.addRow("الكمية الحالية:", self.quantity_spin)
form.addRow("سعر الوحدة:", self.unit_cost_spin)
form.addRow("ملاحظات:", self.notes_edit)
btn_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
btn_box.accepted.connect(self.accept)
btn_box.rejected.connect(self.reject)
form.addRow(btn_box)
def get_data(self):
return {
"name": self.name_edit.text().strip(),
"feed_type": self.feed_type_edit.text().strip(),
"unit": self.unit_edit.text().strip(),
"quantity": self.quantity_spin.value(),
"unit_cost": self.unit_cost_spin.value(),
"notes": self.notes_edit.toPlainText().strip(),
}
class FeedLogDialog(QtWidgets.QDialog):
def init(self, parent=None, feed_items=None):
super().init(parent)
self.setWindowTitle("تسجيل وجبة علف")
self.feed_items = feed_items or []
form = QtWidgets.QFormLayout(self)
self.feed_combo = QtWidgets.QComboBox()
for item in self.feed_items:
self.feed_combo.addItem(item["name"], item["id"])
self.date_edit = QtWidgets.QDateEdit()
self.date_edit.setCalendarPopup(True)
self.date_edit.setDisplayFormat("yyyy-MM-dd")
self.date_edit.setDate(date.today())
self.group_edit = QtWidgets.QLineEdit()
self.num_heads_spin = QtWidgets.QSpinBox()
self.num_heads_spin.setRange(1, 100000)
self.num_heads_spin.setValue(10)
self.amount_per_head_spin = QtWidgets.QDoubleSpinBox()
self.amount_per_head_spin.setRange(0, 100)
self.amount_per_head_spin.setDecimals(3)
self.amount_per_head_spin.setValue(1.0)
self.notes_edit = QtWidgets.QPlainTextEdit()
form.addRow("نوع العلف:", self.feed_combo)
form.addRow("التاريخ:", self.date_edit)
form.addRow("اسم المجموعة:", self.group_edit)
form.addRow("عدد الرؤوس:", self.num_heads_spin)
form.addRow("الكمية للرأس (وحدة):", self.amount_per_head_spin)
form.addRow("ملاحظات:", self.notes_edit)
btn_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
btn_box.accepted.connect(self.accept)
btn_box.rejected.connect(self.reject)
form.addRow(btn_box)
def get_data(self):
feed_id = self.feed_combo.currentData()
total = self.num_heads_spin.value() * self.amount_per_head_spin.value()
return {
"feed_item_id": feed_id,
"date": self.date_edit.date().toString("yyyy-MM-dd"),
"group_name": self.group_edit.text().strip(),
"num_heads": self.num_heads_spin.value(),
"amount_per_head": self.amount_per_head_spin.value(),
"total_amount": total,
"notes": self.notes_edit.toPlainText().strip(),
}
class TaskDialog(QtWidgets.QDialog):
def init(self, parent=None, data=None):
super().init(parent)
self.setWindowTitle("مهمة جديدة" if data is None else "تعديل مهمة")
self.data = data or {}
form = QtWidgets.QFormLayout(self)
self.title_edit = QtWidgets.QLineEdit(self.data.get("title", ""))
self.due_date_edit = QtWidgets.QDateEdit()
self.due_date_edit.setCalendarPopup(True)
self.due_date_edit.setDisplayFormat("yyyy-MM-dd")
if self.data.get("due_date"):
d = parse_date(self.data["due_date"])
if d:
self.due_date_edit.setDate(d)
else:
self.due_date_edit.setDate(date.today())
self.completed_check = QtWidgets.QCheckBox("منجز")
self.completed_check.setChecked(bool(self.data.get("completed")))
self.notes_edit = QtWidgets.QPlainTextEdit(self.data.get("notes", ""))
form.addRow("عنوان المهمة:", self.title_edit)
form.addRow("تاريخ الاستحقاق:", self.due_date_edit)
form.addRow("", self.completed_check)
form.addRow("ملاحظات:", self.notes_edit)
btn_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
btn_box.accepted.connect(self.accept)
btn_box.rejected.connect(self.reject)
form.addRow(btn_box)
def get_data(self):
return {
"title": self.title_edit.text().strip(),
"due_date": self.due_date_edit.date().toString("yyyy-MM-dd"),
"completed": 1 if self.completed_check.isChecked() else 0,
"notes": self.notes_edit.toPlainText().strip(),
}
class SettingDialog(QtWidgets.QDialog):
def init(self, parent=None, data=None):
super().init(parent)
self.setWindowTitle("إعداد جديد" if data is None else "تعديل إعداد")
self.data = data or {}
form = QtWidgets.QFormLayout(self)
self.key_edit = QtWidgets.QLineEdit(self.data.get("key", ""))
if self.data.get("key"):
self.key_edit.setReadOnly(True)
self.value_edit = QtWidgets.QLineEdit(self.data.get("value", ""))
form.addRow("مفتاح الإعداد:", self.key_edit)
form.addRow("القيمة:", self.value_edit)
btn_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
btn_box.accepted.connect(self.accept)
btn_box.rejected.connect(self.reject)
form.addRow(btn_box)
def get_data(self):
return {
"key": self.key_edit.text().strip(),
"value": self.value_edit.text().strip(),
}
---------------------- Main Window ----------------------
class MainWindow(QtWidgets.QMainWindow):
def init(self):
super().init()
self.setWindowTitle(APP_TITLE)
self.resize(1200, 700)
self.tabs = QtWidgets.QTabWidget()
self.setCentralWidget(self.tabs)
self._setup_style()
self._init_tabs()
self._load_all()
def _setup_style(self):
self.setStyleSheet("""
QMainWindow {
background-color: #101820;
color: #ffffff;
}
QTabWidget::pane {
border: 1px solid #444;
}
QTabBar::tab {
background: #1a2634;
color: #ffffff;
padding: 8px 16px;
margin-right: 2px;
}
QTabBar::tab:selected {
background: #00a3a3;
}
QTableWidget {
background-color: #182430;
color: #f0f0f0;
gridline-color: #333;
}
QHeaderView::section {
background-color: #223347;
color: #ffffff;
padding: 4px;
}
QPushButton {
background-color: #00a3a3;
border-radius: 4px;
padding: 6px 12px;
color: white;
}
QPushButton:hover {
background-color: #00c0c0;
}
QLineEdit, QComboBox, QDateEdit, QDoubleSpinBox, QSpinBox, QPlainTextEdit {
background-color: #121a24;
color: #ffffff;
border: 1px solid #444;
}
""")
# ---------- Tabs initialization ---------- #
def _init_tabs(self):
self._init_dashboard_tab()
self._init_herd_tab()
self._init_breeding_tab()
self._init_health_tab()
self._init_feed_tab()
self._init_pasture_tab()
self._init_finance_tab()
self._init_tasks_tab()
self._init_settings_tab()
def _init_dashboard_tab(self):
self.dashboard_tab = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(self.dashboard_tab)
self.lbl_summary = QtWidgets.QLabel("ملخص القطيع والأعلاف")
self.lbl_summary.setAlignment(QtCore.Qt.AlignCenter)
font = self.lbl_summary.font()
font.setPointSize(16)
font.setBold(True)
self.lbl_summary.setFont(font)
self.dashboard_text = QtWidgets.QTextBrowser()
self.dashboard_text.setReadOnly(True)
layout.addWidget(self.lbl_summary)
layout.addWidget(self.dashboard_text)
self.tabs.addTab(self.dashboard_tab, "لوحة المعلومات")
def _init_herd_tab(self):
self.herd_tab = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout(self.herd_tab)
btn_layout = QtWidgets.QHBoxLayout()
self.btn_add_animal = QtWidgets.QPushButton("إضافة حيوان")
self.btn_edit_animal = QtWidgets.QPushButton("تعديل")
self.btn_delete_animal = QtWidgets.QPushButton("حذف")
self.btn_refresh_animals = QtWidgets.QPushButton("تحديث")
btn_layout.addWidget(self.btn_add_animal)
btn_layout.addWidget(self.btn_edit_animal)
btn_layout.addWidget(self.btn_delete_animal)
btn_layout.addStretch()
btn_layout.addWidget(self.btn_refresh_animals)
self.table_animals = QtWidgets.QTableWidget()
self.table_animals.setColumnCount(10)
self.table_animals.setHorizontalHeaderLabels([
"ID", "الرقم", "النوع", "السلالة", "الجنس", "تاريخ الميلاد", "العمر", "الحالة", "الوزن", "ملاحظات"
])
self.table_animals.horizontalHeader().setStretchLastSection(True)
self.table_animals.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table_animals.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
vbox.addLayout(btn_layout)
vbox.addWidget(self.table_animals)
self.btn_add_animal.clicked.connect(self.add_animal)
self.btn_edit_animal.clicked.connect(self.edit_animal)
self.btn_delete_animal.clicked.connect(self.delete_animal)
self.btn_refresh_animals.clicked.connect(self.load_animals)
self.tabs.addTab(self.herd_tab, "القطيع")
def _init_breeding_tab(self):
self.breeding_tab = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout(self.breeding_tab)
btn_layout = QtWidgets.QHBoxLayout()
self.btn_add_breeding = QtWidgets.QPushButton("تسجيل تزاوج")
self.btn_delete_breeding = QtWidgets.QPushButton("حذف")
self.btn_refresh_breeding = QtWidgets.QPushButton("تحديث")
btn_layout.addWidget(self.btn_add_breeding)
btn_layout.addWidget(self.btn_delete_breeding)
btn_layout.addStretch()
btn_layout.addWidget(self.btn_refresh_breeding)
self.table_breeding = QtWidgets.QTableWidget()
self.table_breeding.setColumnCount(7)
self.table_breeding.setHorizontalHeaderLabels([
"ID", "الأنثى", "الذكر", "تاريخ التزاوج", "تاريخ الولادة المتوقع", "الطريقة", "ملاحظات"
])
self.table_breeding.horizontalHeader().setStretchLastSection(True)
self.table_breeding.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table_breeding.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
vbox.addLayout(btn_layout)
vbox.addWidget(self.table_breeding)
self.btn_add_breeding.clicked.connect(self.add_breeding)
self.btn_delete_breeding.clicked.connect(self.delete_breeding)
self.btn_refresh_breeding.clicked.connect(self.load_breeding)
self.tabs.addTab(self.breeding_tab, "التناسل والولادات")
def _init_health_tab(self):
self.health_tab = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout(self.health_tab)
# Vaccinations table
vacc_group = QtWidgets.QGroupBox("اللقاحات")
vacc_layout = QtWidgets.QVBoxLayout(vacc_group)
btn_v_layout = QtWidgets.QHBoxLayout()
self.btn_add_vacc = QtWidgets.QPushButton("إضافة لقاح")
self.btn_delete_vacc = QtWidgets.QPushButton("حذف")
self.btn_refresh_vacc = QtWidgets.QPushButton("تحديث")
btn_v_layout.addWidget(self.btn_add_vacc)
btn_v_layout.addWidget(self.btn_delete_vacc)
btn_v_layout.addStretch()
btn_v_layout.addWidget(self.btn_refresh_vacc)
self.table_vacc = QtWidgets.QTableWidget()
self.table_vacc.setColumnCount(7)
self.table_vacc.setHorizontalHeaderLabels([
"ID", "رقم الحيوان", "تاريخ اللقاح", "اسم اللقاح", "الجرعة", "الجرعة القادمة", "ملاحظات"
])
self.table_vacc.horizontalHeader().setStretchLastSection(True)
self.table_vacc.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table_vacc.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
vacc_layout.addLayout(btn_v_layout)
vacc_layout.addWidget(self.table_vacc)
vbox.addWidget(vacc_group)
self.btn_add_vacc.clicked.connect(self.add_vaccination)
self.btn_delete_vacc.clicked.connect(self.delete_vaccination)
self.btn_refresh_vacc.clicked.connect(self.load_vaccinations)
self.tabs.addTab(self.health_tab, "الصحة واللقاحات")
def _init_feed_tab(self):
self.feed_tab = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout(self.feed_tab)
# Feed items
group_items = QtWidgets.QGroupBox("مخزون الأعلاف")
items_layout = QtWidgets.QVBoxLayout(group_items)
btn_layout = QtWidgets.QHBoxLayout()
self.btn_add_feed = QtWidgets.QPushButton("إضافة علف")
self.btn_edit_feed = QtWidgets.QPushButton("تعديل")
self.btn_delete_feed = QtWidgets.QPushButton("حذف")
self.btn_refresh_feed = QtWidgets.QPushButton("تحديث")
btn_layout.addWidget(self.btn_add_feed)
btn_layout.addWidget(self.btn_edit_feed)
btn_layout.addWidget(self.btn_delete_feed)
btn_layout.addStretch()
btn_layout.addWidget(self.btn_refresh_feed)
self.table_feed = QtWidgets.QTableWidget()
self.table_feed.setColumnCount(7)
self.table_feed.setHorizontalHeaderLabels([
"ID", "اسم العلف", "النوع", "الوحدة", "الكمية", "سعر الوحدة", "ملاحظات"
])
self.table_feed.horizontalHeader().setStretchLastSection(True)
self.table_feed.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table_feed.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
items_layout.addLayout(btn_layout)
items_layout.addWidget(self.table_feed)
# Feed logs
group_logs = QtWidgets.QGroupBox("سجل الوجبات")
logs_layout = QtWidgets.QVBoxLayout(group_logs)
btn_l_layout = QtWidgets.QHBoxLayout()
self.btn_add_feed_log = QtWidgets.QPushButton("تسجيل وجبة")
self.btn_refresh_feed_log = QtWidgets.QPushButton("تحديث السجل")
btn_l_layout.addWidget(self.btn_add_feed_log)
btn_l_layout.addStretch()
btn_l_layout.addWidget(self.btn_refresh_feed_log)
self.table_feed_logs = QtWidgets.QTableWidget()
self.table_feed_logs.setColumnCount(8)
self.table_feed_logs.setHorizontalHeaderLabels([
"ID", "التاريخ", "العلف", "المجموعة", "عدد الرؤوس", "كمية للرأس", "الإجمالي", "ملاحظات"
])
self.table_feed_logs.horizontalHeader().setStretchLastSection(True)
self.table_feed_logs.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table_feed_logs.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
logs_layout.addLayout(btn_l_layout)
logs_layout.addWidget(self.table_feed_logs)
vbox.addWidget(group_items)
vbox.addWidget(group_logs)
self.btn_add_feed.clicked.connect(self.add_feed_item)
self.btn_edit_feed.clicked.connect(self.edit_feed_item)
self.btn_delete_feed.clicked.connect(self.delete_feed_item)
self.btn_refresh_feed.clicked.connect(self.load_feed_items)
self.btn_add_feed_log.clicked.connect(self.add_feed_log)
self.btn_refresh_feed_log.clicked.connect(self.load_feed_logs)
self.tabs.addTab(self.feed_tab, "الأعلاف والمخزون")
def _init_pasture_tab(self):
self.pasture_tab = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout(self.pasture_tab)
label = QtWidgets.QLabel(
"هنا يمكنك مستقبلاً إدارة المراعي والحظائر والرعي الدوراني.\n"
"الهيكل موجود في قاعدة البيانات ويمكن توسيعه حسب احتياجك."
)
label.setAlignment(QtCore.Qt.AlignCenter)
vbox.addWidget(label)
self.tabs.addTab(self.pasture_tab, "المراعي والحظائر")
def _init_finance_tab(self):
self.finance_tab = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout(self.finance_tab)
label = QtWidgets.QLabel(
"الوحدة المالية (المبيعات والمشتريات) جاهزة في قاعدة البيانات\n"
"ويمكن لاحقاً إضافة شاشات تفصيلية للحسابات والتقارير."
)
label.setAlignment(QtCore.Qt.AlignCenter)
vbox.addWidget(label)
self.tabs.addTab(self.finance_tab, "المبيعات والمالية")
def _init_tasks_tab(self):
self.tasks_tab = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout(self.tasks_tab)
btn_layout = QtWidgets.QHBoxLayout()
self.btn_add_task = QtWidgets.QPushButton("إضافة مهمة")
self.btn_edit_task = QtWidgets.QPushButton("تعديل")
self.btn_delete_task = QtWidgets.QPushButton("حذف")
self.btn_refresh_tasks = QtWidgets.QPushButton("تحديث")
btn_layout.addWidget(self.btn_add_task)
btn_layout.addWidget(self.btn_edit_task)
btn_layout.addWidget(self.btn_delete_task)
btn_layout.addStretch()
btn_layout.addWidget(self.btn_refresh_tasks)
self.table_tasks = QtWidgets.QTableWidget()
self.table_tasks.setColumnCount(5)
self.table_tasks.setHorizontalHeaderLabels([
"ID", "العنوان", "تاريخ الاستحقاق", "منجز", "ملاحظات"
])
self.table_tasks.horizontalHeader().setStretchLastSection(True)
self.table_tasks.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table_tasks.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
vbox.addLayout(btn_layout)
vbox.addWidget(self.table_tasks)
self.btn_add_task.clicked.connect(self.add_task)
self.btn_edit_task.clicked.connect(self.edit_task)
self.btn_delete_task.clicked.connect(self.delete_task)
self.btn_refresh_tasks.clicked.connect(self.load_tasks)
self.tabs.addTab(self.tasks_tab, "المهام والتنبيهات")
def _init_settings_tab(self):
self.settings_tab = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout(self.settings_tab)
btn_layout = QtWidgets.QHBoxLayout()
self.btn_add_setting = QtWidgets.QPushButton("إضافة إعداد")
self.btn_edit_setting = QtWidgets.QPushButton("تعديل")
self.btn_delete_setting = QtWidgets.QPushButton("حذف")
self.btn_refresh_settings = QtWidgets.QPushButton("تحديث")
btn_layout.addWidget(self.btn_add_setting)
btn_layout.addWidget(self.btn_edit_setting)
btn_layout.addWidget(self.btn_delete_setting)
btn_layout.addStretch()
btn_layout.addWidget(self.btn_refresh_settings)
self.table_settings = QtWidgets.QTableWidget()
self.table_settings.setColumnCount(2)
self.table_settings.setHorizontalHeaderLabels(["المفتاح", "القيمة"])
self.table_settings.horizontalHeader().setStretchLastSection(True)
self.table_settings.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table_settings.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
vbox.addLayout(btn_layout)
vbox.addWidget(self.table_settings)
self.btn_add_setting.clicked.connect(self.add_setting)
self.btn_edit_setting.clicked.connect(self.edit_setting)
self.btn_delete_setting.clicked.connect(self.delete_setting)
self.btn_refresh_settings.clicked.connect(self.load_settings)
self.tabs.addTab(self.settings_tab, "الإعدادات")
# ---------- Data loading ---------- #
def _load_all(self):
self.load_animals()
self.load_breeding()
self.load_vaccinations()
self.load_feed_items()
self.load_feed_logs()
self.load_tasks()
self.load_settings()
self.refresh_dashboard()
def refresh_dashboard(self):
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT COUNT(*) AS c FROM animals WHERE status = 'في القطيع'")
in_herd = cur.fetchone()["c"]
cur.execute("SELECT COUNT(*) AS c FROM animals")
total_animals = cur.fetchone()["c"]
cur.execute("SELECT IFNULL(SUM(quantity),0) AS qty FROM feed_items")
total_feed = cur.fetchone()["qty"]
cur.execute("SELECT COUNT(*) AS c FROM tasks WHERE completed = 0")
pending_tasks = cur.fetchone()["c"]
conn.close()
text = []
text.append(f"عدد الرؤوس في القطيع حالياً: {in_herd}")
text.append(f"إجمالي عدد الحيوانات (كل الحالات): {total_animals}")
text.append(f"إجمالي كمية الأعلاف المتوفرة (مجموع كل الأصناف): {total_feed:.3f}")
text.append(f"عدد المهام غير المنجزة: {pending_tasks}")
self.dashboard_text.setText("\n".join(text))
# ---------- Animals CRUD ---------- #
def load_animals(self):
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM animals ORDER BY id DESC")
rows = cur.fetchall()
conn.close()
self.table_animals.setRowCount(len(rows))
for r, row in enumerate(rows):
age_str = calculate_age(row["birth_date"])
data = [
row["id"],
row["tag"],
row["species"],
row["breed"],
row["sex"],
row["birth_date"],
age_str,
row["status"],
row["weight"],
row["notes"],
]
for c, value in enumerate(data):
item = QtWidgets.QTableWidgetItem(str(value))
if c == 0:
item.setData(QtCore.Qt.UserRole, row["id"])
self.table_animals.setItem(r, c, item)
self.table_animals.resizeColumnsToContents()
self.refresh_dashboard()
def get_selected_id(self, table):
selected = table.selectionModel().selectedRows()
if not selected:
return None
row_index = selected[0].row()
item = table.item(row_index, 0)
if not item:
return None
return int(item.text())
def add_animal(self):
dlg = AnimalDialog(self)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
if not data["tag"]:
QtWidgets.QMessageBox.warning(self, "تنبيه", "رقم الحيوان مطلوب.")
return
conn = get_connection()
cur = conn.cursor()
try:
cur.execute("""
INSERT INTO animals (tag, species, breed, sex, birth_date,
mother_tag, father_tag, origin, status, weight, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
data["tag"], data["species"], data["breed"], data["sex"], data["birth_date"],
data["mother_tag"], data["father_tag"], data["origin"], data["status"], data["weight"],
data["notes"]
))
conn.commit()
except sqlite3.IntegrityError:
QtWidgets.QMessageBox.warning(self, "خطأ", "رقم الحيوان موجود مسبقاً.")
finally:
conn.close()
self.load_animals()
def edit_animal(self):
animal_id = self.get_selected_id(self.table_animals)
if not animal_id:
return
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM animals WHERE id = ?", (animal_id,))
row = cur.fetchone()
conn.close()
if not row:
return
data = dict(row)
dlg = AnimalDialog(self, data=data)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
conn = get_connection()
cur = conn.cursor()
cur.execute("""
UPDATE animals SET tag=?, species=?, breed=?, sex=?, birth_date=?,
mother_tag=?, father_tag=?, origin=?, status=?, weight=?, notes=?
WHERE id=?
""", (
data["tag"], data["species"], data["breed"], data["sex"], data["birth_date"],
data["mother_tag"], data["father_tag"], data["origin"], data["status"], data["weight"],
data["notes"], animal_id
))
conn.commit()
conn.close()
self.load_animals()
def delete_animal(self):
animal_id = self.get_selected_id(self.table_animals)
if not animal_id:
return
if QtWidgets.QMessageBox.question(self, "تأكيد", "هل تريد حذف هذا الحيوان؟") != QtWidgets.QMessageBox.Yes:
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM animals WHERE id = ?", (animal_id,))
conn.commit()
conn.close()
self.load_animals()
# ---------- Breeding ---------- #
def load_breeding(self):
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM breedings ORDER BY id DESC")
rows = cur.fetchall()
conn.close()
self.table_breeding.setRowCount(len(rows))
for r, row in enumerate(rows):
data = [
row["id"],
row["female_tag"],
row["male_tag"],
row["date"],
row["expected_lambing_date"],
row["method"],
row["notes"],
]
for c, value in enumerate(data):
item = QtWidgets.QTableWidgetItem(str(value))
self.table_breeding.setItem(r, c, item)
self.table_breeding.resizeColumnsToContents()
def add_breeding(self):
dlg = BreedingDialog(self)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
if not data["female_tag"] or not data["male_tag"]:
QtWidgets.QMessageBox.warning(self, "تنبيه", "يجب إدخال رقم الأنثى والذكر.")
return
# Compute expected lambing date
# default: gestation_days_sheep setting
gest_days = int(get_setting("gestation_days_sheep", "150"))
d = parse_date(data["date"])
expected = d + timedelta(days=gest_days) if d else None
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO breedings (female_tag, male_tag, date, expected_lambing_date, method, notes)
VALUES (?, ?, ?, ?, ?, ?)
""", (
data["female_tag"], data["male_tag"], data["date"], format_date(expected),
data["method"], data["notes"]
))
conn.commit()
conn.close()
self.load_breeding()
def delete_breeding(self):
breeding_id = self.get_selected_id(self.table_breeding)
if not breeding_id:
return
if QtWidgets.QMessageBox.question(self, "تأكيد", "هل تريد حذف سجل التزاوج؟") != QtWidgets.QMessageBox.Yes:
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM breedings WHERE id = ?", (breeding_id,))
conn.commit()
conn.close()
self.load_breeding()
# ---------- Vaccinations ---------- #
def load_vaccinations(self):
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM vaccinations ORDER BY date DESC")
rows = cur.fetchall()
conn.close()
self.table_vacc.setRowCount(len(rows))
for r, row in enumerate(rows):
data = [
row["id"],
row["animal_tag"],
row["date"],
row["vaccine_name"],
row["dose"],
row["next_due_date"],
row["notes"],
]
for c, value in enumerate(data):
item = QtWidgets.QTableWidgetItem(str(value))
self.table_vacc.setItem(r, c, item)
self.table_vacc.resizeColumnsToContents()
def add_vaccination(self):
dlg = VaccinationDialog(self)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
if not data["animal_tag"]:
QtWidgets.QMessageBox.warning(self, "تنبيه", "رقم الحيوان مطلوب.")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO vaccinations (animal_tag, date, vaccine_name, dose, next_due_date, notes)
VALUES (?, ?, ?, ?, ?, ?)
""", (
data["animal_tag"], data["date"], data["vaccine_name"], data["dose"],
data["next_due_date"], data["notes"]
))
conn.commit()
conn.close()
self.load_vaccinations()
def delete_vaccination(self):
vacc_id = self.get_selected_id(self.table_vacc)
if not vacc_id:
return
if QtWidgets.QMessageBox.question(self, "تأكيد", "هل تريد حذف هذا اللقاح؟") != QtWidgets.QMessageBox.Yes:
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM vaccinations WHERE id = ?", (vacc_id,))
conn.commit()
conn.close()
self.load_vaccinations()
# ---------- Feed items & logs ---------- #
def load_feed_items(self):
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM feed_items ORDER BY id DESC")
rows = cur.fetchall()
conn.close()
self.table_feed.setRowCount(len(rows))
for r, row in enumerate(rows):
data = [
row["id"],
row["name"],
row["feed_type"],
row["unit"],
row["quantity"],
row["unit_cost"],
row["notes"],
]
for c, value in enumerate(data):
item = QtWidgets.QTableWidgetItem(str(value))
self.table_feed.setItem(r, c, item)
self.table_feed.resizeColumnsToContents()
self.refresh_dashboard()
def add_feed_item(self):
dlg = FeedItemDialog(self)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
if not data["name"]:
QtWidgets.QMessageBox.warning(self, "تنبيه", "اسم العلف مطلوب.")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO feed_items (name, feed_type, unit, quantity, unit_cost, notes)
VALUES (?, ?, ?, ?, ?, ?)
""", (
data["name"], data["feed_type"], data["unit"], data["quantity"],
data["unit_cost"], data["notes"]
))
conn.commit()
conn.close()
self.load_feed_items()
def edit_feed_item(self):
feed_id = self.get_selected_id(self.table_feed)
if not feed_id:
return
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM feed_items WHERE id = ?", (feed_id,))
row = cur.fetchone()
conn.close()
if not row:
return
data = dict(row)
dlg = FeedItemDialog(self, data=data)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
conn = get_connection()
cur = conn.cursor()
cur.execute("""
UPDATE feed_items
SET name=?, feed_type=?, unit=?, quantity=?, unit_cost=?, notes=?
WHERE id=?
""", (
data["name"], data["feed_type"], data["unit"], data["quantity"],
data["unit_cost"], data["notes"], feed_id
))
conn.commit()
conn.close()
self.load_feed_items()
def delete_feed_item(self):
feed_id = self.get_selected_id(self.table_feed)
if not feed_id:
return
if QtWidgets.QMessageBox.question(self, "تأكيد", "هل تريد حذف هذا العلف؟") != QtWidgets.QMessageBox.Yes:
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM feed_items WHERE id = ?", (feed_id,))
conn.commit()
conn.close()
self.load_feed_items()
def load_feed_logs(self):
conn = get_connection()
cur = conn.cursor()
cur.execute("""
SELECT fl.*, fi.name AS feed_name
FROM feed_logs fl
LEFT JOIN feed_items fi ON fi.id = fl.feed_item_id
ORDER BY fl.date DESC, fl.id DESC
""")
rows = cur.fetchall()
conn.close()
self.table_feed_logs.setRowCount(len(rows))
for r, row in enumerate(rows):
data = [
row["id"],
row["date"],
row["feed_name"] or "",
row["group_name"],
row["num_heads"],
row["amount_per_head"],
row["total_amount"],
row["notes"],
]
for c, value in enumerate(data):
item = QtWidgets.QTableWidgetItem(str(value))
self.table_feed_logs.setItem(r, c, item)
self.table_feed_logs.resizeColumnsToContents()
def add_feed_log(self):
# Load feed items
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT id, name FROM feed_items ORDER BY name")
feed_items = [dict(row) for row in cur.fetchall()]
conn.close()
if not feed_items:
QtWidgets.QMessageBox.warning(self, "تنبيه", "لا توجد أعلاف في المخزون. أضف عناصر علف أولاً.")
return
dlg = FeedLogDialog(self, feed_items=feed_items)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
conn = get_connection()
cur = conn.cursor()
# Insert log
cur.execute("""
INSERT INTO feed_logs (date, feed_item_id, group_name, num_heads,
amount_per_head, total_amount, notes)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (
data["date"], data["feed_item_id"], data["group_name"], data["num_heads"],
data["amount_per_head"], data["total_amount"], data["notes"]
))
# Deduct from feed_items
cur.execute("UPDATE feed_items SET quantity = quantity - ? WHERE id = ?",
(data["total_amount"], data["feed_item_id"]))
conn.commit()
conn.close()
self.load_feed_items()
self.load_feed_logs()
# ---------- Tasks ---------- #
def load_tasks(self):
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM tasks ORDER BY completed ASC, due_date ASC")
rows = cur.fetchall()
conn.close()
self.table_tasks.setRowCount(len(rows))
for r, row in enumerate(rows):
data = [
row["id"],
row["title"],
row["due_date"],
"نعم" if row["completed"] else "لا",
row["notes"],
]
for c, value in enumerate(data):
item = QtWidgets.QTableWidgetItem(str(value))
self.table_tasks.setItem(r, c, item)
self.table_tasks.resizeColumnsToContents()
self.refresh_dashboard()
def add_task(self):
dlg = TaskDialog(self)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
if not data["title"]:
QtWidgets.QMessageBox.warning(self, "تنبيه", "عنوان المهمة مطلوب.")
return
conn = get_connection()
cur = conn.cursor()
cur.execute("""
INSERT INTO tasks (title, due_date, completed, notes)
VALUES (?, ?, ?, ?)
""", (
data["title"], data["due_date"], data["completed"], data["notes"]
))
conn.commit()
conn.close()
self.load_tasks()
def edit_task(self):
task_id = self.get_selected_id(self.table_tasks)
if not task_id:
return
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM tasks WHERE id = ?", (task_id,))
row = cur.fetchone()
conn.close()
if not row:
return
data = dict(row)
dlg = TaskDialog(self, data=data)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
conn = get_connection()
cur = conn.cursor()
cur.execute("""
UPDATE tasks SET title=?, due_date=?, completed=?, notes=? WHERE id=?
""", (
data["title"], data["due_date"], data["completed"], data["notes"], task_id
))
conn.commit()
conn.close()
self.load_tasks()
def delete_task(self):
task_id = self.get_selected_id(self.table_tasks)
if not task_id:
return
if QtWidgets.QMessageBox.question(self, "تأكيد", "هل تريد حذف هذه المهمة؟") != QtWidgets.QMessageBox.Yes:
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM tasks WHERE id = ?", (task_id,))
conn.commit()
conn.close()
self.load_tasks()
# ---------- Settings ---------- #
def load_settings(self):
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT * FROM settings ORDER BY key")
rows = cur.fetchall()
conn.close()
self.table_settings.setRowCount(len(rows))
for r, row in enumerate(rows):
item_key = QtWidgets.QTableWidgetItem(row["key"])
item_val = QtWidgets.QTableWidgetItem(row["value"])
self.table_settings.setItem(r, 0, item_key)
self.table_settings.setItem(r, 1, item_val)
self.table_settings.resizeColumnsToContents()
def add_setting(self):
dlg = SettingDialog(self)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
if not data["key"]:
QtWidgets.QMessageBox.warning(self, "تنبيه", "مفتاح الإعداد مطلوب.")
return
set_setting(data["key"], data["value"])
self.load_settings()
def edit_setting(self):
selected = self.table_settings.selectionModel().selectedRows()
if not selected:
return
row_index = selected[0].row()
key_item = self.table_settings.item(row_index, 0)
val_item = self.table_settings.item(row_index, 1)
if not key_item:
return
data = {"key": key_item.text(), "value": val_item.text()}
dlg = SettingDialog(self, data=data)
if dlg.exec() == QtWidgets.QDialog.Accepted:
data = dlg.get_data()
set_setting(data["key"], data["value"])
self.load_settings()
def delete_setting(self):
selected = self.table_settings.selectionModel().selectedRows()
if not selected:
return
row_index = selected[0].row()
key_item = self.table_settings.item(row_index, 0)
if not key_item:
return
key = key_item.text()
if QtWidgets.QMessageBox.question(self, "تأكيد", f"حذف الإعداد '{key}'؟") != QtWidgets.QMessageBox.Yes:
return
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM settings WHERE key = ?", (key,))
conn.commit()
conn.close()
self.load_settings()
---------------------- main ----------------------
def main():
init_db()
app = QtWidgets.QApplication(sys.argv)
app.setApplicationName(APP_TITLE)
window = MainWindow()
window.show()
sys.exit(app.exec())
if name == "main":
main()