Files
animal_management/models/forms/AnimalMedicalVisitForm.php
2026-04-04 13:13:00 -04:00

347 lines
12 KiB
PHP

<?php
namespace humhub\modules\animal_management\models\forms;
use humhub\modules\animal_management\models\Animal;
use humhub\modules\animal_management\models\AnimalMedicalVisit;
use humhub\modules\animal_management\models\AnimalMedicalVisitFieldValue;
use humhub\modules\animal_management\services\AnimalStreamPublisherService;
use humhub\modules\rescue_foundation\models\RescueFieldDefinition;
use Yii;
use yii\base\Model;
use yii\helpers\Json;
class AnimalMedicalVisitForm extends Model
{
public const MODULE_ID = 'animal_management';
public Animal $animal;
public string $visit_at = '';
public string $provider_name = '';
public string $notes = '';
public string $recommendations = '';
public bool $post_to_space_feed = false;
public bool $post_to_animal_feed = true;
public array $customFields = [];
public ?AnimalMedicalVisit $medicalVisit = null;
private array $customFieldDefinitions = [];
public function init()
{
parent::init();
$this->bootstrapCustomFieldDefinitions();
}
public function rules()
{
return [
[['visit_at', 'provider_name', 'notes', 'recommendations'], 'safe'],
[['provider_name'], 'string', 'max' => 190],
[['notes', 'recommendations'], 'string'],
[['post_to_space_feed', 'post_to_animal_feed'], 'boolean'],
[['customFields'], 'safe'],
[['customFields'], 'validateCustomFieldValues'],
];
}
public function save(): bool
{
if (!$this->validate()) {
return false;
}
$record = $this->medicalVisit instanceof AnimalMedicalVisit
? $this->medicalVisit
: new AnimalMedicalVisit();
$isNew = $record->getIsNewRecord();
if ($isNew) {
$record->animal_id = (int)$this->animal->id;
$record->created_by = Yii::$app->user->isGuest ? null : (int)Yii::$app->user->id;
}
$record->visit_at = $this->normalizeDateTime($this->visit_at);
$record->provider_name = trim($this->provider_name);
$record->notes = trim($this->notes);
$record->recommendations = trim($this->recommendations);
if ($record->hasAttribute('post_to_space_feed') && $record->hasAttribute('post_to_animal_feed')) {
$record->post_to_space_feed = $this->post_to_space_feed ? 1 : 0;
$record->post_to_animal_feed = $this->post_to_animal_feed ? 1 : 0;
}
if (!$record->save()) {
$this->addErrors($record->getErrors());
return false;
}
$this->medicalVisit = $record;
if (!$this->saveCustomFieldValues($record)) {
return false;
}
if ($this->post_to_space_feed) {
AnimalStreamPublisherService::publishMedicalVisit($this->animal, $record);
}
return true;
}
public function setMedicalVisit(AnimalMedicalVisit $medicalVisit): void
{
$this->medicalVisit = $medicalVisit;
$this->visit_at = (string)$medicalVisit->visit_at;
$this->provider_name = (string)$medicalVisit->provider_name;
$this->notes = (string)$medicalVisit->notes;
$this->recommendations = (string)$medicalVisit->recommendations;
if ($medicalVisit->hasAttribute('post_to_space_feed') && $medicalVisit->hasAttribute('post_to_animal_feed')) {
$this->post_to_space_feed = ((int)$medicalVisit->post_to_space_feed) === 1;
$this->post_to_animal_feed = ((int)$medicalVisit->post_to_animal_feed) === 1;
} else {
$this->post_to_space_feed = false;
$this->post_to_animal_feed = true;
}
$this->loadCustomFieldValues($medicalVisit);
}
public function getCustomFieldDefinitions(): array
{
return $this->customFieldDefinitions;
}
public function getCustomFieldSelectOptions(string $fieldKey): array
{
if (!isset($this->customFieldDefinitions[$fieldKey])) {
return [];
}
return $this->parseSelectOptions((string)$this->customFieldDefinitions[$fieldKey]['options']);
}
public function validateCustomFieldValues(string $attribute): void
{
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
$value = $this->customFields[$fieldKey] ?? null;
$label = (string)$definition['label'];
$inputType = (string)$definition['input_type'];
$required = ((int)$definition['required']) === 1;
if ($required && $this->isCustomValueEmpty($value, $inputType)) {
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} is required.', ['{field}' => $label]));
continue;
}
if ($this->isCustomValueEmpty($value, $inputType)) {
continue;
}
$normalized = is_array($value) ? '' : trim((string)$value);
if ($inputType === 'number' && !is_numeric($normalized)) {
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} must be a valid number.', ['{field}' => $label]));
continue;
}
if (($inputType === 'date' || $inputType === 'datetime') && strtotime($normalized) === false) {
$this->addError($attribute, Yii::t('AnimalManagementModule.base', '{field} must be a valid date/time.', ['{field}' => $label]));
continue;
}
if ($inputType === 'select') {
$options = $this->getCustomFieldSelectOptions($fieldKey);
if (!empty($options) && !array_key_exists($normalized, $options)) {
$this->addError($attribute, Yii::t('AnimalManagementModule.base', 'Invalid selection for {field}.', ['{field}' => $label]));
}
}
}
}
private function normalizeDateTime(string $value): string
{
$value = trim($value);
if ($value === '') {
return date('Y-m-d H:i:s');
}
$timestamp = strtotime($value);
if ($timestamp === false) {
return date('Y-m-d H:i:s');
}
return date('Y-m-d H:i:s', $timestamp);
}
private function bootstrapCustomFieldDefinitions(): void
{
if (!class_exists(RescueFieldDefinition::class)) {
return;
}
if (Yii::$app->db->schema->getTableSchema('rescue_field_definition', true) === null) {
return;
}
$definitions = RescueFieldDefinition::find()
->where(['module_id' => self::MODULE_ID, 'group_key' => 'animal_medical_visit', 'is_active' => 1])
->orderBy(['sort_order' => SORT_ASC, 'id' => SORT_ASC])
->all();
foreach ($definitions as $definition) {
$fieldKey = (string)$definition->field_key;
$this->customFieldDefinitions[$fieldKey] = [
'id' => (int)$definition->id,
'field_key' => $fieldKey,
'label' => trim((string)$definition->label) !== '' ? (string)$definition->label : $fieldKey,
'input_type' => (string)$definition->input_type,
'required' => (int)$definition->required,
'visibility' => (string)$definition->visibility,
'options' => (string)$definition->options,
];
}
}
private function saveCustomFieldValues(AnimalMedicalVisit $record): bool
{
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
$fieldDefinitionId = (int)$definition['id'];
if ($fieldDefinitionId === 0) {
continue;
}
$inputType = (string)$definition['input_type'];
$rawValue = $this->customFields[$fieldKey] ?? null;
$valueText = $this->normalizeCustomValue($rawValue, $inputType);
$valueRecord = AnimalMedicalVisitFieldValue::findOne([
'medical_visit_id' => (int)$record->id,
'field_definition_id' => $fieldDefinitionId,
]);
if ($this->isCustomValueEmpty($valueText, $inputType)) {
if ($valueRecord instanceof AnimalMedicalVisitFieldValue) {
$valueRecord->delete();
}
continue;
}
if (!$valueRecord instanceof AnimalMedicalVisitFieldValue) {
$valueRecord = new AnimalMedicalVisitFieldValue();
$valueRecord->medical_visit_id = (int)$record->id;
$valueRecord->field_definition_id = $fieldDefinitionId;
}
$valueRecord->value_text = $valueText;
$valueRecord->value_json = null;
if (!$valueRecord->save()) {
$this->addError('customFields', Yii::t('AnimalManagementModule.base', 'Could not save custom medical field {field}.', [
'{field}' => (string)$definition['label'],
]));
foreach ($valueRecord->getFirstErrors() as $error) {
$this->addError('customFields', $error);
}
}
}
return !$this->hasErrors('customFields');
}
private function normalizeCustomValue($value, string $inputType): ?string
{
if ($inputType === 'boolean') {
return !empty($value) ? '1' : '0';
}
if (is_array($value)) {
return Json::encode($value);
}
return trim((string)$value);
}
private function isCustomValueEmpty($value, string $inputType): bool
{
if ($inputType === 'boolean') {
return false;
}
if (is_array($value)) {
return empty($value);
}
return trim((string)$value) === '';
}
private function parseSelectOptions(string $options): array
{
$options = trim($options);
if ($options === '') {
return [];
}
$decoded = null;
try {
$decoded = Json::decode($options, true);
} catch (\Throwable $e) {
$decoded = null;
}
if (is_array($decoded)) {
$result = [];
if (array_values($decoded) === $decoded) {
foreach ($decoded as $item) {
$item = (string)$item;
if ($item === '') {
continue;
}
$result[$item] = $item;
}
return $result;
}
foreach ($decoded as $key => $value) {
$key = (string)$key;
if ($key === '') {
continue;
}
$result[$key] = (string)$value;
}
return $result;
}
$result = [];
foreach (preg_split('/[\r\n,]+/', $options) as $item) {
$item = trim((string)$item);
if ($item === '') {
continue;
}
$result[$item] = $item;
}
return $result;
}
private function loadCustomFieldValues(AnimalMedicalVisit $record): void
{
foreach ($this->customFieldDefinitions as $fieldKey => $definition) {
$value = AnimalMedicalVisitFieldValue::findOne([
'medical_visit_id' => (int)$record->id,
'field_definition_id' => (int)$definition['id'],
]);
if (!$value instanceof AnimalMedicalVisitFieldValue) {
continue;
}
$this->customFields[$fieldKey] = (string)$value->value_text;
}
}
}