Skip to content

sheep #327

@sauds91-boop

Description

@sauds91-boop

#!/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()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions