feat: permettre la modification des portées
This commit is contained in:
parent
4493357590
commit
f45cfc40f2
15 changed files with 218 additions and 14 deletions
|
|
@ -8,8 +8,10 @@ trait AnimalRelationshipActions
|
|||
{
|
||||
$db = DB::pdo();
|
||||
$sourceAnimalId = (int) ($_GET['animal_id'] ?? 0);
|
||||
$existingLitterId = (int) ($_GET['litter_id'] ?? 0);
|
||||
$motherId = 0;
|
||||
$fatherId = 0;
|
||||
$birthDate = '';
|
||||
|
||||
if ($sourceAnimalId > 0) {
|
||||
$stmt = $db->prepare(
|
||||
|
|
@ -30,6 +32,20 @@ trait AnimalRelationshipActions
|
|||
}
|
||||
}
|
||||
|
||||
if ($existingLitterId > 0) {
|
||||
$stmt = $db->prepare('SELECT mother_id,father_id,birth_date FROM litters WHERE id=:id');
|
||||
$stmt->execute([':id' => $existingLitterId]);
|
||||
$existingLitter = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$existingLitter) {
|
||||
http_response_code(404);
|
||||
echo h(t('litter.not_found'));
|
||||
return;
|
||||
}
|
||||
$motherId = (int) ($existingLitter['mother_id'] ?? 0);
|
||||
$fatherId = (int) ($existingLitter['father_id'] ?? 0);
|
||||
$birthDate = (string) ($existingLitter['birth_date'] ?? '');
|
||||
}
|
||||
|
||||
$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
|
||||
|
|
@ -53,6 +69,8 @@ trait AnimalRelationshipActions
|
|||
'sourceAnimalId' => $sourceAnimalId,
|
||||
'motherId' => $motherId,
|
||||
'fatherId' => $fatherId,
|
||||
'birthDate' => $birthDate,
|
||||
'existingLitterId' => $existingLitterId,
|
||||
'mothers' => $mothers,
|
||||
'fathers' => $fathers,
|
||||
'existingKittens' => $existingKittens,
|
||||
|
|
@ -66,6 +84,7 @@ trait AnimalRelationshipActions
|
|||
return;
|
||||
}
|
||||
$db = DB::pdo();
|
||||
$existingLitterId = (int) ($_POST['existing_litter_id'] ?? 0);
|
||||
$motherId = (int) ($_POST['mother_id'] ?? 0) ?: null;
|
||||
$fatherId = (int) ($_POST['father_id'] ?? 0) ?: null;
|
||||
$birthDate = trim((string) ($_POST['birth_date'] ?? '')) ?: null;
|
||||
|
|
@ -141,11 +160,22 @@ trait AnimalRelationshipActions
|
|||
$allowedSexes = ['F', 'M', 'U'];
|
||||
$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();
|
||||
if ($existingLitterId > 0) {
|
||||
$stmt = $db->prepare('SELECT id,mother_id,father_id,birth_date FROM litters WHERE id=:id');
|
||||
$stmt->execute([':id' => $existingLitterId]);
|
||||
$existingLitter = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$existingLitter) {
|
||||
throw new RuntimeException(t('litter.not_found'));
|
||||
}
|
||||
$litterId = (int) $existingLitter['id'];
|
||||
$motherId = (int) ($existingLitter['mother_id'] ?? 0) ?: null;
|
||||
$fatherId = (int) ($existingLitter['father_id'] ?? 0) ?: null;
|
||||
$birthDate = (string) ($existingLitter['birth_date'] ?? '') ?: null;
|
||||
} else {
|
||||
$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'))
|
||||
|
|
@ -153,11 +183,14 @@ trait AnimalRelationshipActions
|
|||
$link = $db->prepare(
|
||||
'INSERT INTO litter_kittens(litter_id, animal_id, position) VALUES(:litter, :animal, :position)',
|
||||
);
|
||||
$positionBase = $existingLitterId > 0
|
||||
? (int) $db->query('SELECT COALESCE(MAX(position),0) FROM litter_kittens WHERE litter_id=' . $litterId)->fetchColumn()
|
||||
: 0;
|
||||
foreach ($kittens as $index => $kitten) {
|
||||
if (!is_array($kitten)) {
|
||||
continue;
|
||||
}
|
||||
$position = $index + 1;
|
||||
$position = $positionBase + $index + 1;
|
||||
$name = trim((string) ($kitten['name'] ?? ''));
|
||||
if ($name === '') {
|
||||
$name = $baseName . '-Bébé-' . $position;
|
||||
|
|
@ -198,7 +231,7 @@ trait AnimalRelationshipActions
|
|||
);
|
||||
self::logHistory($animalId, 'create', 'Chaton ajouté avec sa portée', 'Portée n°' . $litterId);
|
||||
}
|
||||
$position = count($kittens);
|
||||
$position = $positionBase + 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);
|
||||
|
|
@ -214,6 +247,113 @@ trait AnimalRelationshipActions
|
|||
}
|
||||
}
|
||||
|
||||
public static function litterEditForm(): void
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$litterId = (int) ($_GET['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;
|
||||
}
|
||||
$parents = $db->query("SELECT id,name,sex FROM animals WHERE deleted_at IS NULL AND lower(species) IN ('chat','cat') ORDER BY name COLLATE NOCASE")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$stmt = $db->prepare('SELECT a.id,a.name,a.internal_code,a.sex,a.color,a.status,a.archived_at FROM litter_kittens lk JOIN animals a ON a.id=lk.animal_id WHERE lk.litter_id=:id ORDER BY lk.position,a.name COLLATE NOCASE');
|
||||
$stmt->execute([':id' => $litterId]);
|
||||
render('litter_edit.php', [
|
||||
'title' => t('litter.edit'),
|
||||
'pageDescription' => t('litter.edit_help'),
|
||||
'litter' => $litter,
|
||||
'mothers' => array_values(array_filter($parents, static fn(array $animal): bool => $animal['sex'] === 'F')),
|
||||
'fathers' => array_values(array_filter($parents, static fn(array $animal): bool => $animal['sex'] === 'M')),
|
||||
'kittens' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
||||
'sourceAnimalId' => $sourceAnimalId,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function updateLitter(): 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);
|
||||
$motherId = (int) ($_POST['mother_id'] ?? 0) ?: null;
|
||||
$fatherId = (int) ($_POST['father_id'] ?? 0) ?: null;
|
||||
$birthDate = trim((string) ($_POST['birth_date'] ?? '')) ?: null;
|
||||
if ($litterId <= 0 || ($motherId !== null && $motherId === $fatherId)) {
|
||||
http_response_code(400);
|
||||
echo h(t('litter.invalid_parent'));
|
||||
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;
|
||||
}
|
||||
$ids = array_values(array_filter([$motherId, $fatherId]));
|
||||
if ($ids) {
|
||||
$placeholders = implode(',', array_fill(0, count($ids), '?'));
|
||||
$stmt = $db->prepare("SELECT id,sex FROM animals WHERE id IN ($placeholders) AND deleted_at IS NULL AND lower(species) IN ('chat','cat')");
|
||||
$stmt->execute($ids);
|
||||
$valid = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $animal) {
|
||||
$valid[(int) $animal['id']] = $animal['sex'];
|
||||
}
|
||||
if (($motherId && ($valid[$motherId] ?? '') !== 'F') || ($fatherId && ($valid[$fatherId] ?? '') !== 'M')) {
|
||||
http_response_code(400);
|
||||
echo h(t('litter.invalid_parent'));
|
||||
return;
|
||||
}
|
||||
$stmt = $db->prepare('SELECT COUNT(*) FROM litter_kittens WHERE litter_id=? AND animal_id IN (' . $placeholders . ')');
|
||||
$stmt->execute([$litterId, ...$ids]);
|
||||
if ((int) $stmt->fetchColumn() > 0) {
|
||||
http_response_code(400);
|
||||
echo h(t('litter.parent_is_kitten'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
$exists = $db->prepare('SELECT 1 FROM litters WHERE id=:id');
|
||||
$exists->execute([':id' => $litterId]);
|
||||
if (!$exists->fetchColumn()) {
|
||||
http_response_code(404);
|
||||
return;
|
||||
}
|
||||
$db->prepare('UPDATE litters SET mother_id=:mother,father_id=:father,birth_date=:birth WHERE id=:id')->execute([':mother' => $motherId, ':father' => $fatherId, ':birth' => $birthDate, ':id' => $litterId]);
|
||||
header('Location: /animal?id=' . ($sourceAnimalId ?: ($motherId ?: ($fatherId ?: 0))) . '#tab-litters');
|
||||
exit();
|
||||
}
|
||||
|
||||
public static function detachLitterKitten(): void
|
||||
{
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||
http_response_code(405);
|
||||
return;
|
||||
}
|
||||
$litterId = (int) ($_POST['litter_id'] ?? 0);
|
||||
$animalId = (int) ($_POST['animal_id'] ?? 0);
|
||||
$sourceAnimalId = (int) ($_POST['source_animal_id'] ?? 0);
|
||||
$stmt = DB::pdo()->prepare('DELETE FROM litter_kittens WHERE litter_id=:litter AND animal_id=:animal');
|
||||
$stmt->execute([':litter' => $litterId, ':animal' => $animalId]);
|
||||
if ($stmt->rowCount() > 0) {
|
||||
self::logHistory($animalId, 'update', 'Retiré d’une portée', 'Ancienne portée n°' . $litterId);
|
||||
}
|
||||
$remaining = DB::pdo()->prepare('SELECT COUNT(*) FROM litter_kittens WHERE litter_id=:id');
|
||||
$remaining->execute([':id' => $litterId]);
|
||||
if ((int) $remaining->fetchColumn() === 0) {
|
||||
DB::pdo()->prepare('DELETE FROM litters WHERE id=:id')->execute([':id' => $litterId]);
|
||||
header('Location: /animal?id=' . ($sourceAnimalId ?: $animalId) . '#tab-litters');
|
||||
exit();
|
||||
}
|
||||
header('Location: /litter/edit?id=' . $litterId . '&animal_id=' . $sourceAnimalId);
|
||||
exit();
|
||||
}
|
||||
|
||||
public static function litterAddExistingForm(): void
|
||||
{
|
||||
$db = DB::pdo();
|
||||
|
|
|
|||
|
|
@ -189,6 +189,15 @@ switch ($path) {
|
|||
case '/litter/save':
|
||||
AnimalsController::saveLitter();
|
||||
break;
|
||||
case '/litter/edit':
|
||||
AnimalsController::litterEditForm();
|
||||
break;
|
||||
case '/litter/update':
|
||||
AnimalsController::updateLitter();
|
||||
break;
|
||||
case '/litter/kitten/detach':
|
||||
AnimalsController::detachLitterKitten();
|
||||
break;
|
||||
case '/litter/add-existing':
|
||||
AnimalsController::litterAddExistingForm();
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -211,6 +211,9 @@ final class PermissionService
|
|||
'/animal/death',
|
||||
'/animal/surgery/new',
|
||||
'/litter/new',
|
||||
'/litter/edit',
|
||||
'/litter/update',
|
||||
'/litter/kitten/detach',
|
||||
'/litter/add-existing',
|
||||
'/litter/add-existing/save',
|
||||
'/bonded/new',
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@
|
|||
<?= $litter['father_id'] ? '<a href="/animal?id=' . (int)$litter['father_id'] . '">' . h($litter['father_name']) . '</a>' : h(t('animal.unknown_male')) ?>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-outline-primary btn-sm ms-auto"
|
||||
<a class="btn btn-outline-primary btn-sm ms-auto" href="/litter/edit?id=<?= (int)$litter['id'] ?>&animal_id=<?= (int)$animal['id'] ?>">✏️ <?= h(t('common.edit')) ?></a>
|
||||
<a class="btn btn-outline-primary btn-sm"
|
||||
href="/litter/add-existing?litter_id=<?= (int)$litter['id'] ?>&animal_id=<?= (int)$animal['id'] ?>">+
|
||||
<?= h(t('animal.add_existing_cat')) ?></a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@
|
|||
<div class="col-lg-6">
|
||||
<section class="card h-100">
|
||||
<div class="card-body">
|
||||
<span class="badge bg-blue-lt mb-2">1.0.0-rc.24</span>
|
||||
<h3>Fiabilité des portées</h3>
|
||||
<span class="badge bg-blue-lt mb-2">1.0.0-rc.25</span>
|
||||
<h3>Portées modifiables et fiables</h3>
|
||||
<p class="text-muted">La création d’une fratrie ne dépend plus d’un ancien traitement de photo et
|
||||
son historique reste compatible avec les bases existantes.</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ if ($pageDescription === '') {
|
|||
if (path.startsWith('/admin') || path.startsWith('/documents')) return 'administrative';
|
||||
return null;
|
||||
};
|
||||
const permissionEditPages = new Set(['/animal/new', '/animal/edit', '/animal/death', '/litter/new',
|
||||
const permissionEditPages = new Set(['/animal/new', '/animal/edit', '/animal/death', '/litter/new', '/litter/edit', '/litter/update', '/litter/kitten/detach',
|
||||
'/litter/add-existing', '/litter/add-existing/save', '/bonded/new', '/directory/new',
|
||||
'/directory/edit', '/care-round/setup'
|
||||
]);
|
||||
|
|
|
|||
13
app/Views/litter_edit.php
Normal file
13
app/Views/litter_edit.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php $returnId = $sourceAnimalId ?: ((int)($litter['mother_id'] ?? 0) ?: (int)($litter['father_id'] ?? 0)); ?>
|
||||
<form method="post" action="/litter/update" class="card mb-3">
|
||||
<input type="hidden" name="csrf" value="<?= h(Auth::csrf()) ?>"><input type="hidden" name="litter_id" value="<?= (int)$litter['id'] ?>"><input type="hidden" name="source_animal_id" value="<?= (int)$returnId ?>">
|
||||
<div class="card-header"><div><h3 class="card-title mb-1"><?= h(t('litter.edit')) ?></h3><div class="text-muted small"><?= h(t('litter.edit_help')) ?></div></div><a class="btn btn-ghost-secondary ms-auto" href="/animal?id=<?= (int)$returnId ?>#tab-litters"><?= h(t('common.cancel')) ?></a></div>
|
||||
<div class="card-body"><div class="row g-3">
|
||||
<div class="col-md-4"><label class="form-label"><?= h(t('litter.mother')) ?></label><select class="form-select" name="mother_id"><option value=""><?= h(t('litter.unknown_parent')) ?></option><?php foreach ($mothers as $parent): ?><option value="<?= (int)$parent['id'] ?>" <?= (int)$litter['mother_id'] === (int)$parent['id'] ? 'selected' : '' ?>><?= h($parent['name']) ?></option><?php endforeach; ?></select></div>
|
||||
<div class="col-md-4"><label class="form-label"><?= h(t('litter.father')) ?></label><select class="form-select" name="father_id"><option value=""><?= h(t('litter.unknown_parent')) ?></option><?php foreach ($fathers as $parent): ?><option value="<?= (int)$parent['id'] ?>" <?= (int)$litter['father_id'] === (int)$parent['id'] ? 'selected' : '' ?>><?= h($parent['name']) ?></option><?php endforeach; ?></select></div>
|
||||
<div class="col-md-4"><label class="form-label"><?= h(t('litter.birth_date')) ?></label><input class="form-control" type="date" name="birth_date" value="<?= h($litter['birth_date'] ?? '') ?>"></div>
|
||||
</div></div><div class="card-footer text-end"><button class="btn btn-primary" type="submit"><?= h(t('common.save')) ?></button></div>
|
||||
</form>
|
||||
<div class="card"><div class="card-header"><h3 class="card-title"><?= h(t('litter.kittens')) ?></h3><div class="ms-auto d-flex gap-2"><a class="btn btn-outline-primary btn-sm" href="/litter/new?litter_id=<?= (int)$litter['id'] ?>&animal_id=<?= (int)$returnId ?>">+ <?= h(t('litter.new_kittens')) ?></a><a class="btn btn-outline-primary btn-sm" href="/litter/add-existing?litter_id=<?= (int)$litter['id'] ?>&animal_id=<?= (int)$returnId ?>">+ <?= h(t('animal.add_existing_cat')) ?></a></div></div><div class="list-group list-group-flush">
|
||||
<?php foreach ($kittens as $kitten): ?><div class="list-group-item d-flex align-items-center gap-2"><span class="coat-swatch" style="<?= h(coat_swatch_style($kitten['color'] ?? '')) ?>"></span><strong><?= h($kitten['name']) ?></strong><span class="text-muted small"><?= h($kitten['internal_code']) ?></span><a class="btn btn-outline-primary btn-sm ms-auto" href="/animal/edit?id=<?= (int)$kitten['id'] ?>"><?= h(t('common.edit')) ?></a><form method="post" action="/litter/kitten/detach" onsubmit="return confirm(<?= h(json_encode(t('litter.detach_confirm'), JSON_UNESCAPED_UNICODE)) ?>)"><input type="hidden" name="csrf" value="<?= h(Auth::csrf()) ?>"><input type="hidden" name="litter_id" value="<?= (int)$litter['id'] ?>"><input type="hidden" name="animal_id" value="<?= (int)$kitten['id'] ?>"><input type="hidden" name="source_animal_id" value="<?= (int)$returnId ?>"><button class="btn btn-outline-danger btn-sm" type="submit"><?= h(t('litter.detach')) ?></button></form></div><?php endforeach; ?>
|
||||
</div></div>
|
||||
|
|
@ -8,6 +8,7 @@ $prefillCount = max(0, min(20, (int)($_GET['count'] ?? 4)));
|
|||
?>
|
||||
<form method="post" action="/litter/save" class="card">
|
||||
<input type="hidden" name="csrf" value="<?= h(Auth::csrf()) ?>">
|
||||
<input type="hidden" name="existing_litter_id" value="<?= (int)($existingLitterId ?? 0) ?>">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<h3 class="card-title"><?= h(t('litter.create')) ?></h3>
|
||||
<a href="<?= $sourceAnimalId ? '/animal?id=' . (int)$sourceAnimalId . '#tab-litters' : '/animals' ?>"
|
||||
|
|
@ -43,7 +44,7 @@ $prefillCount = max(0, min(20, (int)($_GET['count'] ?? 4)));
|
|||
<div class="col-md-4">
|
||||
<label class="form-label"><?= h(t('litter.birth_date')) ?> <span
|
||||
class="text-muted"><?= h(t('common.optional_parentheses')) ?></span></label>
|
||||
<input class="form-control" type="date" name="birth_date" placeholder="jj/mm/aaaa">
|
||||
<input class="form-control" type="date" name="birth_date" value="<?= h($birthDate ?? '') ?>" placeholder="jj/mm/aaaa">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue