portal.mkgtu.ru/common/modules/abiturient/models/drafts/DraftsManager.php

488 lines
18 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace common\modules\abiturient\models\drafts;
use Closure;
use common\models\EmptyCheck;
use common\components\EntrantModeratorManager\interfaces\IEntrantManager;
use common\models\interfaces\FileToSendInterface;
use common\models\interfaces\IHaveIgnoredOnCopyingAttributes;
use common\models\relation_presenters\BaseRelationPresenter;
use common\models\User;
use common\modules\abiturient\models\AbiturientQuestionary;
use common\modules\abiturient\models\bachelor\ApplicationType;
use common\modules\abiturient\models\bachelor\BachelorApplication;
use common\modules\abiturient\models\bachelor\changeHistory\interfaces\ModelWithChangeHistoryHandlerInterface;
use common\modules\abiturient\models\interfaces\ApplicationInterface;
use common\modules\abiturient\models\interfaces\IDraftable;
use common\modules\abiturient\models\interfaces\IHaveCallbackAfterDraftCopy;
use common\modules\abiturient\models\NeedBlockAndUpdateProcessor;
use Throwable;
use Yii;
use yii\base\Model;
use yii\base\UserException;
use yii\db\ActiveQuery;
use yii\db\ActiveRecord;
use yii\helpers\ArrayHelper;
class DraftsManager
{
const REASON_RETURN = 'return_docs';
const REASON_SENT = 'sent';
const REASON_DECLINED = 'declined';
const REASON_REJECTED_BY_1C = 'master_system_error';
const REASON_APPROVED = 'approved';
const REASON_ACTUAL_UPDATED_FROM_1C = 'actual_app_updated';
const REASON_UPDATED_FROM_1C = 'updated';
const REASON_MASS_REMOVAL_ADMINISTRATOR = 'mass_removal_administrator';
const ARCHIVE_REASONS = [
DraftsManager::REASON_RETURN => 'Подан отзыв документов',
DraftsManager::REASON_SENT => 'Подано на проверку',
DraftsManager::REASON_DECLINED => 'Отклонено модератором',
DraftsManager::REASON_REJECTED_BY_1C => 'Отклонено 1С',
DraftsManager::REASON_APPROVED => 'Принято',
DraftsManager::REASON_ACTUAL_UPDATED_FROM_1C => 'Чистовик обновлён из 1С',
DraftsManager::REASON_UPDATED_FROM_1C => 'Обновлено из 1С',
DraftsManager::REASON_MASS_REMOVAL_ADMINISTRATOR => 'Массовое удаление администратором',
];
public static $attributes_to_ignore = [
'id',
'created_at',
'updated_at',
];
public static function makeCopy(
ActiveRecord $from,
ActiveRecord $to = null,
bool $wrap_in_transaction = true,
Closure $additional_model_attributes_provider = null
): ActiveRecord
{
$class = get_class($from);
if (!$to) {
$to = new $class();
}
$processed_attributes = DraftsManager::excludeIgnoredProps($from);
$transaction = null;
if ($wrap_in_transaction) {
$transaction = Yii::$app->db->beginTransaction();
}
try {
$additional_model_attributes = [];
if ($additional_model_attributes_provider) {
$additional_model_attributes = $additional_model_attributes_provider($to);
}
$processed_attributes = ArrayHelper::merge($processed_attributes, $additional_model_attributes);
DraftsManager::setModelAttributes($to, $processed_attributes);
if ($to instanceof IHaveCallbackAfterDraftCopy && $from instanceof FileToSendInterface) {
$to->afterDraftCopy($from);
}
if ($from instanceof IHasRelations) {
foreach ($from->getRelationsInfo() as $relationInfo) {
$attrs = $relationInfo->copyRelatedRecords($to);
$processed_attributes = ArrayHelper::merge($processed_attributes, $attrs);
DraftsManager::setModelAttributes($to, $processed_attributes);
}
}
} catch (\Throwable $e) {
if ($wrap_in_transaction) {
$transaction->rollBack();
}
Yii::error("Ошибка клонирования {$class}: {$e->getMessage()}", 'cloning');
throw $e;
}
if ($wrap_in_transaction) {
$transaction->commit();
}
return $to;
}
public static function SuspendHistory($model)
{
if ($model instanceof ModelWithChangeHistoryHandlerInterface) {
$handler = $model->getChangeHistoryHandler();
if ($handler) {
$handler->setDisabled(true);
}
}
}
public static function excludeIgnoredProps(Model $model_with_attributes)
{
$props = $model_with_attributes->attributes;
$ignored_attributes = DraftsManager::$attributes_to_ignore;
if ($model_with_attributes instanceof IHaveIgnoredOnCopyingAttributes) {
$ignored_attributes = $model_with_attributes->getIgnoredOnCopyingAttributes();
}
foreach ($ignored_attributes as $attr) {
if (array_key_exists($attr, $props)) {
unset($props[$attr]);
}
}
return $props;
}
public static function setModelAttributes(ActiveRecord $model, array $attributes)
{
foreach ($attributes as $key => $value) {
if ($model->hasAttribute($key)) {
$model->{$key} = $value;
}
}
DraftsManager::SuspendHistory($model);
$model
->loadDefaultValues()
->save(false);
}
public static function ensurePersisted(ActiveRecord $model): ActiveRecord
{
if ($model->getIsNewRecord()) {
DraftsManager::SuspendHistory($model);
$model
->loadDefaultValues()
->save(false);
}
return $model;
}
public static function getOrCreateApplicationDraftByOtherDraft(BachelorApplication $from, int $draft_status): BachelorApplication
{
if ($from->draft_status == $draft_status) {
return $from;
}
$draft = DraftsManager::getApplicationDraftByOtherDraft($from, $draft_status);
if ($draft && $draft->status != $from->status) {
$draft
->setArchiveInitiator(Yii::$app->user->identity)
->setArchiveReason(DraftsManager::REASON_DECLINED)
->archive();
$draft = null;
}
if (!$draft) {
$draft = DraftsManager::createApplicationDraftByOtherDraft($from, $draft_status);
}
return $draft;
}
public static function getApplicationDraftByOtherDraft(BachelorApplication $application, int $draft_status): ?BachelorApplication
{
if (!$application->user) {
return null;
}
if ($application->draft_status == $draft_status) {
return $application;
}
return DraftsManager::getApplicationDraft($application->user, $application->type, $draft_status);
}
public static function createApplicationDraftByOtherDraft(BachelorApplication $from, int $to_draft_status, int $status = null): BachelorApplication
{
$from_draft_status = $from->draft_status;
$transaction = Yii::$app->db->beginTransaction();
$new_app = null;
try {
$new_app = DraftsManager::makeCopy($from);
$new_app->draft_status = $to_draft_status;
if (is_null($status)) {
$new_app = DraftsManager::setupApplicationStatus($new_app);
} else {
$new_app->status = $status;
}
$new_app->synced_with_1C_at = ($from->draft_status == IDraftable::DRAFT_STATUS_APPROVED ? max($from->approved_at, $from->sent_at, $from->synced_with_1C_at) : $from->synced_with_1C_at);
$new_app
->loadDefaultValues()
->setParentDraft($from)
->save(false);
$new_app = ApplicationAndQuestionaryLinker::setUpQuestionaryLink($new_app);
if ($to_draft_status != $from_draft_status && !$from->isArchive()) {
$from_questionary = $from->getAbiturientQuestionary()->one();
if ($to_draft_status == IDraftable::DRAFT_STATUS_APPROVED) {
ApplicationAndQuestionaryLinker::copyQuestionaryToActual($from_questionary);
} elseif ($from_draft_status == IDraftable::DRAFT_STATUS_APPROVED && $to_draft_status == IDraftable::DRAFT_STATUS_CREATED) {
ApplicationAndQuestionaryLinker::copyQuestionaryToDraft($from_questionary);
}
}
$transaction->commit();
} catch (Throwable $e) {
$transaction->rollBack();
throw $e;
}
return $new_app;
}
public static function getApplicationDraftQuery(User $user, ApplicationType $applicationType, int $draft_status): ActiveQuery
{
$tn = BachelorApplication::tableName();
return BachelorApplication::find()
->active()
->andWhere([
"{$tn}.user_id" => $user->id,
"{$tn}.type_id" => $applicationType->id,
])
->andWhere([
"{$tn}.draft_status" => $draft_status,
])
->orderBy(["{$tn}.updated_at" => SORT_DESC]);
}
public static function getApplicationDraft(User $user, ApplicationType $applicationType, int $draft_status): ?BachelorApplication
{
return DraftsManager::getApplicationDraftQuery($user, $applicationType, $draft_status)
->limit(1)
->one();
}
public static function createArchivePoint(BachelorApplication $entity, string $reason, int $draft_status, $initiator = null): BachelorApplication
{
if (!$initiator) {
$initiator = Yii::$app->user->identity;
}
$new_entity = DraftsManager::createApplicationDraftByOtherDraft($entity, $draft_status, $entity->status);
$entity
->setArchiveInitiator($initiator)
->setArchiveReason($reason)
->archive();
return $new_entity;
}
public static function getOrCreateApplicationDraft(User $user, ApplicationType $applicationType, int $draft_status): array
{
$created = false;
$app = DraftsManager::getApplicationDraft($user, $applicationType, $draft_status);
if (!$app) {
$created = true;
$app = new BachelorApplication();
$app->user_id = $user->id;
$app->type_id = $applicationType->id;
$app->draft_status = $draft_status;
$app = DraftsManager::setupApplicationStatus($app);
$app
->loadDefaultValues()
->save(false);
ApplicationAndQuestionaryLinker::setUpQuestionaryLink($app);
}
return [$app, $created];
}
private static function setupApplicationStatus(BachelorApplication $app)
{
$draft_status = $app->draft_status;
if ($draft_status == IDraftable::DRAFT_STATUS_APPROVED) {
$app->status = ApplicationInterface::STATUS_APPROVED;
} elseif ($draft_status == IDraftable::DRAFT_STATUS_CREATED) {
$app->status = ApplicationInterface::STATUS_CREATED;
} elseif (!$app->moderationAllowedByStatus()) {
$app->status = ApplicationInterface::STATUS_SENT;
}
return $app;
}
public static function getActualApplication(User $user, ApplicationType $type, bool $forced = false): ?BachelorApplication
{
$needs_update = false;
$isIn1C = $user->hasAppInOneS($type);
$application = DraftsManager::getApplicationDraft($user, $type, IDraftable::DRAFT_STATUS_APPROVED);
if ($isIn1C && $application && !$forced) {
[$needs_update, $_] = NeedBlockAndUpdateProcessor::getProcessedNeedBlockAndUpdate($application);
}
$transaction = Yii::$app->db->beginTransaction();
try {
if (!$isIn1C && $application) {
$application->delete();
$application = null;
}
if ($isIn1C) {
if (!$application) {
[$application, $_] = DraftsManager::getOrCreateApplicationDraft($user, $type, IDraftable::DRAFT_STATUS_APPROVED);
$forced = true;
}
if ($forced || $needs_update) {
$application->fullUpdateFrom1C();
if ($application->type->archive_actual_app_on_update) {
$application = DraftsManager::createArchivePoint(
$application,
DraftsManager::REASON_ACTUAL_UPDATED_FROM_1C,
IDraftable::DRAFT_STATUS_APPROVED
);
}
}
}
if ($application) {
$application->status = ApplicationInterface::STATUS_APPROVED;
if (EmptyCheck::isEmpty($application->approver_id)) {
$application->approver_id = 1;
}
$application
->loadDefaultValues()
->save(false);
}
$transaction->commit();
} catch (Throwable $e) {
$transaction->rollBack();
throw $e;
}
return $application;
}
public static function getActualQuestionary(User $user, bool $update = false): ?AbiturientQuestionary
{
if (!$user->userRef) {
return null;
}
$questionary = $user->getActualAbiturientQuestionary()->one();
$transaction = Yii::$app->db->beginTransaction();
try {
if (!$questionary) {
$editable_questionary = $user->getAbiturientQuestionary()->one();
if ($editable_questionary) {
$questionary = DraftsManager::makeCopy($editable_questionary);
} else {
$questionary = new AbiturientQuestionary();
$questionary->user_id = $user->id;
$questionary->loadDefaultValues();
}
$questionary->status = AbiturientQuestionary::STATUS_CREATE_FROM_1C;
$questionary->draft_status = IDraftable::DRAFT_STATUS_APPROVED;
$update = true;
}
if ($update) {
if (!$questionary->getFrom1CWithParents()) {
throw new UserException('Не удалось получить данные из Информационной системы вуза');
}
}
$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollBack();
Yii::error($e->getMessage(), 'ActualUpdate');
Yii::$app->session->setFlash('alert', [
'body' => $e->getMessage(),
'options' => ['class' => 'alert-danger']
]);
return null;
}
return $questionary;
}
public static function clearOldModerations(BachelorApplication $app, IEntrantManager $initiator, string $reason)
{
if ($app->isArchive()) {
throw new UserException("Вы работаете с устаревшей версией заявления");
}
$old_moderating_apps = BachelorApplication::find()
->active()
->andWhere(['draft_status' => IDraftable::DRAFT_STATUS_MODERATING])
->andWhere(['not', ['id' => $app->id]])
->andWhere([
'user_id' => $app->user->id,
'type_id' => $app->type->id,
])
->all();
foreach ($old_moderating_apps as $old_moderating_app) {
$old_moderating_app
->setArchiveInitiator($initiator)
->setArchiveReason($reason)
->archive();
}
}
public static function clearOldSendings(BachelorApplication $app, IEntrantManager $initiator, string $reason)
{
if ($app->isArchive()) {
throw new UserException("Вы работаете с устаревшей версией заявления");
}
$old_apps = BachelorApplication::find()
->active()
->andWhere(['draft_status' => IDraftable::DRAFT_STATUS_SENT])
->andWhere([
'user_id' => $app->user->id,
'type_id' => $app->type->id,
])
->andWhere(['not', ['id' => $app->id]])
->all();
foreach ($old_apps as $old_app) {
$old_app
->setArchiveInitiator($initiator)
->setArchiveReason($reason)
->archive();
}
}
public static function removeOldApproved(BachelorApplication $app, IEntrantManager $initiator, string $reason)
{
if ($app->isArchive()) {
throw new UserException("Вы работаете с устаревшей версией заявления");
}
$provide_real_deletion = !$app->type->archive_actual_app_on_update;
$old_apps = BachelorApplication::find()
->active()
->andWhere(['draft_status' => IDraftable::DRAFT_STATUS_APPROVED])
->andWhere([
'user_id' => $app->user->id,
'type_id' => $app->type->id,
])
->andWhere(['not', ['id' => $app->id]])
->all();
foreach ($old_apps as $old_app) {
if ($provide_real_deletion) {
$old_app->delete();
} else {
$old_app
->setArchiveInitiator($initiator)
->setArchiveReason($reason)
->archive();
}
}
}
}