Publier Globinours 1.0.0-rc.3
This commit is contained in:
parent
ea8c24d622
commit
9a2b4068da
325 changed files with 38230 additions and 20 deletions
443
app/Controllers/AnimalRelationshipActions.php
Normal file
443
app/Controllers/AnimalRelationshipActions.php
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
trait AnimalRelationshipActions
|
||||
{
|
||||
public static function litterForm(): void
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$sourceAnimalId = (int) ($_GET['animal_id'] ?? 0);
|
||||
$motherId = 0;
|
||||
$fatherId = 0;
|
||||
|
||||
if ($sourceAnimalId > 0) {
|
||||
$stmt = $db->prepare(
|
||||
'SELECT id, sex FROM animals WHERE id = :id AND deleted_at IS NULL AND archived_at IS NULL',
|
||||
);
|
||||
$stmt->execute([':id' => $sourceAnimalId]);
|
||||
$source = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$source) {
|
||||
http_response_code(404);
|
||||
echo h(t('error.animal_not_found'));
|
||||
return;
|
||||
}
|
||||
if (($source['sex'] ?? '') === 'F') {
|
||||
$motherId = $sourceAnimalId;
|
||||
}
|
||||
if (($source['sex'] ?? '') === 'M') {
|
||||
$fatherId = $sourceAnimalId;
|
||||
}
|
||||
}
|
||||
|
||||
$eligibleSql = "
|
||||
SELECT id, name FROM animals
|
||||
WHERE deleted_at IS NULL AND archived_at IS NULL AND lower(species) IN ('chat', 'cat') AND sex = :sex
|
||||
AND lower(status) NOT IN ('adopte', 'adopté', 'decede', 'décédé')
|
||||
ORDER BY name COLLATE NOCASE
|
||||
";
|
||||
$stmt = $db->prepare($eligibleSql);
|
||||
$stmt->execute([':sex' => 'F']);
|
||||
$mothers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$stmt->execute([':sex' => 'M']);
|
||||
$fathers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$existingKittens = $db
|
||||
->query(
|
||||
"SELECT a.id,a.name,a.internal_code,a.color,a.sex,a.status,a.archived_at FROM animals a WHERE a.deleted_at IS NULL AND lower(a.species) IN ('chat','cat') AND NOT EXISTS (SELECT 1 FROM litter_kittens lk WHERE lk.animal_id=a.id) ORDER BY CASE WHEN a.archived_at IS NULL THEN 0 ELSE 1 END,a.name COLLATE NOCASE,a.id",
|
||||
)
|
||||
->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
render('litter_form.php', [
|
||||
'title' => t('litter.create'),
|
||||
'pageDescription' => 'Création groupée des fiches d’une portée de chatons.',
|
||||
'sourceAnimalId' => $sourceAnimalId,
|
||||
'motherId' => $motherId,
|
||||
'fatherId' => $fatherId,
|
||||
'mothers' => $mothers,
|
||||
'fathers' => $fathers,
|
||||
'existingKittens' => $existingKittens,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function saveLitter(): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
http_response_code(405);
|
||||
return;
|
||||
}
|
||||
$db = DB::pdo();
|
||||
$motherId = (int) ($_POST['mother_id'] ?? 0) ?: null;
|
||||
$fatherId = (int) ($_POST['father_id'] ?? 0) ?: null;
|
||||
$birthDate = trim((string) ($_POST['birth_date'] ?? '')) ?: null;
|
||||
$kittenMode = in_array((string) ($_POST['kitten_mode'] ?? 'new'), ['new', 'existing'], true)
|
||||
? (string) $_POST['kitten_mode']
|
||||
: 'new';
|
||||
if ($birthDate !== null && preg_match('/^(\d{2})\/(\d{2})\/(\d{4})$/', $birthDate, $dateParts)) {
|
||||
$birthDate = $dateParts[3] . '-' . $dateParts[2] . '-' . $dateParts[1];
|
||||
}
|
||||
$kittens = is_array($_POST['kittens'] ?? null) ? array_values($_POST['kittens']) : [];
|
||||
$existingKittenIds = is_array($_POST['existing_kitten_ids'] ?? null)
|
||||
? array_values(array_unique(array_filter(array_map('intval', $_POST['existing_kitten_ids']))))
|
||||
: [];
|
||||
if ($kittenMode === 'new') {
|
||||
$existingKittenIds = [];
|
||||
} else {
|
||||
$kittens = [];
|
||||
}
|
||||
|
||||
if (!$kittens && !$existingKittenIds) {
|
||||
http_response_code(400);
|
||||
echo h(t('litter.need_kitten'));
|
||||
return;
|
||||
}
|
||||
if ($motherId !== null && $fatherId !== null && $motherId === $fatherId) {
|
||||
http_response_code(400);
|
||||
echo h(t('litter.different_parents'));
|
||||
return;
|
||||
}
|
||||
if ($birthDate !== null && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $birthDate)) {
|
||||
http_response_code(400);
|
||||
echo h(t('error.invalid_birth_date'));
|
||||
return;
|
||||
}
|
||||
|
||||
$parentIds = array_values(array_filter([$motherId, $fatherId]));
|
||||
if ($parentIds) {
|
||||
$placeholders = implode(',', array_fill(0, count($parentIds), '?'));
|
||||
$stmt = $db->prepare(
|
||||
"SELECT id, name, sex FROM animals WHERE id IN ($placeholders) AND deleted_at IS NULL AND archived_at IS NULL AND lower(species) IN ('chat','cat') AND lower(status) NOT IN ('adopte','adopté','decede','décédé')",
|
||||
);
|
||||
$stmt->execute($parentIds);
|
||||
$parents = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $parent) {
|
||||
$parents[(int) $parent['id']] = $parent;
|
||||
}
|
||||
if (
|
||||
($motherId && ($parents[$motherId]['sex'] ?? '') !== 'F') ||
|
||||
($fatherId && ($parents[$fatherId]['sex'] ?? '') !== 'M')
|
||||
) {
|
||||
http_response_code(400);
|
||||
echo h(t('litter.invalid_parent'));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$parents = [];
|
||||
}
|
||||
|
||||
if ($existingKittenIds) {
|
||||
$placeholders = implode(',', array_fill(0, count($existingKittenIds), '?'));
|
||||
$stmt = $db->prepare(
|
||||
"SELECT id FROM animals WHERE id IN ($placeholders) AND deleted_at IS NULL AND lower(species) IN ('chat','cat') AND id NOT IN (?,?) AND NOT EXISTS (SELECT 1 FROM litter_kittens lk WHERE lk.animal_id=animals.id)",
|
||||
);
|
||||
$stmt->execute([...$existingKittenIds, (int) ($motherId ?? 0), (int) ($fatherId ?? 0)]);
|
||||
if (count($stmt->fetchAll(PDO::FETCH_ASSOC)) !== count($existingKittenIds)) {
|
||||
http_response_code(400);
|
||||
echo h(t('litter.invalid_selected'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$baseName = trim((string) ($parents[$motherId]['name'] ?? ($parents[$fatherId]['name'] ?? 'Portée')));
|
||||
$allowedSexes = ['F', 'M', 'U'];
|
||||
try {
|
||||
PrivateMediaService::moveVisibility($animalId, (string) $photo['filename'], $isPublic === 1);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo h(t('error.upload_failed'));
|
||||
return;
|
||||
}
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$stmt = $db->prepare(
|
||||
'INSERT INTO litters(mother_id, father_id, birth_date) VALUES(:mother, :father, :birth)',
|
||||
);
|
||||
$stmt->execute([':mother' => $motherId, ':father' => $fatherId, ':birth' => $birthDate]);
|
||||
$litterId = (int) $db->lastInsertId();
|
||||
$insertAnimal = $db->prepare("
|
||||
INSERT INTO animals(name, internal_code, status, species, sex, breed, color, birth_date, birth_is_estimated,intake_date,intake_type,intake_reason, created_at, updated_at)
|
||||
VALUES(:name, :code, 'refuge', 'chat', :sex, 'Chat Européen', :color, :birth, 0,:birth,'born_in_care','birth', datetime('now'), datetime('now'))
|
||||
");
|
||||
$link = $db->prepare(
|
||||
'INSERT INTO litter_kittens(litter_id, animal_id, position) VALUES(:litter, :animal, :position)',
|
||||
);
|
||||
foreach ($kittens as $index => $kitten) {
|
||||
if (!is_array($kitten)) {
|
||||
continue;
|
||||
}
|
||||
$position = $index + 1;
|
||||
$name = trim((string) ($kitten['name'] ?? ''));
|
||||
if ($name === '') {
|
||||
$name = $baseName . '-Bébé-' . $position;
|
||||
}
|
||||
$sex = (string) ($kitten['sex'] ?? 'U');
|
||||
if (!in_array($sex, $allowedSexes, true)) {
|
||||
$sex = 'U';
|
||||
}
|
||||
$color = trim((string) ($kitten['color'] ?? '')) ?: null;
|
||||
$insertAnimal->execute([
|
||||
':name' => $name,
|
||||
':code' => Ids::nextAnimalCode($name),
|
||||
':sex' => $sex,
|
||||
':color' => $color,
|
||||
':birth' => $birthDate,
|
||||
]);
|
||||
$animalId = (int) $db->lastInsertId();
|
||||
$link->execute([':litter' => $litterId, ':animal' => $animalId, ':position' => $position]);
|
||||
self::syncIntakeMovement(
|
||||
$db,
|
||||
$animalId,
|
||||
$birthDate,
|
||||
'born_in_care',
|
||||
'birth',
|
||||
null,
|
||||
'',
|
||||
'',
|
||||
'Naissance enregistrée avec la portée n°' . $litterId,
|
||||
);
|
||||
LocationHistoryService::record(
|
||||
$db,
|
||||
$animalId,
|
||||
null,
|
||||
['status' => 'refuge', 'refuge_room' => null, 'care_box_key' => null, 'current_address' => null],
|
||||
'litter_creation',
|
||||
'Emplacement lors de la création de la portée',
|
||||
$birthDate ? $birthDate . ' 12:00:00' : null,
|
||||
);
|
||||
self::logHistory($animalId, 'create', 'Chaton ajouté avec sa portée', 'Portée n°' . $litterId);
|
||||
}
|
||||
$position = count($kittens);
|
||||
foreach ($existingKittenIds as $animalId) {
|
||||
$link->execute([':litter' => $litterId, ':animal' => $animalId, ':position' => ++$position]);
|
||||
self::logHistory($animalId, 'update', 'Rattaché à une nouvelle portée', 'Portée n°' . $litterId);
|
||||
}
|
||||
$db->commit();
|
||||
$targetId = (int) ($_POST['source_animal_id'] ?? 0) ?: ($motherId ?: ($fatherId ?: 0));
|
||||
header('Location: ' . ($targetId ? '/animal?id=' . $targetId . '#tab-litters' : '/animals'));
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction()) {
|
||||
$db->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public static function litterAddExistingForm(): void
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$litterId = (int) ($_GET['litter_id'] ?? 0);
|
||||
$sourceAnimalId = (int) ($_GET['animal_id'] ?? 0);
|
||||
$stmt = $db->prepare(
|
||||
'SELECT l.*,m.name mother_name,f.name father_name FROM litters l LEFT JOIN animals m ON m.id=l.mother_id LEFT JOIN animals f ON f.id=l.father_id WHERE l.id=:id',
|
||||
);
|
||||
$stmt->execute([':id' => $litterId]);
|
||||
$litter = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$litter) {
|
||||
http_response_code(404);
|
||||
echo h(t('litter.not_found'));
|
||||
return;
|
||||
}
|
||||
$stmt = $db->prepare("
|
||||
SELECT a.id,a.name,a.internal_code,a.color,a.sex,a.status,a.archived_at
|
||||
FROM animals a
|
||||
WHERE a.deleted_at IS NULL AND lower(a.species) IN ('chat','cat')
|
||||
AND a.id NOT IN (COALESCE(:mother,0),COALESCE(:father,0))
|
||||
AND NOT EXISTS (SELECT 1 FROM litter_kittens lk WHERE lk.animal_id=a.id)
|
||||
ORDER BY CASE WHEN a.archived_at IS NULL THEN 0 ELSE 1 END,a.name COLLATE NOCASE,a.id
|
||||
");
|
||||
$stmt->execute([':mother' => $litter['mother_id'], ':father' => $litter['father_id']]);
|
||||
render('litter_add_existing.php', [
|
||||
'title' => t('litter.add_title'),
|
||||
'pageDescription' => 'Rattacher des fiches animales existantes à une portée.',
|
||||
'litter' => $litter,
|
||||
'animals' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
||||
'sourceAnimalId' => $sourceAnimalId,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function saveExistingLitterKittens(): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
http_response_code(405);
|
||||
return;
|
||||
}
|
||||
$db = DB::pdo();
|
||||
$litterId = (int) ($_POST['litter_id'] ?? 0);
|
||||
$sourceAnimalId = (int) ($_POST['source_animal_id'] ?? 0);
|
||||
$ids = is_array($_POST['animal_ids'] ?? null)
|
||||
? array_values(array_unique(array_filter(array_map('intval', $_POST['animal_ids']))))
|
||||
: [];
|
||||
if ($litterId <= 0 || !$ids) {
|
||||
http_response_code(400);
|
||||
echo h(t('litter.select_one'));
|
||||
return;
|
||||
}
|
||||
$litterStmt = $db->prepare('SELECT id,mother_id,father_id FROM litters WHERE id=:id');
|
||||
$litterStmt->execute([':id' => $litterId]);
|
||||
$litter = $litterStmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$litter) {
|
||||
http_response_code(404);
|
||||
echo h(t('litter.not_found'));
|
||||
return;
|
||||
}
|
||||
$placeholders = implode(',', array_fill(0, count($ids), '?'));
|
||||
$stmt = $db->prepare(
|
||||
"SELECT id FROM animals WHERE id IN ($placeholders) AND deleted_at IS NULL AND lower(species) IN ('chat','cat') AND id NOT IN (?,?) AND NOT EXISTS (SELECT 1 FROM litter_kittens lk WHERE lk.animal_id=animals.id)",
|
||||
);
|
||||
$stmt->execute([...$ids, (int) ($litter['mother_id'] ?? 0), (int) ($litter['father_id'] ?? 0)]);
|
||||
if (count($stmt->fetchAll(PDO::FETCH_ASSOC)) !== count($ids)) {
|
||||
http_response_code(400);
|
||||
echo h(t('litter.invalid_existing'));
|
||||
return;
|
||||
}
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$position = (int) $db
|
||||
->query('SELECT COALESCE(MAX(position),0) FROM litter_kittens WHERE litter_id=' . $litterId)
|
||||
->fetchColumn();
|
||||
$link = $db->prepare(
|
||||
'INSERT INTO litter_kittens(litter_id,animal_id,position) VALUES(:litter,:animal,:position)',
|
||||
);
|
||||
foreach ($ids as $animalId) {
|
||||
$link->execute([':litter' => $litterId, ':animal' => $animalId, ':position' => ++$position]);
|
||||
self::logHistory($animalId, 'update', 'Rattaché à une portée existante', 'Portée n°' . $litterId);
|
||||
}
|
||||
$db->commit();
|
||||
header(
|
||||
'Location: /animal?id=' .
|
||||
($sourceAnimalId ?: ($litter['mother_id'] ?: ($litter['father_id'] ?: $ids[0]))) .
|
||||
'#tab-litters',
|
||||
);
|
||||
exit();
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction()) {
|
||||
$db->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public static function bondedForm(): void
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$sourceAnimalId = (int) ($_GET['animal_id'] ?? 0);
|
||||
if ($sourceAnimalId <= 0) {
|
||||
http_response_code(400);
|
||||
return;
|
||||
}
|
||||
$stmt = $db->prepare("
|
||||
SELECT a.id, a.name, a.color, a.species, a.status
|
||||
FROM animals a
|
||||
WHERE a.deleted_at IS NULL AND a.archived_at IS NULL
|
||||
AND lower(a.species) IN ('chat', 'cat')
|
||||
AND lower(a.status) NOT IN ('adopte', 'adopté', 'decede', 'décédé')
|
||||
AND (a.id = :source OR NOT EXISTS (
|
||||
SELECT 1 FROM bonded_group_members bgm
|
||||
JOIN bonded_groups bg ON bg.id = bgm.group_id AND bg.active = 1
|
||||
WHERE bgm.animal_id = a.id
|
||||
))
|
||||
ORDER BY a.name COLLATE NOCASE
|
||||
");
|
||||
$stmt->execute([':source' => $sourceAnimalId]);
|
||||
$animals = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
if (!array_filter($animals, fn(array $animal): bool => (int) $animal['id'] === $sourceAnimalId)) {
|
||||
http_response_code(404);
|
||||
echo h(t('bonded.unavailable'));
|
||||
return;
|
||||
}
|
||||
render('bonded_form.php', [
|
||||
'title' => t('bonded.create'),
|
||||
'pageDescription' => 'Associer plusieurs chats qui doivent rester ensemble.',
|
||||
'sourceAnimalId' => $sourceAnimalId,
|
||||
'animals' => $animals,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function saveBondedGroup(): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
http_response_code(405);
|
||||
return;
|
||||
}
|
||||
$ids = is_array($_POST['animal_ids'] ?? null)
|
||||
? array_values(array_unique(array_filter(array_map('intval', $_POST['animal_ids']))))
|
||||
: [];
|
||||
if (count($ids) < 2) {
|
||||
http_response_code(400);
|
||||
echo h(t('bonded.need_two'));
|
||||
return;
|
||||
}
|
||||
$db = DB::pdo();
|
||||
$placeholders = implode(',', array_fill(0, count($ids), '?'));
|
||||
$stmt = $db->prepare("
|
||||
SELECT a.id FROM animals a
|
||||
WHERE a.id IN ($placeholders) AND a.deleted_at IS NULL AND a.archived_at IS NULL
|
||||
AND lower(a.species) IN ('chat', 'cat')
|
||||
AND lower(a.status) NOT IN ('adopte', 'adopté', 'decede', 'décédé')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM bonded_group_members bgm JOIN bonded_groups bg ON bg.id=bgm.group_id
|
||||
WHERE bgm.animal_id=a.id AND bg.active=1
|
||||
)
|
||||
");
|
||||
$stmt->execute($ids);
|
||||
if (count($stmt->fetchAll()) !== count($ids)) {
|
||||
http_response_code(400);
|
||||
echo h(t('bonded.invalid_member'));
|
||||
return;
|
||||
}
|
||||
$name = trim((string) ($_POST['name'] ?? '')) ?: null;
|
||||
$notes = trim((string) ($_POST['notes'] ?? '')) ?: null;
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$stmt = $db->prepare('INSERT INTO bonded_groups(name, notes) VALUES(:name, :notes)');
|
||||
$stmt->execute([':name' => $name, ':notes' => $notes]);
|
||||
$groupId = (int) $db->lastInsertId();
|
||||
$link = $db->prepare(
|
||||
'INSERT INTO bonded_group_members(group_id, animal_id, position) VALUES(:group_id, :animal_id, :position)',
|
||||
);
|
||||
foreach ($ids as $index => $animalId) {
|
||||
$link->execute([':group_id' => $groupId, ':animal_id' => $animalId, ':position' => $index + 1]);
|
||||
self::logHistory(
|
||||
$animalId,
|
||||
'update',
|
||||
'Ajouté à un groupe inséparable',
|
||||
$name ?: 'Groupe n°' . $groupId,
|
||||
);
|
||||
}
|
||||
$db->commit();
|
||||
header('Location: /animal?id=' . $ids[0]);
|
||||
exit();
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction()) {
|
||||
$db->rollBack();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public static function dissolveBondedGroup(): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
http_response_code(405);
|
||||
return;
|
||||
}
|
||||
$groupId = (int) ($_POST['group_id'] ?? 0);
|
||||
$animalId = (int) ($_POST['animal_id'] ?? 0);
|
||||
if ($groupId <= 0) {
|
||||
http_response_code(400);
|
||||
return;
|
||||
}
|
||||
$db = DB::pdo();
|
||||
$members = $db->prepare('SELECT animal_id FROM bonded_group_members WHERE group_id=:id');
|
||||
$members->execute([':id' => $groupId]);
|
||||
$memberIds = array_map('intval', array_column($members->fetchAll(PDO::FETCH_ASSOC), 'animal_id'));
|
||||
$stmt = $db->prepare(
|
||||
"UPDATE bonded_groups SET active=0, dissolved_at=datetime('now') WHERE id=:id AND active=1",
|
||||
);
|
||||
$stmt->execute([':id' => $groupId]);
|
||||
foreach ($memberIds as $memberId) {
|
||||
self::logHistory($memberId, 'update', 'Groupe inséparable dissous');
|
||||
}
|
||||
header('Location: ' . ($animalId > 0 ? '/animal?id=' . $animalId : '/animals'));
|
||||
exit();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue