Статьи

Создание вашего стартапа: запрос изменений в расписании

Конечный продукт
Что вы будете создавать

Это руководство является частью серии Envato Tuts + Building Your Startup With PHP . В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе я буду публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете извлечь уроки. Я также буду решать вопросы, связанные с бизнесом по мере их возникновения.

Когда начался этап альфа-тестирования Планировщика собраний , самым явным недостатком была невозможность изменить собрание после того, как оно было запланировано. Это не простая проблема. Можно ли просто поменять встречу без разрешения участника? Или ты должен спросить? Или же, в зависимости от вашей роли в организации встречи? Что если вы просто хотите спросить, можно ли встретиться через 15 минут — это должно быть легко, верно?

Решение всего этого потребовало размышлений о социальных аспектах организации встречи.

Со временем я осознал, что способность легко настраивать собрания после их планирования может привести к созданию или разрушению бренда Meeting Planner.

В нашем последнем выпуске «Расширенное планирование» я реализовал функцию « Внести изменения» , которая позволяет организатору или участнику с правами на организацию по существу перепланировать собрание, не спрашивая разрешения. В сегодняшнем уроке я расскажу вам, как создать инфраструктуру запроса изменений . Это требует, чтобы участники запрашивали изменения, а затем другие могли принять или отклонить их, влияя на детали календаря окончательной встречи.

Пока вы читаете, я надеюсь, что вы попробуете новую функцию «запросить изменение» на живом сайте и поделитесь своими мыслями и отзывами в комментариях ниже. Я участвую в обсуждениях, но вы также можете связаться со мной @reifman в Twitter. Я всегда открыт для новых идей для планировщика собраний, а также предложений для будущих серий.

Напоминаем, что весь код для Meeting Planner предоставляется с открытым исходным кодом и написан на Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с моей параллельной серией Программирование с Yii2 .

Давайте начнем.

Помимо функций просмотра собрания и планирования, Запрос изменений требует больше времени и нового кода, чем любая другая функция в этом проекте.

Как я уже упоминал в прошлом эпизоде, кодирование всего с использованием базовой безопасности занимает немного больше времени, чем быстрое создание прототипов, но проектирование и создание этой функции также коснулись многих других областей платформы:

  • Разработка с социальной инженерией запроса и внесения изменений в график.
  • Простота UX для запросов на изменение, помогая людям запрашивать изменения и отвечать на них, не загромождая интерфейс.
  • Обработка запросов для собраний 1: 1 была бы простой, но кодирование функции предстоящих нескольких участников потребовало бы гораздо более сложной инфраструктуры.
  • Обработка ответов на запросы с несколькими участниками впереди.
  • Отправка по электронной почте уведомлений о новых и отозванных запросах, принятых и отклоненных ответах.
  • Обновление подтверждения встречи и деталей календаря, когда принятые запросы влияют на расписание.

Итак, хотя это не идеальное представление об изменениях только для этой функции, вот скриншоты возможного извлечения кода производственного сервера.

Вот изменения в существующий код:

Изменения в планировании запроса на запуск - изменения файла Git Pull

И вот новые файлы:

Создайте свой запрос на запуск Запланированные изменения - Git Pull New Files

Было много нового кода, связанного с этой функцией.

В конечном итоге я выбрал архитектуру, основанную на двух таблицах. Первым был Requests :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
$this->createTable(‘{{%request}}’, [
    ‘id’ => Schema::TYPE_PK,
    ‘meeting_id’ => Schema::TYPE_INTEGER.’
    ‘requestor_id’ => Schema::TYPE_BIGINT.’
    ‘completed_by’ => Schema::TYPE_BIGINT.’
    ‘time_adjustment’ => Schema::TYPE_SMALLINT .
    ‘alternate_time’ => Schema::TYPE_BIGINT.’
    ‘meeting_time_id’ => Schema::TYPE_BIGINT.’
    ‘place_adjustment’ => Schema::TYPE_SMALLINT .
    ‘meeting_place_id’ => Schema::TYPE_BIGINT.’
    ‘note’ => Schema::TYPE_TEXT.’
    ‘status’ => Schema::TYPE_SMALLINT .
    ‘created_at’ => Schema::TYPE_INTEGER .
    ‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
$this->addForeignKey(‘fk_request_meeting’, ‘{{%request}}’, ‘meeting_id’, ‘{{%meeting}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
$this->addForeignKey(‘fk_request_user’, ‘{{%request}}’, ‘requestor_id’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);

Вот константы, которые объясняют модель дальше:

01
02
03
04
05
06
07
08
09
10
11
const STATUS_OPEN = 0;
 const STATUS_ACCEPTED = 10;
 const STATUS_REJECTED = 20;
 const STATUS_WITHDRAWN = 30;
 
 const TIME_ADJUST_NONE = 50;
 const TIME_ADJUST_ABIT = 60;
 const TIME_ADJUST_OTHER = 70;
 
 const PLACE_ADJUST_NONE = 80;
 const PLACE_ADJUST_OTHER = 90;

Существует два способа настройки времени: TIME_ADJUST_ABIT , то есть интервалы в минуты или часы раньше или позже выбранного времени, или TIME_ADJUST_OTHER , другое время встречи в целом.

И вторая таблица была RequestResponses :

1
2
3
4
5
6
7
8
9
$this->createTable(‘{{%request_response}}’, [
    ‘id’ => Schema::TYPE_PK,
    ‘request_id’ => Schema::TYPE_INTEGER.’
    ‘responder_id’ => Schema::TYPE_BIGINT.’
    ‘note’ => Schema::TYPE_TEXT.’
    ‘response’ => Schema::TYPE_SMALLINT .
    ‘created_at’ => Schema::TYPE_INTEGER .
    ‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);

В основном, кто запросил изменение, кто на него отреагировал и каков был ответ: принять или отклонить.

Вторая таблица необходима для среды с несколькими участниками.

Организаторы и участники собрания могут получить доступ к Запросу изменений через раскрывающееся меню « Параметры», которое мы создали в последнем эпизоде:

Создайте свой запрос на запуск Планирование изменений - меню параметров запросов Изменения

actionCreate() в RequestController.php загружает форму, из которой изменяется запрос пользователя:

Создайте свой запрос на запуск Планирование изменений - запросите форму изменения

И вот тут началась сложность. Какие изменения могут запросить участники?

  • Хочешь встретиться раньше или позже?
  • Вы хотите встретиться в совершенно другое время?
  • Вы хотите встретиться в другом месте?

Примечание. Я еще не реализовал возможность добавления новых мест и времени — в настоящее время вы можете выбирать альтернативные даты и места из тех, которые были предложены в процессе планирования.

Код для создания выпадающего списка был сложным. Я сделал так, чтобы вы могли выбирать время на два с половиной часа раньше или позже, с 15-минутными приращениями, близкими к исходному времени, и 30-минутными приращениями после этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
for ($i=1;$i<12;$i++) {
  // later times
  if ($i<4 || $i%2 == 0) {
    $altTimesList[$chosenTime->start+($i*15*60)]=Meeting::friendlyDateFromTimestamp($chosenTime->start+($i*15*60),$timezone,false);
  }
  // earlier times
  $earlierIndex = ((12-$i)*-15);
  if ($i%2 == 0 || $i>=9) {
    $altTimesList[$chosenTime->start+($earlierIndex*60)]=Meeting::friendlyDateFromTimestamp($chosenTime->start+($earlierIndex*60),$timezone,false);
  }
}
$altTimesList[$chosenTime->start]=’────────────────────’;
$altTimesList[-1000]=Yii::t(‘frontend’,’Select an alternate time below’);
ksort($altTimesList);

Я заполнил $altTimesList с ключами каждого возможного времени со значениями дружественного времени, настроенного для часового пояса пользователя. Затем я использовал ksort() чтобы отсортировать выпадающий список, чтобы раньше показывалось раньше.

Один из советников Планировщика собраний (в данный момент у меня есть только один) предложил показать текущее выбранное время встречи, что я и сделал ниже. Я также добавил разделитель с отключенной опцией в выпадающем списке. Он делит более ранние времена с более поздних, но не выбирается

Создайте свои изменения при планировании запросов на запуск - улучшенная форма запроса с разделителем

Вот выпадающий код, который показывает, как отключить разделитель на основе его индексного ключа $currentStart :

1
2
3
4
5
6
7
<?php
echo $form->field($model, ‘alternate_time’)->label(Yii::t(‘frontend’,’Choose a time slightly earlier or later than {currentStartStr}’,[‘currentStartStr’=>$currentStartStr]))
  ->dropDownList(
      $altTimesList,
      [‘options’ => [$currentStart => [‘disabled’ => true]]]
  );
  ?>

И когда участники хотят выбрать другой случай, есть JQuery, чтобы изменить выпадающие списки, еще одна сложность для построения форм:

1
2
3
4
<?php ActiveForm::end();
    $this->registerJsFile(MiscHelpers::buildUrl().’/js/request.js’,[‘depends’ => [\yii\web\JqueryAsset::className()]]);
    ?>
</div>

Вот /frontend/web/js/request.js:

01
02
03
04
05
06
07
08
09
10
11
12
$(«#adjust_how» ).change(function() {
   if ($(«#adjust_how» ).val()==50) {
     $(«#choose_earlier»).addClass(‘hidden’);
     $(«#choose_another»).addClass(‘hidden’);
   } else if ($(«#adjust_how» ).val()==60) {
     $(«#choose_earlier»).removeClass(‘hidden’);
     $(«#choose_another»).addClass(‘hidden’);
   } else {
     $(«#choose_earlier»).addClass(‘hidden’);
     $(«#choose_another»).removeClass(‘hidden’);
   }
 });

Вот как выглядит форма со скрытым альтернативным временем:

Создайте свой запрос на запуск Планирование изменений - выбор другого места

Различные места просто интегрированы в выпадающий список мест (как вы можете видеть на верхнем изображенном изображении).

Как только запрос сделан, мы уведомляем запросившего, что другие участники встречи будут уведомлены. И, когда существуют активные запросы на собрание, есть ссылка на Просмотр их :

Создайте свой запрос на запуск Планирование изменений - страница собрания Просмотр запросов

Я решил, что это будет простой, беспрепятственный подход для людей, чтобы получить доступ к запросам.

Вот список запросов на встречу, чаще всего только один:

Создайте свой запрос на запуск Запланированные изменения - список запросов на собрание

Request::buildSubject() создает приведенную выше строку на основе содержимого запроса, т.е. изменения времени и / или места:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public static function buildSubject($request_id,$include_requestor = true) {
  $r = Request::findOne($request_id);
  $requestor = MiscHelpers::getDisplayName($r->requestor_id);
  $timezone = MiscHelpers::fetchUserTimezone(Yii::$app->user->getId());
  $rtime =»;
  $place = »;
  switch ($r->time_adjustment) {
    case Request::TIME_ADJUST_NONE:
    break;
    case Request::TIME_ADJUST_ABIT:
      $rtime = Meeting::friendlyDateFromTimestamp($r->alternate_time,$timezone);
    break;
    case Request::TIME_ADJUST_OTHER:
      $t = MeetingTime::findOne($r->meeting_time_id);
      if (!is_null($t)) {
          $rtime = Meeting::friendlyDateFromTimestamp($t->start,$timezone);;
      }
    break;
  }
  if ($r->place_adjustment == Request::PLACE_ADJUST_NONE || $r->place_adjustment == 0 && $r->meeting_place_id ==0 ) {
    // do nothing
  } else {
    // get place name
    $place = MeetingPlace::findOne($r->meeting_place_id)->place->name;
  }
  $result = $requestor.Yii::t(‘frontend’,’ asked to meet at ‘);
  if ($rtime==» && $place ==») {
    $result.=Yii::t(‘frontend’,’oops…no changes were requested.’);
  } else if ($rtime<>») {
    $result.=$rtime;
    if ($place<>») {
      $result.=Yii::t(‘frontend’,’ and ‘);
    }
  }
  if ($place<>») {
    $result.=$place;
  }
  return $result;
}

Эта функция также используется в уведомлениях по электронной почте.

В RequestController.php также есть ограничения, которые не позволяют пользователям делать более одного запроса за одно собрание:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public function actionCreate($meeting_id)
   {
       // verify is attendee
       if (!Meeting::isAttendee($meeting_id,Yii::$app->user->getId())) {
         $this->redirect([‘site/authfailure’]);
       }
       if (Request::countRequestorOpen($meeting_id,Yii::$app->user->getId())>0) {
           $r = Request::find()
             ->where([‘meeting_id’=>$meeting_id])
             ->andWhere([‘requestor_id’=>Yii::$app->user->getId()])
             ->andWhere([‘status’=>Request::STATUS_OPEN])
             ->one();
           Yii::$app->getSession()->setFlash(‘info’, Yii::t(‘frontend’,’You already have an existing request below.’));
             return $this->redirect([‘view’,’id’=>$r->id]);
       }

Вот страница запроса просмотра, показывающая ограничение:

Создайте свой запрос на запуск Запланированные изменения - Просмотр запроса

Если это ваш собственный запрос, вы можете отозвать свой запрос .

Как видите, для этого было создано множество разнообразных UX-функций. И я не показал вам, когда люди, кроме запрашивающего, отвечают.

В процессе создания этих функций я решил создать generic_html электронной почты generic_html и generic_text а также многократно используемую функцию Request::notify() чтобы упростить доставку объявлений различного типа вокруг Meeting Planner.

Вот метод Request::create() для подготовки электронного письма:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public function create() {
  $user_id = $this->requestor_id;
  $meeting_id = $this->meeting_id;
  $subject = Request::buildSubject($this->id);
  $content=[
    ‘subject’ => Yii::t(‘frontend’,’Change Requested to Your Meeting’),
    ‘heading’ => Yii::t(‘frontend’,’Requested Change to Your Meeting’),
    ‘p1’ => $subject,
    ‘p2’ => $this->note,
    ‘plain_text’ => $subject.’
  ];
  $button= [
    ‘text’ => Yii::t(‘frontend’,’Respond to Request’),
    ‘command’ => Meeting::COMMAND_VIEW_REQUEST,
    ‘obj_id’ => $this->id,
  ];
  $this->notify($user_id,$meeting_id, $content,$button);
  // add to log
  MeetingLog::add($meeting_id,MeetingLog::ACTION_REQUEST_SENT,$user_id,0);
}

$content массив заполняется для темы электронного письма, заголовка сообщения и абзацев, а массив $button используется для любой командной кнопки, например, Ответить на запрос или Просмотреть собрание .

Вот метод notify() , аналогичный предыдущим действиям send() и finalize() которые отправляют электронную почту:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public static function notify($user_id,$meeting_id,$content,$button = false) {
  // sends a generic message based on arguments
  $mtg = Meeting::findOne($meeting_id);
  // build an attendees array for all participants without contact information
  $cnt =0;
  $attendees = array();
  foreach ($mtg->participants as $p) {
      $auth_key=\common\models\User::find()->where([‘id’=>$p->participant_id])->one()->auth_key;
      $attendees[$cnt]=[‘user_id’=>$p->participant_id,’auth_key’=>$auth_key,
      ’email’=>$p->participant->email,
      ‘username’=>$p->participant->username];
      $cnt+=1;
  }
  // add organizer
  $auth_key=\common\models\User::find()->where([‘id’=>$mtg->owner_id])->one()->auth_key;
  $attendees[$cnt]=[‘user_id’=>$mtg->owner_id,’auth_key’=>$auth_key,
    ’email’=>$mtg->owner->email,
    ‘username’=>$mtg->owner->username];
// use this code to send
foreach ($attendees as $cnt=>$a) {
  // check if email is okay and okay from this sender_id
  if ($user_id != $a[‘user_id’] && User::checkEmailDelivery($a[‘user_id’],$user_id)) {
    Yii::$app->timeZone = $timezone = MiscHelpers::fetchUserTimezone($a[‘user_id’]);
      // Build the absolute links to the meeting and commands
      $links=[
        ‘home’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_HOME,0,$a[‘user_id’],$a[‘auth_key’]),
        ‘view’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW,0,$a[‘user_id’],$a[‘auth_key’]),
        ‘footer_email’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_EMAIL,0,$a[‘user_id’],$a[‘auth_key’]),
        ‘footer_block’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK,$user_id,$a[‘user_id’],$a[‘auth_key’]),
        ‘footer_block_all’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK_ALL,0,$a[‘user_id’],$a[‘auth_key’]),
      ];
      if ($button!==false) {
        $links[‘button_url’]=MiscHelpers::buildCommand($mtg->id,$button[‘command’],$button[‘obj_id’],$a[‘user_id’],$a[‘auth_key’]);
        $content[‘button_text’]=$button[‘text’];
      }
      // send the message
      $message = Yii::$app->mailer->compose([
        ‘html’ => ‘generic-html’,
        ‘text’ => ‘generic-text’
      ],
      [
        ‘meeting_id’ => $mtg->id,
        ‘sender_id’=> $user_id,
        ‘user_id’ => $a[‘user_id’],
        ‘auth_key’ => $a[‘auth_key’],
        ‘links’ => $links,
        ‘content’=>$content,
        ‘meetingSettings’ => $mtg->meetingSettings,
    ]);
      // to do — add full name
    $message->setFrom(array(‘[email protected]’=>$mtg->owner->email));
    $message->setReplyTo(‘mp_’.$mtg->id.’@meetingplanner.io’);
    $message->setTo($a[’email’])
        ->setSubject($content[‘subject’])
        ->send();
    }
  }
}

По сути, макет generic_html.php основан на простом текстовом шаблоне обновления, о котором я говорил в наших руководствах по шаблонам электронной почты . Он предоставляет хорошо отформатированный способ обновления участников по электронной почте с несколькими абзацами.

Вот файл представления generic_html.php, объединяющий данные $content и $button . Проверяется второй и третий абзацы, например, $p2 , $p3   и данные $button :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<tr>
  <td style=»color:#777; font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:21px; text-align:center; border-collapse:collapse; padding:10px 60px 0; width:100%» align=»center» width=»100%»>
    <p>Hi <?php echo Html::encode(MiscHelpers::getDisplayName($user_id));
    <p><?= Html::encode($content[‘p1’]) ?></p>
    <?php if ($content[‘p2’]<>») {
    ?>
    <p><?= Html::encode($content[‘p2’]);
    <?php
      }
    ?>
    <?php if (isset($content[‘p3’]) && $content[‘p3’]<>») {
    ?>
    <p><?= Html::encode($content[‘p3’]);
    <?php
      }
    ?>
  </td>
</tr>
<?php if ($links[‘button_url’]!=») {
 ?>
<tr>
  <td style=»color:#777; font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:21px; text-align:center; border-collapse:collapse; padding:30px 0 30px 0″ align=»center»>
    <div>
<!—[if mso]>
      <v:roundrect xmlns:v=»urn:schemas-microsoft-com:vml» xmlns:w=»urn:schemas-microsoft-com:office:word» href=»http://» style=»height:45px;v-text-anchor:middle;width:155px;»
        <w:anchorlock/>
        <center style=»color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;»>My Account</center>
      </v:roundrect>
    <![endif]—><a href=»<?php echo $links[‘button_url’] ?>» style=’color:#fff;
  </div>
  </td>
</tr>
<?php } ?>

Вот пример уведомления, которое Роб Смит попросил меня изменить время и место нашей встречи (сгенерированный из кода выше):

Создайте свой запрос на запуск Планирование изменений - уведомление по электронной почте о запрошенном изменении

Когда я нажимаю « Ответить на запрос» , меня RequestResponse в actionCreate() контроллера actionCreate() :

Создайте изменения при планировании запроса на запуск - ответ на форму запроса - принять или отклонить

На протяжении всего запроса UX я включал возможность для людей писать заметки, предоставляя контекст для запросов и ответов.

Одной из проблем этой формы было определение того, как направлять ответы на различные методы контроллера, в зависимости от того, какая кнопка отправки была нажата. Другими словами, различать различные нажатия кнопки POST.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public function actionCreate($id)
{
  $request = Request::findOne($id);
  if (!Meeting::isAttendee($request->meeting_id,Yii::$app->user->getId())) {
    $this->redirect([‘site/authfailure’]);
  }
    // has this user already responded
    $check = RequestResponse::find()
      ->where([‘request_id’=>$id])
      ->andWhere([‘responder_id’=>Yii::$app->user->getId()])
      ->count();
    if ($check>0) {
      Yii::$app->getSession()->setFlash(‘error’, Yii::t(‘frontend’,’Sorry, you already responded to this request.’));
      return $this->redirect([‘meeting/view’, ‘id’ => $request->meeting_id]);
    }
    if ($request->requestor_id == Yii::$app->user->getId()) {
      Yii::$app->getSession()->setFlash(‘error’, Yii::t(‘frontend’,’Sorry, can not respond to your own request.’));
      return $this->redirect([‘meeting/view’, ‘id’ => $request->meeting_id]);
    }
    $subject = Request::buildSubject($id);
    $model = new RequestResponse();
    $model->request_id = $id;
    $model->responder_id = Yii::$app->user->getId();
    if ($model->load(Yii::$app->request->post()) ) {
      $posted = Yii::$app->request->post();
      if (isset($posted[‘accept’])) {
        // accept
        $model->response = RequestResponse::RESPONSE_ACCEPT;
        $model->save();
        $request->accept($model);
        Yii::$app->getSession()->setFlash(‘success’, Yii::t(‘frontend’,’Request accepted. We will update the meeting details and inform other participants.’));
      } else {
        // reject
        $model->response = RequestResponse::RESPONSE_REJECT;
        $model->save();
        $request->reject($model);
        Yii::$app->getSession()->setFlash(‘success’, Yii::t(‘frontend’,’Your decline has been recorded. We will let other participants know.’));
      }
      return $this->redirect([‘/meeting/view’, ‘id’ => $request->meeting_id]);
    } else {
        return $this->render(‘create’, [
            ‘model’ => $model,
            ‘subject’ => $subject,
            ‘meeting_id’ => $request->meeting_id,
        ]);
    }
}

Вот /frontend/views/request-response/_form.php:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<div class=»request-response-form»>
    <?php $form = ActiveForm::begin();
      <?= BaseHtml::activeHiddenInput($model, ‘responder_id’);
        <?= BaseHtml::activeHiddenInput($model, ‘request_id’);
    <?= $form->field($model, ‘note’)->label(Yii::t(‘frontend’,’Include a note’))->textarea([‘rows’ => 6])->hint(Yii::t(‘frontend’,’optional’)) ?>
 
<div class=»form-group»>
  <?= Html::submitButton(Yii::t(‘frontend’, ‘Accept Request’),
        [‘class’ => ‘btn btn-success’,’name’=>’accept’,]) ?>
  <?= Html::submitButton(Yii::t(‘frontend’, ‘Decline Request’),
        [‘class’ => ‘btn btn-danger’,’name’=>’reject’,
      ‘data’ => [
          ‘confirm’ => Yii::t(‘frontend’, ‘Are you sure you want to decline this request?’),
          ‘method’ => ‘post’,
      ],]) ?>

По сути, я просто добавил значения name 'accept' или 'reject' для каждой кнопки. Затем это доставляется как объявленное значение, как показано:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
if ($model->load(Yii::$app->request->post()) ) {
     $posted = Yii::$app->request->post();
     if (isset($posted[‘accept’])) {
       // accept
       $model->response = RequestResponse::RESPONSE_ACCEPT;
       $model->save();
       $request->accept($model);
       Yii::$app->getSession()->setFlash(‘success’, Yii::t(‘frontend’,’Request accepted. We will update the meeting details and inform other participants.’));
     } else {
       // reject
       $model->response = RequestResponse::RESPONSE_REJECT;
       $model->save();
       $request->reject($model);
       Yii::$app->getSession()->setFlash(‘success’, Yii::t(‘frontend’,’Your decline has been recorded. We will let other participants know.’));
     }
     return $this->redirect([‘/meeting/view’, ‘id’ => $request->meeting_id]);
   }

Когда респондент принимает или отклоняет запрос, ему показывают флэш-сообщение и отправляют электронное письмо. Кроме того, у собрания больше нет активных запросов на показ:

Создайте свой запрос на запуск Запланированные изменения - страница собрания после того, как запрос принят

Вот электронное письмо с уведомлением о запрошенном изменении :

Постройте свой запрос на запуск Планирование изменений - уведомление по электронной почте о запрошенном изменении принимается

Многое происходит в Request::accept() ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public function accept($request_response) {
  // to do — this will need to change when there are multiple participants
  $this->status = Request::STATUS_ACCEPTED;
  $this->update();
  $m = Meeting::findOne($this->meeting_id);
  // is there a new time
  switch ($this->time_adjustment) {
    case Request::TIME_ADJUST_ABIT:
      // create a new meeting time with alternate_time
      $this->meeting_time_id = MeetingTime::addFromRequest($this->id);
      $this->update();
      // mark as selected
      MeetingTime::setChoice($this->meeting_id,$this->meeting_time_id,$request_response->responder_id);
    break;
    case Request::TIME_ADJUST_OTHER:
     // mark as selected
      MeetingTime::setChoice($this->meeting_id,$this->meeting_time_id,$request_response->responder_id);
    break;
  }
  // is there a different place
  if ($this->place_adjustment == Request::PLACE_ADJUST_OTHER || $this->meeting_place_id !=0 ) {
    MeetingPlace::setChoice($this->meeting_id,$this->meeting_place_id,$request_response->responder_id);
  }
  if ($m->isOwner($request_response->responder_id)) {
    // they are an organizer
    $this->completed_by =$request_response->responder_id;
    $this->update();
    MeetingLog::add($this->meeting_id,MeetingLog::ACTION_REQUEST_ORGANIZER_ACCEPT,$request_response->responder_id,$this->id);
  } else {
    // they are a participant
    MeetingLog::add($this->meeting_id,MeetingLog::ACTION_REQUEST_ACCEPT,$request_response->responder_id,$this->id);
  }
  $user_id = $request_response->responder_id;
  $subject = Request::buildSubject($this->id, true);
  $p1 = MiscHelpers::getDisplayName($user_id).Yii::t(‘frontend’,’ accepted the request: ‘).$subject;
  $p2 = $request_response->note;
  $p3 = Yii::t(‘frontend’,’You will receive an updated meeting confirmation reflecting these change(s). It will also include an updated attachment for your Calendar.’);
  $content=[
    ‘subject’ => Yii::t(‘frontend’,’Accepted Requested Change to Meeting’),
    ‘heading’ => Yii::t(‘frontend’,’Requested Change Accepted’),
    ‘p1’ => $p1,
    ‘p2’ => $p2,
    ‘p3’ => $p3,
    ‘plain_text’ => $p1.’
  ];
  $button= [
    ‘text’ => Yii::t(‘frontend’,’View the Meeting’),
    ‘command’ => Meeting::COMMAND_VIEW,
    ‘obj_id’ => 0,
  ];
  $this->notify($user_id,$this->meeting_id, $content,$button);
  // Make changes to the Meeting
  $m->increaseSequence();
  // resend the finalization — which also needs to be done for resend invitation
  $m->finalize($m->owner_id);
}

Перед отправкой электронного письма расписание встреч обновляется, чтобы отразить любую новую дату / время и / или новое место. После отправки электронного письма встреча завершается. Это обеспечивает новое обновление встречи с обновленным файлом календаря для всех участников:

Создайте свой запрос на запуск Запланированные изменения - Обновление уведомления о собрании

Я надеюсь, вам понравился этот урок. Создание этой функции заняло больше времени, чем я надеялся, но получилось довольно хорошо. Я думаю, что это добавляет измерение планированию с Meeting Planner, не имеющему аналогов в других службах.

Если вы еще этого не сделали, запланируйте свою первую встречу с Планировщиком совещаний . Я продолжал добиваться невероятного прогресса в выпуске бета-версии, несмотря на отвлекающие факторы (кодирование сложно):

Я также планирую написать учебное пособие по краудфандингу, поэтому просим вас посетить нашу страницу Планировщика собраний WeFunder .

Пожалуйста, поделитесь своими комментариями ниже. Я всегда открыт для новых идей и тематических предложений для будущих уроков. Вы также можете обратиться ко мне @reifman .

Следите за всеми этими и другими учебниками, ознакомившись с серией « Построение стартапа на PHP» . И, конечно же, ознакомьтесь с нашей Программой для серии Yii2 (Envato Tuts +) .