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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions