Валидация. Зависимые атрибуты

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
micpan
Сообщения: 3
Зарегистрирован: 2022.07.28, 10:57

Валидация. Зависимые атрибуты

Сообщение micpan »

Друзья, добрый день!

Подскажите как организовать такой функционал: если накладная проведена, нельзя менять клиента.
мой вариант сделать собственный валидатор в модели, но может есть другие методы?

и связанный вопрос: где организовать логику типа: при проведении накладной сделать то-то. А ля БД триггер уровня строки. То есть если старый атрибут $model->ok пуст а новый нет. Есть мысль закинуть все в afterSave но так ли по канонам?

Заранее спасибо за науку!
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Валидация. Зависимые атрибуты

Сообщение ElisDN »

По канонам – делать для каждой операции отдельные экшены со своими формами. В данном случае вынести операции проведения накладной и смены клиента. И там уже по месту что-то делать или проверять.
micpan
Сообщения: 3
Зарегистрирован: 2022.07.28, 10:57

Re: Валидация. Зависимые атрибуты

Сообщение micpan »

А по месту этот как раз где именно? Я вот пытаюсь валидацдию осилить но никак не пойму почему у меня errors всегда пусто и валидация проходит:

Код: Выделить всё

        $model = new ZmPrh();

        if ($model->load(Yii::$app->request->post()) ) {
            $model->validate();
            $model->addError('user_ins', 'wtf?');
            Yii::warning(var_dump($model->errors));
            $model->save();
        }
Я из КРУДА работаю с формой приходной накладной, там все атрибуты в куче, а надо для каждого атрибута отдельную форму? Не могу проникнуться идеей...
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Валидация. Зависимые атрибуты

Сообщение ElisDN »

micpan писал(а): 2022.12.01, 10:50 Я вот пытаюсь валидацию осилить но никак не пойму почему у меня errors всегда пусто и валидация проходит?
В КРУДЕ обычно либо сразу сохраняют с валидацией:

Код: Выделить всё

if ($model->load(Yii::$app->request->post() && $model->save()) ) {
    return $this->refresh();
}
Либо отдельно валидируют, а потом сохраняют без повторной валидации:

Код: Выделить всё

if ($model->load(Yii::$app->request->post() && $model->validate()) ) {
    ...
    $model->save(false);
    return $this->refresh();
}
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Валидация. Зависимые атрибуты

Сообщение ElisDN »

micpan писал(а): 2022.12.01, 10:50 А по месту этот как раз где именно?
micpan писал(а): 2022.12.01, 10:50 Я из КРУДА работаю с формой приходной накладной, там все атрибуты в куче, а надо для каждого атрибута отдельную форму? Не могу проникнуться идеей...
Ну не все же там поля равноправные.

Есть поля, которые можно редактировать в любое время. Их можно оставить в actionUpdate.

А есть более сложные бизнес-операции вроде смены контрагента и проведения накладной. Их можно сделать отдельными кнопками и экшенами вроде actionChangeCounterparty и actionCarryOut. И как раз там всё проверять и проводить.

Для проверок и проводок можно в накладную добавить методы с понятными названиями:

Код: Выделить всё

class ZmPrh extends ActiveRecord
{
    // ...

    public function carryOut(): void
    {
        if ($this->isCarriedOut()) {
            throw new DomainException('Накладная уже проведена.');
        }
        $this->ok = true;
    }

    public function isCarriedOut(): bool
    {
        return $this->ok;
    }

    public function changeCounterparty(int $ins): void
    {
        if (!$this->canChangeCounterparty()) {
            throw new DomainException('Нельзя менять контрагента проведённой накладной.');
        }        
        if ($ins === $this->user_ins) {
            throw new DomainException('Этот контрагент уже назначен.');        
        }        
        $this->user_ins = $ins;
    }

    public function canChangeCounterparty(): bool
    {
        return !$this->ok;
    }
}
Потом в контроллере вызывать проводки и делать нужные операции:

Код: Выделить всё

public function actionCarryOut(int $id, Session $session): Response
{
    $model = $this->loadModel($id);

    try {
        $model->carryOut();
        $model->save(false);
        $session->setFlash('success', 'Накладная проведена.');
    } catch (DomainException $e) {
        $session->setFlash('error', $e->getMessage());
    }

    return $this->back();
}

public function actionChangeCounterparty(int $id, Request $request, Session $session, Mailer $mailer): Response|string
{
    $model = $this->loadModel($id);
    $form = new ChangeCounterpartyForm(['ins' => $model->user_ins]);

    if ($form->load($request->post()) && $form->validate()) {
        try {
            $model->changeCounterparty($form->ins);
            $model->save(false);

            $mailer->compose('mail', ['model' => $model])->setTo('...')->setSubject('...')->send();

            $session->setFlash('success', 'Контрагент изменён.');
            return $this->redirect(['view', 'id' => $id]);
        } catch (DomainException $e) {
            $session->setFlash('error', $e->getMessage());
        }
    }

    return $this->render('change-counterparty', ['model' => $model, 'form' => $form]);
}
И в представлении показывать или скрывать нужные кнопки:

Код: Выделить всё

<p>
    <?= Html::a('Изменить', ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?>

    <?php if (!$model->isCarriedOut()): ?>
        <?= Html::a('Провести', ['carry-out', 'id' => $model->id], ['class' => 'btn btn-success', 'data' => [
            'method' => 'post',  'confirm' => 'Провести?'
        ]]) ?>
    <?php endif; ?>
    
    <?php if ($model->canChangeCounterparty()): ?>
        <?= Html::a('Сменить контрагента', ['change-counterparty', 'id' => $model->id], ['class' => 'btn btn-primary']) ?>
    <?php endif; ?>
    
    <?= Html::a('Удалить', ['delete', 'id' => $model->id], ['class' => 'btn btn-danger', 'data' => [
        'method' => 'post',  'confirm' => 'Удалить?'
    ]]) ?>
</p>
micpan
Сообщения: 3
Зарегистрирован: 2022.07.28, 10:57

Re: Валидация. Зависимые атрибуты

Сообщение micpan »

Дмитрий, благодарю!
Отдельный респект за твои ценные обучающие материалы! :)
Ответить