feat(bovine) : page Vie du bovin + tabs réutilisables + parents EDNOTIF
- Nouvelle page /bovine/[id] avec tabs Mouvement / Passeport bovin / Santé - Composant UiTabs partagé, réutilisé sur réception et expédition - Champs père/mère (numéro national + type de race) sur Bovine, alimentés via la sync EDNOTIF - Inventaire : ligne cliquable vers le passeport Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
35
frontend/components/ui/UiTabs.vue
Normal file
35
frontend/components/ui/UiTabs.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex justify-evenly gap-y-8 gap-x-41 mb-10 border-b border-primary-500/60">
|
||||||
|
<h1
|
||||||
|
v-for="tab in tabs"
|
||||||
|
:key="tab.key"
|
||||||
|
class="font-bold text-3xl uppercase px-12 cursor-pointer"
|
||||||
|
:class="[
|
||||||
|
modelValue === tab.key
|
||||||
|
? 'border-b-[6px] border-primary-500 text-primary-500'
|
||||||
|
: 'text-primary-500/50',
|
||||||
|
tab.error ? '!text-red-500 !border-red-500' : ''
|
||||||
|
]"
|
||||||
|
@click="emit('update:modelValue', tab.key)"
|
||||||
|
>
|
||||||
|
{{ tab.label }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" generic="T extends string">
|
||||||
|
export interface UiTab<K extends string = string> {
|
||||||
|
key: K
|
||||||
|
label: string
|
||||||
|
error?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
modelValue: T
|
||||||
|
tabs: UiTab<T>[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: T): void
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
135
frontend/pages/bovine/[id].vue
Normal file
135
frontend/pages/bovine/[id].vue
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<div class="px-[86px]">
|
||||||
|
<div class="flex items-center justify-between relative mb-10">
|
||||||
|
<div class="flex flex-row absolute -left-[60px]">
|
||||||
|
<Icon
|
||||||
|
@click="router.push('/inventory')"
|
||||||
|
name="gg:arrow-left-o"
|
||||||
|
size="44"
|
||||||
|
class="cursor-pointer text-primary-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1 class="font-bold text-3xl uppercase text-primary-500">Vie du bovin</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<UiTabs
|
||||||
|
v-model="activeTab"
|
||||||
|
:tabs="[
|
||||||
|
{ key: 'mouvement', label: 'Mouvement' },
|
||||||
|
{ key: 'passeport', label: 'Passeport bovin' },
|
||||||
|
{ key: 'sante', label: 'Santé' }
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-show="activeTab === 'passeport'">
|
||||||
|
<div class="mt-6">
|
||||||
|
<div class="grid grid-cols-[3rem_repeat(6,minmax(0,1fr))] grid-rows-2 border-2 border-black">
|
||||||
|
<div class="row-span-2 flex items-center justify-center border-r-2 border-black">
|
||||||
|
<span class="uppercase font-bold -rotate-90 whitespace-nowrap transform-gpu">Veau</span>
|
||||||
|
</div>
|
||||||
|
<div class="border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">Numéro national</div>
|
||||||
|
<div class="border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">N° de travail</div>
|
||||||
|
<div class="border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">Sexe</div>
|
||||||
|
<div class="border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">Code race</div>
|
||||||
|
<div class="border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">Type de race</div>
|
||||||
|
<div class="border-b border-black px-2 py-1 text-center font-semibold text-sm">Date de naissance</div>
|
||||||
|
<div class="border-r border-black px-2 py-1 text-center">{{ display(bovine?.nationalNumber) }}</div>
|
||||||
|
<div class="border-r border-black px-2 py-1 text-center">{{ display(bovine?.workNumber) }}</div>
|
||||||
|
<div class="border-r border-black px-2 py-1 text-center">{{ display(bovine?.sex) }}</div>
|
||||||
|
<div class="border-r border-black px-2 py-1 text-center">{{ display(bovine?.bovineType?.code) }}</div>
|
||||||
|
<div class="border-r border-black px-2 py-1 text-center">{{ display(bovine?.bovineType?.label) }}</div>
|
||||||
|
<div class="px-2 py-1 text-center">{{ formatDate(bovine?.birthDate) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-9">
|
||||||
|
<div class="grid grid-cols-[3rem_repeat(6,minmax(0,1fr))] grid-rows-2 border-2 border-black">
|
||||||
|
<div class="row-span-2 flex items-center justify-center border-r-2 border-black">
|
||||||
|
<span class="uppercase font-bold -rotate-90 whitespace-nowrap transform-gpu">Père</span>
|
||||||
|
</div>
|
||||||
|
<div class="border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">Numéro national</div>
|
||||||
|
<div class="col-span-2 border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">N° de travail</div>
|
||||||
|
<div class="border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">Code race</div>
|
||||||
|
<div class="col-span-2 border-b border-black px-2 py-1 text-center font-semibold text-sm">Type de race</div>
|
||||||
|
<div class="border-r border-black px-2 py-1 text-center">{{ display(bovine?.fatherNationalNumber) }}</div>
|
||||||
|
<div class="col-span-2 border-r border-black px-2 py-1 text-center">{{ display(workNumberFromNational(bovine?.fatherNationalNumber)) }}</div>
|
||||||
|
<div class="border-r border-black px-2 py-1 text-center">{{ display(bovine?.fatherBovineType?.code) }}</div>
|
||||||
|
<div class="col-span-2 px-2 py-1 text-center">{{ display(bovine?.fatherBovineType?.label) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-9">
|
||||||
|
<div class="grid grid-cols-[3rem_repeat(6,minmax(0,1fr))] grid-rows-2 border-2 border-black">
|
||||||
|
<div class="row-span-2 flex items-center justify-center border-r-2 border-black">
|
||||||
|
<span class="uppercase font-bold -rotate-90 whitespace-nowrap transform-gpu">Mère</span>
|
||||||
|
</div>
|
||||||
|
<div class="border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">Numéro national</div>
|
||||||
|
<div class="col-span-2 border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">N° de travail</div>
|
||||||
|
<div class="border-b border-r border-black px-2 py-1 text-center font-semibold text-sm">Code race</div>
|
||||||
|
<div class="col-span-2 border-b border-black px-2 py-1 text-center font-semibold text-sm">Type de race</div>
|
||||||
|
<div class="border-r border-black px-2 py-1 text-center">{{ display(bovine?.motherNationalNumber) }}</div>
|
||||||
|
<div class="col-span-2 border-r border-black px-2 py-1 text-center">{{ display(workNumberFromNational(bovine?.motherNationalNumber)) }}</div>
|
||||||
|
<div class="border-r border-black px-2 py-1 text-center">{{ display(bovine?.motherBovineType?.code) }}</div>
|
||||||
|
<div class="col-span-2 px-2 py-1 text-center">{{ display(bovine?.motherBovineType?.label) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
useHead({ title: 'Vie du bovin' })
|
||||||
|
|
||||||
|
type BovineTab = 'mouvement' | 'passeport' | 'sante'
|
||||||
|
const activeTab = ref<BovineTab>('passeport')
|
||||||
|
|
||||||
|
interface BovineTypeRef {
|
||||||
|
id: number
|
||||||
|
label: string | null
|
||||||
|
code: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BovinePassportData {
|
||||||
|
id: number
|
||||||
|
nationalNumber: string
|
||||||
|
workNumber: string | null
|
||||||
|
sex: string | null
|
||||||
|
birthDate: string | null
|
||||||
|
bovineType: BovineTypeRef | null
|
||||||
|
motherNationalNumber: string | null
|
||||||
|
motherBovineType: BovineTypeRef | null
|
||||||
|
fatherNationalNumber: string | null
|
||||||
|
fatherBovineType: BovineTypeRef | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const bovine = ref<BovinePassportData | null>(null)
|
||||||
|
|
||||||
|
const bovineId = computed(() => {
|
||||||
|
const raw = Array.isArray(route.params.id) ? route.params.id[0] : route.params.id
|
||||||
|
const n = Number(raw)
|
||||||
|
return Number.isFinite(n) ? n : null
|
||||||
|
})
|
||||||
|
|
||||||
|
const display = (value: string | null | undefined) => (value && value !== '' ? value : '—')
|
||||||
|
|
||||||
|
const workNumberFromNational = (nationalNumber: string | null | undefined) => {
|
||||||
|
if (!nationalNumber) return null
|
||||||
|
return nationalNumber.slice(-4)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (date: string | null | undefined) => {
|
||||||
|
if (!date) return '—'
|
||||||
|
const d = new Date(date)
|
||||||
|
if (isNaN(d.getTime())) return date
|
||||||
|
return d.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (bovineId.value === null) return
|
||||||
|
bovine.value = await api.get<BovinePassportData>(`bovines/${bovineId.value}`)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -57,6 +57,8 @@
|
|||||||
:items="items"
|
:items="items"
|
||||||
:total-items="totalItems"
|
:total-items="totalItems"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
row-clickable
|
||||||
|
@row-click="(item: BovineData) => router.push(`/bovine/${item.id}`)"
|
||||||
>
|
>
|
||||||
<template #header-nationalNumber>
|
<template #header-nationalNumber>
|
||||||
<UiTextInput
|
<UiTextInput
|
||||||
|
|||||||
@@ -145,38 +145,14 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="formIsLoading">
|
<div v-if="formIsLoading">
|
||||||
<div class="flex justify-evenly gap-y-8 gap-x-41 mb-10 border-b border-primary-500/60">
|
<UiTabs
|
||||||
<h1
|
v-model="activeTab"
|
||||||
class="font-bold text-3xl uppercase px-12 col-start-1 row-start-1 cursor-pointer"
|
:tabs="[
|
||||||
:class="[
|
{ key: 'weights', label: 'pesée à plein', error: hasGrossWeightError },
|
||||||
activeTab === 'weights' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50',
|
{ key: 'weightsEmpty', label: 'pesée à vide', error: hasTareWeightError },
|
||||||
hasGrossWeightError ? '!text-red-500 !border-red-500' : ''
|
{ key: 'merchandise', label: isMerchandise ? 'Marchandise' : 'Bovins', error: hasMerchandiseTabError }
|
||||||
]"
|
]"
|
||||||
@click="activeTab = 'weights'"
|
/>
|
||||||
>
|
|
||||||
pesée à plein
|
|
||||||
</h1>
|
|
||||||
<h1
|
|
||||||
class="font-bold text-3xl uppercase col-start-1 row-start-1 px-12 cursor-pointer"
|
|
||||||
:class="[
|
|
||||||
activeTab === 'weightsEmpty' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50',
|
|
||||||
hasTareWeightError ? '!text-red-500 !border-red-500' : ''
|
|
||||||
]"
|
|
||||||
@click="activeTab = 'weightsEmpty'"
|
|
||||||
>
|
|
||||||
pesée à vide
|
|
||||||
</h1>
|
|
||||||
<h1
|
|
||||||
class="font-bold text-3xl uppercase px-12 col-start-2 row-start-1 cursor-pointer"
|
|
||||||
:class="[
|
|
||||||
activeTab === 'merchandise' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50',
|
|
||||||
hasMerchandiseTabError ? '!text-red-500 !border-red-500' : ''
|
|
||||||
]"
|
|
||||||
@click="activeTab = 'merchandise'"
|
|
||||||
>
|
|
||||||
{{ isMerchandise ? "Marchandise" : "Bovins" }}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div class="mb-12 ">
|
<div class="mb-12 ">
|
||||||
<update-weight
|
<update-weight
|
||||||
v-show="activeTab === 'weights'"
|
v-show="activeTab === 'weights'"
|
||||||
|
|||||||
@@ -146,28 +146,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="formIsLoading">
|
<div v-if="formIsLoading">
|
||||||
<div class="flex justify-evenly gap-y-8 gap-x-41 mb-10 border-b border-primary-500/60">
|
<UiTabs
|
||||||
<h1
|
v-model="activeTab"
|
||||||
class="font-bold text-3xl uppercase px-12 col-start-1 row-start-1 cursor-pointer"
|
:tabs="[
|
||||||
:class="[
|
{ key: 'weightsEmpty', label: 'pesée à vide', error: hasTareWeightError },
|
||||||
activeTab === 'weightsEmpty' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50',
|
{ key: 'weights', label: 'pesée à plein', error: hasGrossWeightError }
|
||||||
hasTareWeightError ? '!text-red-500 !border-red-500' : ''
|
|
||||||
]"
|
]"
|
||||||
@click="activeTab = 'weightsEmpty'"
|
/>
|
||||||
>
|
|
||||||
pesée à vide
|
|
||||||
</h1>
|
|
||||||
<h1
|
|
||||||
class="font-bold text-3xl uppercase col-start-1 row-start-1 px-12 cursor-pointer"
|
|
||||||
:class="[
|
|
||||||
activeTab === 'weights' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50',
|
|
||||||
hasGrossWeightError ? '!text-red-500 !border-red-500' : ''
|
|
||||||
]"
|
|
||||||
@click="activeTab = 'weights'"
|
|
||||||
>
|
|
||||||
pesée à plein
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div class="mb-12">
|
<div class="mb-12">
|
||||||
<update-weight
|
<update-weight
|
||||||
v-show="activeTab === 'weights'"
|
v-show="activeTab === 'weights'"
|
||||||
|
|||||||
40
migrations/Version20260504125011.php
Normal file
40
migrations/Version20260504125011.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260504125011 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add mother/father national number and bovine type to bovine.';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE bovine ADD mother_national_number VARCHAR(50) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE bovine ADD father_national_number VARCHAR(50) DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE bovine ADD mother_bovine_type_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE bovine ADD father_bovine_type_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE bovine ADD CONSTRAINT FK_2068337F14E5E9FB FOREIGN KEY (mother_bovine_type_id) REFERENCES bovine_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE bovine ADD CONSTRAINT FK_2068337F53F12909 FOREIGN KEY (father_bovine_type_id) REFERENCES bovine_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('CREATE INDEX IDX_2068337F14E5E9FB ON bovine (mother_bovine_type_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_2068337F53F12909 ON bovine (father_bovine_type_id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE bovine DROP CONSTRAINT FK_2068337F14E5E9FB');
|
||||||
|
$this->addSql('ALTER TABLE bovine DROP CONSTRAINT FK_2068337F53F12909');
|
||||||
|
$this->addSql('DROP INDEX IDX_2068337F14E5E9FB');
|
||||||
|
$this->addSql('DROP INDEX IDX_2068337F53F12909');
|
||||||
|
$this->addSql('ALTER TABLE bovine DROP mother_national_number');
|
||||||
|
$this->addSql('ALTER TABLE bovine DROP father_national_number');
|
||||||
|
$this->addSql('ALTER TABLE bovine DROP mother_bovine_type_id');
|
||||||
|
$this->addSql('ALTER TABLE bovine DROP father_bovine_type_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -135,6 +135,24 @@ class Bovine
|
|||||||
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
||||||
private ?DateTimeImmutable $exitedAt = null;
|
private ?DateTimeImmutable $exitedAt = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 50, nullable: true)]
|
||||||
|
#[Groups(['bovine:read'])]
|
||||||
|
private ?string $motherNationalNumber = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
#[Groups(['bovine:read'])]
|
||||||
|
#[ApiProperty(readableLink: true)]
|
||||||
|
private ?BovineType $motherBovineType = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 50, nullable: true)]
|
||||||
|
#[Groups(['bovine:read'])]
|
||||||
|
private ?string $fatherNationalNumber = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
#[Groups(['bovine:read'])]
|
||||||
|
#[ApiProperty(readableLink: true)]
|
||||||
|
private ?BovineType $fatherBovineType = null;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
@@ -329,6 +347,54 @@ class Bovine
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMotherNationalNumber(): ?string
|
||||||
|
{
|
||||||
|
return $this->motherNationalNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMotherNationalNumber(?string $motherNationalNumber): static
|
||||||
|
{
|
||||||
|
$this->motherNationalNumber = $motherNationalNumber;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMotherBovineType(): ?BovineType
|
||||||
|
{
|
||||||
|
return $this->motherBovineType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMotherBovineType(?BovineType $motherBovineType): static
|
||||||
|
{
|
||||||
|
$this->motherBovineType = $motherBovineType;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFatherNationalNumber(): ?string
|
||||||
|
{
|
||||||
|
return $this->fatherNationalNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFatherNationalNumber(?string $fatherNationalNumber): static
|
||||||
|
{
|
||||||
|
$this->fatherNationalNumber = $fatherNationalNumber;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFatherBovineType(): ?BovineType
|
||||||
|
{
|
||||||
|
return $this->fatherBovineType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFatherBovineType(?BovineType $fatherBovineType): static
|
||||||
|
{
|
||||||
|
$this->fatherBovineType = $fatherBovineType;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function refreshAgeMonths(): void
|
public function refreshAgeMonths(): void
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ final class BovineSyncInventoryProcessor implements ProcessorInterface
|
|||||||
$bovine->setBovineType($this->resolveBovineType($identification->breedType));
|
$bovine->setBovineType($this->resolveBovineType($identification->breedType));
|
||||||
$bovine->setWorkNumber($identification->workNumber);
|
$bovine->setWorkNumber($identification->workNumber);
|
||||||
$bovine->setBirthDate($identification->birthDate?->date);
|
$bovine->setBirthDate($identification->birthDate?->date);
|
||||||
|
|
||||||
|
$bovine->setMotherNationalNumber($identification->motherCarrier?->bovin?->nationalNumber);
|
||||||
|
$bovine->setMotherBovineType($this->resolveBovineType($identification->motherCarrier?->breedType));
|
||||||
|
$bovine->setFatherNationalNumber($identification->fatherIpg?->bovin?->nationalNumber);
|
||||||
|
$bovine->setFatherBovineType($this->resolveBovineType($identification->fatherIpg?->breedType));
|
||||||
}
|
}
|
||||||
|
|
||||||
$latestEntry = null;
|
$latestEntry = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user