Spanish (Español) translation by Elías Nicolás (you can also view the original English article)



Este tutorial es parte de la serie Envato Tuts+ Construyendo tu startup. En esta serie, te guiare para lanzando una startup desde el concepto de la realidad usando mi app Meeting Planner como un ejemplo de vida real. Cada paso del camino, publicare el codigo Meeting Planner como un open-source ejemplo con el cual puedes aprender También me ocuparé de asuntos relacionados con el inicio de negocios a medida que surjan.
Cambiando tus planes de reunión
Como el testeo de Meeting Planner alpha comienza, la clara característica faltante fue la inhabilidad de cambiar una reunión después de que fue programada No es un problema facil. ¿Esta bien solo cambiar una reunion sin el consentimiento del participante? ¿O deberia preguntar? ¿O hacer cualquiera?, dependiendo del rol que tengas organizando la reunión. ¿Que pasaría si solo quieres preguntar si esta bien reunirnos 15 mas tarde? —Debería ser facil, ¿no?
Resolviendo todo esto, requiere reflejar todos los aspectos sociales de cambiar una reunion
Con el tiempo, me di cuenta que la habilidad de cambiar una reunion facilmente que han programado podria hacer o quebrar la marca "Meeting Planner"
En nuestro ultimo episodio en programación avanzada, implemente Hacer Cambios, que permite un organizador o participante con permisos para esencialmente re programar una reunión sin pedir permiso En el tutorial de hoy, los guiare por todo el proceso de construir toda la infraestructura de "Pedir cambios" Requiere que los participantes pidan cambios y los otros puedan aceptar o declinar, afectando los detalles del calendario de la reunión final
Mientras lees, espero puedas intentar la característica "Pide un cambio" en el sitio y compartas sus pensamientos y opiniones en los comentarios de abajo. Participio en las discusiones, pero puedes contactarme en @reifman en twitter. Estoy siempre abierto a ideas de caracteristicas nuevas para Meeting Planner como tambien sugerencias para futuras episodios de la serie.
Como recordatorio, todo el codigo para Meeting Planner es previsto como codigo abierto y escrito en la Yii2 Framework para PHP. Si te gustaria aprender mas de Yii2, visita mi serie paralela Programando con Yii2.
Empecemos.
Construyendo "Pedir cambios"
Una montaña alta por escalar
Aparte de la vista de las características de reunión y programación, "Pedir cambios" requiere mas tiempo y nuevo código que cualquier otra característica en este proyecto.
Como mencione en el anterior episodio, programar todo con seguridad básica toma un poco mas tiempo que rápido prototipo, pero diseñar y construir esta característica también toque mucho las otras áreas de la plataforma:
- Diseñar con la ingeniería social de solicitar y hacer cambios de horario.
- Mantener el UX para las solicitudes de cambio simple, ayudando a la gente a solicitar y responder a las solicitudes de cambio sin enturbiar la interfaz.
- Manejar solicitudes para reuniones de 1:1 sería fácil, pero la programacion de la nueva característica de múltiples participantes requeriría una infraestructura mucho más sofisticada.
- Manejo de las respuestas a las solicitudes con varios participantes por delante.
- Notificaciones por correo electrónico de solicitudes nuevas y retiradas, respuestas aceptadas y rechazadas.
- Actualizar la confirmación de la reunión y los detalles del calendario cuando las solicitudes aceptadas afectan a la reunion programada.
Por lo tanto, aunque esto no es una imagen perfecta de los cambios sólo para esta característica, aquí hay capturas de pantalla de la eventual extracción de código de servidor de producción.
Aqui estan los cambios al codigo existente:



Y aqui estan los nuevos archivos:



Hubo mucho codigo nuevo en esta caracteristica.
Las tablas y sus migraciones
En última instancia, me decidí por una arquitectura construida alrededor de dos tablas. El primero fue Pedidos
:
1 |
$this->createTable('{{%request}}', [ |
2 |
'id' => Schema::TYPE_PK, |
3 |
'meeting_id' => Schema::TYPE_INTEGER.' NOT NULL DEFAULT 0', |
4 |
'requestor_id' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0', |
5 |
'completed_by' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0', |
6 |
'time_adjustment' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', |
7 |
'alternate_time' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0', |
8 |
'meeting_time_id' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0', |
9 |
'place_adjustment' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', |
10 |
'meeting_place_id' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0', |
11 |
'note' => Schema::TYPE_TEXT.' NOT NULL DEFAULT ""', |
12 |
'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', |
13 |
'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', |
14 |
'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', |
15 |
], $tableOptions); |
16 |
$this->addForeignKey('fk_request_meeting', '{{%request}}', 'meeting_id', '{{%meeting}}', 'id', 'CASCADE', 'CASCADE'); |
17 |
$this->addForeignKey('fk_request_user', '{{%request}}', 'requestor_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE'); |
Aqui estan las constantes que explican el modelo mas a fondo:
1 |
const STATUS_OPEN = 0; |
2 |
const STATUS_ACCEPTED = 10; |
3 |
const STATUS_REJECTED = 20; |
4 |
const STATUS_WITHDRAWN = 30; |
5 |
|
6 |
const TIME_ADJUST_NONE = 50; |
7 |
const TIME_ADJUST_ABIT = 60; |
8 |
const TIME_ADJUST_OTHER = 70; |
9 |
|
10 |
const PLACE_ADJUST_NONE = 80; |
11 |
const PLACE_ADJUST_OTHER = 90; |
Hay
dos maneras de ajustar el tiempo: TIME_ADJUST_ABIT
, es decir,
intervalos de minutos u horas antes o después de la hora elegida, o
TIME_ADJUST_OTHER
, un tiempo de reunión diferente por completo.
Y la segunda tabla fue RequestResponses
1 |
$this->createTable('{{%request_response}}', [ |
2 |
'id' => Schema::TYPE_PK, |
3 |
'request_id' => Schema::TYPE_INTEGER.' NOT NULL DEFAULT 0', |
4 |
'responder_id' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0', |
5 |
'note' => Schema::TYPE_TEXT.' NOT NULL DEFAULT ""', |
6 |
'response' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', |
7 |
'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', |
8 |
'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', |
9 |
], $tableOptions); |
Básicamente, quién pidió el cambio, quién respondió a él y cuál fue la respuesta: aceptar o rechazar.
La segunda tabla es necesaria para la ocasion de muchos participantes.
Pidiendo Cambios
Los organizadores de la reunión y los participantes pueden acceder a los Pedidos de cambio a través del menú de opciones desplegable que construimos en el último episodio:



El formulario de Pedir Cambio
El actionCreate()
de RequestController.php carga el formulario desde el cual el usuario pide cambios:



Y aquí es donde comenzó la complejidad. ¿Qué tipo de cambios podrían solicitar los participantes?
- ¿Quieres reunirte mas temprano o más tarde?
- ¿Quieres reunirte en un momento totalmente diferente?
- ¿Quieres reunirte en un lugar diferente?
Nota: Todavía no he implementado la posibilidad de agregar nuevos lugares y horas. En este momento, puede elegir fechas y lugares alternativos entre los que se ofrecieron durante el proceso de planificación.
Un desplegable de tiempos anteriores y posteriores
El código para crear la lista desplegable era complicado. Lo hice para que pudiera elegir dos horas y media antes o más tarde, en incrementos de 15 minutos cerca del tiempo original y los incrementos de 30 minutos después de eso:
1 |
for ($i=1;$i<12;$i++) { |
2 |
// later times
|
3 |
if ($i<4 || $i%2 == 0) { |
4 |
$altTimesList[$chosenTime->start+($i*15*60)]=Meeting::friendlyDateFromTimestamp($chosenTime->start+($i*15*60),$timezone,false); |
5 |
}
|
6 |
// earlier times
|
7 |
$earlierIndex = ((12-$i)*-15); |
8 |
if ($i%2 == 0 || $i>=9) { |
9 |
$altTimesList[$chosenTime->start+($earlierIndex*60)]=Meeting::friendlyDateFromTimestamp($chosenTime->start+($earlierIndex*60),$timezone,false); |
10 |
}
|
11 |
}
|
12 |
$altTimesList[$chosenTime->start]='────────────────────'; |
13 |
$altTimesList[-1000]=Yii::t('frontend','Select an alternate time below'); |
14 |
ksort($altTimesList); |
Llené
$altTimesList
con claves de cada tiempo posible con valores del tiempo
amigable ajustado para la zona horaria del usuario. Luego utilicé ksort ()
para ordenar la lista desplegable para que aparecieran tiempos anteriores más tarde.
Uno de los asesores de Meeting Planner (sólo tengo uno por el momento), sugirió que se muestre el tiempo de reunión actualmente seleccionado, el cual hice a continuación. También agregué un separador con la opción deshabilitada en un menú desplegable. Divide tiempos anteriores de tiempos posteriores pero no es seleccionable:



Aquí está el código desplegable, que muestra cómo deshabilitar el separador basado en clave de índice de $currentStart
:
1 |
<?php
|
2 |
echo $form->field($model, 'alternate_time')->label(Yii::t('frontend','Choose a time slightly earlier or later than {currentStartStr}',['currentStartStr'=>$currentStartStr])) |
3 |
->dropDownList( |
4 |
$altTimesList, |
5 |
['options' => [$currentStart => ['disabled' => true]]] |
6 |
);
|
7 |
?>
|
Y, cuando los participantes quieren elegir una de las otras veces, hay JQuery para cambiar los desplegables, otra complejidad a la construcción de los formularios:
1 |
<?php ActiveForm::end(); |
2 |
$this->registerJsFile(MiscHelpers::buildUrl().'/js/request.js',['depends' => [\yii\web\JqueryAsset::className()]]); |
3 |
?>
|
4 |
</div>
|
Aquí está /frontend/web/js/request.js:
1 |
$("#adjust_how" ).change(function() { |
2 |
if ($("#adjust_how" ).val()==50) { |
3 |
$("#choose_earlier").addClass('hidden'); |
4 |
$("#choose_another").addClass('hidden'); |
5 |
} else if ($("#adjust_how" ).val()==60) { |
6 |
$("#choose_earlier").removeClass('hidden'); |
7 |
$("#choose_another").addClass('hidden'); |
8 |
} else { |
9 |
$("#choose_earlier").addClass('hidden'); |
10 |
$("#choose_another").removeClass('hidden'); |
11 |
}
|
12 |
});
|
Asi es como el formulario se ve con las horas alternativas ocultas:



Diferentes lugares se integran en la lista desplegable del lugar (como se puede ver con la imagen arriba).
Manejando el Pedido
Una vez que el pedido es hecho, notificamos que la otra persona participante sera notificada. Y, cuando haya pedidos activos para una reunion, hay un enlace para Mirarlos



Decidí que esto sería un acercamiento simple, sin impedimentos para que la gente tenga acceso a peticiones.
La lista de pedidos de reuniones
Aqui esta la lista de Pedidos para una reunion, casi siempre uno solo:



Request::buildSubject()
crea la string basado en el contenido de la solicitud, el cambio de tiempo y/o lugar:
1 |
public static function buildSubject($request_id,$include_requestor = true) { |
2 |
$r = Request::findOne($request_id); |
3 |
$requestor = MiscHelpers::getDisplayName($r->requestor_id); |
4 |
$timezone = MiscHelpers::fetchUserTimezone(Yii::$app->user->getId()); |
5 |
$rtime =''; |
6 |
$place = ''; |
7 |
switch ($r->time_adjustment) { |
8 |
case Request::TIME_ADJUST_NONE: |
9 |
break; |
10 |
case Request::TIME_ADJUST_ABIT: |
11 |
$rtime = Meeting::friendlyDateFromTimestamp($r->alternate_time,$timezone); |
12 |
break; |
13 |
case Request::TIME_ADJUST_OTHER: |
14 |
$t = MeetingTime::findOne($r->meeting_time_id); |
15 |
if (!is_null($t)) { |
16 |
$rtime = Meeting::friendlyDateFromTimestamp($t->start,$timezone);; |
17 |
}
|
18 |
break; |
19 |
}
|
20 |
if ($r->place_adjustment == Request::PLACE_ADJUST_NONE || $r->place_adjustment == 0 && $r->meeting_place_id ==0 ) { |
21 |
// do nothing
|
22 |
} else { |
23 |
// get place name
|
24 |
$place = MeetingPlace::findOne($r->meeting_place_id)->place->name; |
25 |
}
|
26 |
$result = $requestor.Yii::t('frontend',' asked to meet at '); |
27 |
if ($rtime=='' && $place =='') { |
28 |
$result.=Yii::t('frontend','oops...no changes were requested.'); |
29 |
} else if ($rtime<>'') { |
30 |
$result.=$rtime; |
31 |
if ($place<>'') { |
32 |
$result.=Yii::t('frontend',' and '); |
33 |
}
|
34 |
}
|
35 |
if ($place<>'') { |
36 |
$result.=$place; |
37 |
}
|
38 |
return $result; |
39 |
}
|
Esta función es también usada repetidamente en las notificaciones por correo.
Hay también limites en RequestController.php el cual previenen a usuarios de hacer mas de una petición para reunión a la vez:
1 |
public function actionCreate($meeting_id) |
2 |
{
|
3 |
// verify is attendee
|
4 |
if (!Meeting::isAttendee($meeting_id,Yii::$app->user->getId())) { |
5 |
$this->redirect(['site/authfailure']); |
6 |
}
|
7 |
if (Request::countRequestorOpen($meeting_id,Yii::$app->user->getId())>0) { |
8 |
$r = Request::find() |
9 |
->where(['meeting_id'=>$meeting_id]) |
10 |
->andWhere(['requestor_id'=>Yii::$app->user->getId()]) |
11 |
->andWhere(['status'=>Request::STATUS_OPEN]) |
12 |
->one(); |
13 |
Yii::$app->getSession()->setFlash('info', Yii::t('frontend','You already have an existing request below.')); |
14 |
return $this->redirect(['view','id'=>$r->id]); |
15 |
}
|
16 |
Aquí como se ve la pagina de petición mostrando cuando se limita:



Si es tu propio pedido, puedes Cancelar Tu Pedido.
Como puedes ver, hay mucha diversidad de funcionalidad UX para construir para esto. Y no te he mostrado cuando otras personas que no sean el solicitante responden.
Notificaciones por email de Pedido y Respuesta
En
el proceso de creación de estas características, decidí crear
plantillas de correo electrónico generic_html
y generic_text
, así como
una función Request::notify()
reutilizable para facilitar la entrega
de diferentes tipos de anuncios en torno a Meeting Planner.
Aqui el metodo Request::create()
para preparar un correo electronico:
1 |
public function create() { |
2 |
$user_id = $this->requestor_id; |
3 |
$meeting_id = $this->meeting_id; |
4 |
$subject = Request::buildSubject($this->id); |
5 |
$content=[ |
6 |
'subject' => Yii::t('frontend','Change Requested to Your Meeting'), |
7 |
'heading' => Yii::t('frontend','Requested Change to Your Meeting'), |
8 |
'p1' => $subject, |
9 |
'p2' => $this->note, |
10 |
'plain_text' => $subject.' '.$this->note.'...'.Yii::t('frontend','Respond to the request by visiting this link: '), |
11 |
];
|
12 |
$button= [ |
13 |
'text' => Yii::t('frontend','Respond to Request'), |
14 |
'command' => Meeting::COMMAND_VIEW_REQUEST, |
15 |
'obj_id' => $this->id, |
16 |
];
|
17 |
$this->notify($user_id,$meeting_id, $content,$button); |
18 |
// add to log
|
19 |
MeetingLog::add($meeting_id,MeetingLog::ACTION_REQUEST_SENT,$user_id,0); |
20 |
}
|
La
matriz $content
se rellena para el asunto del correo electrónico, el
encabezado del mensaje y los párrafos, mientras que la matriz $button
se utiliza para cualquier botón de comando como Responder a solicitud o
Ver reunión.
Aqui el metodo notify()
, similar a las acciones anteriores send()
y finalize()
las cuales envian el correo electronico:
1 |
public static function notify($user_id,$meeting_id,$content,$button = false) { |
2 |
// sends a generic message based on arguments
|
3 |
$mtg = Meeting::findOne($meeting_id); |
4 |
// build an attendees array for all participants without contact information
|
5 |
$cnt =0; |
6 |
$attendees = array(); |
7 |
foreach ($mtg->participants as $p) { |
8 |
$auth_key=\common\models\User::find()->where(['id'=>$p->participant_id])->one()->auth_key; |
9 |
$attendees[$cnt]=['user_id'=>$p->participant_id,'auth_key'=>$auth_key, |
10 |
'email'=>$p->participant->email, |
11 |
'username'=>$p->participant->username]; |
12 |
$cnt+=1; |
13 |
}
|
14 |
// add organizer
|
15 |
$auth_key=\common\models\User::find()->where(['id'=>$mtg->owner_id])->one()->auth_key; |
16 |
$attendees[$cnt]=['user_id'=>$mtg->owner_id,'auth_key'=>$auth_key, |
17 |
'email'=>$mtg->owner->email, |
18 |
'username'=>$mtg->owner->username]; |
19 |
// use this code to send
|
20 |
foreach ($attendees as $cnt=>$a) { |
21 |
// check if email is okay and okay from this sender_id
|
22 |
if ($user_id != $a['user_id'] && User::checkEmailDelivery($a['user_id'],$user_id)) { |
23 |
Yii::$app->timeZone = $timezone = MiscHelpers::fetchUserTimezone($a['user_id']); |
24 |
// Build the absolute links to the meeting and commands
|
25 |
$links=[ |
26 |
'home'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_HOME,0,$a['user_id'],$a['auth_key']), |
27 |
'view'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW,0,$a['user_id'],$a['auth_key']), |
28 |
'footer_email'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_EMAIL,0,$a['user_id'],$a['auth_key']), |
29 |
'footer_block'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK,$user_id,$a['user_id'],$a['auth_key']), |
30 |
'footer_block_all'=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK_ALL,0,$a['user_id'],$a['auth_key']), |
31 |
];
|
32 |
if ($button!==false) { |
33 |
$links['button_url']=MiscHelpers::buildCommand($mtg->id,$button['command'],$button['obj_id'],$a['user_id'],$a['auth_key']); |
34 |
$content['button_text']=$button['text']; |
35 |
}
|
36 |
// send the message
|
37 |
$message = Yii::$app->mailer->compose([ |
38 |
'html' => 'generic-html', |
39 |
'text' => 'generic-text' |
40 |
],
|
41 |
[
|
42 |
'meeting_id' => $mtg->id, |
43 |
'sender_id'=> $user_id, |
44 |
'user_id' => $a['user_id'], |
45 |
'auth_key' => $a['auth_key'], |
46 |
'links' => $links, |
47 |
'content'=>$content, |
48 |
'meetingSettings' => $mtg->meetingSettings, |
49 |
]);
|
50 |
// to do - add full name
|
51 |
$message->setFrom(array('support@meetingplanner.com'=>$mtg->owner->email)); |
52 |
$message->setReplyTo('mp_'.$mtg->id.'@meetingplanner.io'); |
53 |
$message->setTo($a['email']) |
54 |
->setSubject($content['subject']) |
55 |
->send(); |
56 |
}
|
57 |
}
|
58 |
}
|
Esencialmente, el diseño de generic_html.php es basado en la simple textual plantilla de actualización que hable en nuestros tutoriales de plantillas de correo electrónico. Proporciona una forma bien formateada de actualizar a los participantes vía correo electrónico con unos pocos párrafos.
Aqui la vista de el archivo generic_html.php intengrando los datos de $contento
y $button
. Comprueba un segundo y tercer párrafo, $p2
, $p3
y los datos de $button
:
1 |
<tr>
|
2 |
<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%"> |
3 |
<p>Hi <?php echo Html::encode(MiscHelpers::getDisplayName($user_id)); ?>,</p> |
4 |
<p><?= Html::encode($content['p1']) ?></p> |
5 |
<?php if ($content['p2']<>'') { |
6 |
?>
|
7 |
<p><?= Html::encode($content['p2']); ?></p> |
8 |
<?php
|
9 |
}
|
10 |
?>
|
11 |
<?php if (isset($content['p3']) && $content['p3']<>'') { |
12 |
?>
|
13 |
<p><?= Html::encode($content['p3']); ?></p> |
14 |
<?php
|
15 |
}
|
16 |
?>
|
17 |
</td>
|
18 |
</tr>
|
19 |
<?php if ($links['button_url']!='') { |
20 |
?>
|
21 |
<tr>
|
22 |
<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"> |
23 |
<div>
|
24 |
<!--[if mso]>
|
25 |
<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;" arcsize="15%" strokecolor="#ffffff" fillcolor="#ff6f6f">
|
26 |
<w:anchorlock/>
|
27 |
<center style="color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;">My Account</center>
|
28 |
</v:roundrect>
|
29 |
<![endif]--><a href="<?php echo $links['button_url'] ?>" style='color:#fff; text-decoration:none; -webkit-text-size-adjust:none; background-color:#ff6f6f; border-radius:5px; display:inline-block; font-family:"Cabin", Helvetica, Arial, sans-serif; font-size:14px; font-weight:regular; line-height:45px; mso-hide:all; text-align:center; width:155px' bgcolor="#ff6f6f" align="center" width="155"><?= Html::encode($content['button_text']); ?></a> |
30 |
</div>
|
31 |
</td>
|
32 |
</tr>
|
33 |
<?php } ?> |
Aquí hay un ejemplo de una notificación que Rob Smith me pregunto para cambiar el tiempo y lugar de nuestra reunión (Generado con el codigo siguiente):



Respondiendo a Pedidos
Cuando hago click en "Responder A Pedido", me llevan al método actionCreate ()
del controlador RequestResponse
:



A través de la solicitud UX, incorporé la capacidad para que personas puedan escribir notas, proporcionando el contexto para las peticiones y respuestas.
Uno de los desafíos de este formulario fue determinar cómo dirigir las respuestas a los métodos de otro controlador en los cuales el boton enviar se hizo clic. En otras palabras, distinguir entre diferentes clics de botón de envío POST.
1 |
public function actionCreate($id) |
2 |
{
|
3 |
$request = Request::findOne($id); |
4 |
if (!Meeting::isAttendee($request->meeting_id,Yii::$app->user->getId())) { |
5 |
$this->redirect(['site/authfailure']); |
6 |
}
|
7 |
// has this user already responded
|
8 |
$check = RequestResponse::find() |
9 |
->where(['request_id'=>$id]) |
10 |
->andWhere(['responder_id'=>Yii::$app->user->getId()]) |
11 |
->count(); |
12 |
if ($check>0) { |
13 |
Yii::$app->getSession()->setFlash('error', Yii::t('frontend','Sorry, you already responded to this request.')); |
14 |
return $this->redirect(['meeting/view', 'id' => $request->meeting_id]); |
15 |
}
|
16 |
if ($request->requestor_id == Yii::$app->user->getId()) { |
17 |
Yii::$app->getSession()->setFlash('error', Yii::t('frontend','Sorry, can not respond to your own request.')); |
18 |
return $this->redirect(['meeting/view', 'id' => $request->meeting_id]); |
19 |
}
|
20 |
$subject = Request::buildSubject($id); |
21 |
$model = new RequestResponse(); |
22 |
$model->request_id = $id; |
23 |
$model->responder_id = Yii::$app->user->getId(); |
24 |
if ($model->load(Yii::$app->request->post()) ) { |
25 |
$posted = Yii::$app->request->post(); |
26 |
if (isset($posted['accept'])) { |
27 |
// accept
|
28 |
$model->response = RequestResponse::RESPONSE_ACCEPT; |
29 |
$model->save(); |
30 |
$request->accept($model); |
31 |
Yii::$app->getSession()->setFlash('success', Yii::t('frontend','Request accepted. We will update the meeting details and inform other participants.')); |
32 |
} else { |
33 |
// reject
|
34 |
$model->response = RequestResponse::RESPONSE_REJECT; |
35 |
$model->save(); |
36 |
$request->reject($model); |
37 |
Yii::$app->getSession()->setFlash('success', Yii::t('frontend','Your decline has been recorded. We will let other participants know.')); |
38 |
}
|
39 |
return $this->redirect(['/meeting/view', 'id' => $request->meeting_id]); |
40 |
} else { |
41 |
return $this->render('create', [ |
42 |
'model' => $model, |
43 |
'subject' => $subject, |
44 |
'meeting_id' => $request->meeting_id, |
45 |
]);
|
46 |
}
|
47 |
}
|
Aqui esta /frontend/views/request-response/_form.php:
1 |
<div class="request-response-form"> |
2 |
<?php $form = ActiveForm::begin(); ?> |
3 |
<?= BaseHtml::activeHiddenInput($model, 'responder_id'); ?> |
4 |
<?= BaseHtml::activeHiddenInput($model, 'request_id'); ?> |
5 |
<?= $form->field($model, 'note')->label(Yii::t('frontend','Include a note'))->textarea(['rows' => 6])->hint(Yii::t('frontend','optional')) ?> |
6 |
|
7 |
<div class="form-group"> |
8 |
<?= Html::submitButton(Yii::t('frontend', 'Accept Request'), |
9 |
['class' => 'btn btn-success','name'=>'accept',]) ?> |
10 |
<?= Html::submitButton(Yii::t('frontend', 'Decline Request'), |
11 |
['class' => 'btn btn-danger','name'=>'reject', |
12 |
'data' => [ |
13 |
'confirm' => Yii::t('frontend', 'Are you sure you want to decline this request?'), |
14 |
'method' => 'post', |
15 |
],]) ?> |
Esencialmente, agregue los valores 'accept'
o 'reject'
de name
para cada boton. Entonces, esto es enviado como un valor publicado, como se mustra:
1 |
if ($model->load(Yii::$app->request->post()) ) { |
2 |
$posted = Yii::$app->request->post(); |
3 |
if (isset($posted['accept'])) { |
4 |
// accept
|
5 |
$model->response = RequestResponse::RESPONSE_ACCEPT; |
6 |
$model->save(); |
7 |
$request->accept($model); |
8 |
Yii::$app->getSession()->setFlash('success', Yii::t('frontend','Request accepted. We will update the meeting details and inform other participants.')); |
9 |
} else { |
10 |
// reject
|
11 |
$model->response = RequestResponse::RESPONSE_REJECT; |
12 |
$model->save(); |
13 |
$request->reject($model); |
14 |
Yii::$app->getSession()->setFlash('success', Yii::t('frontend','Your decline has been recorded. We will let other participants know.')); |
15 |
}
|
16 |
return $this->redirect(['/meeting/view', 'id' => $request->meeting_id]); |
17 |
}
|
Cuando la persona a la cual se ha enviado, acepta o declina el pedido, se les muestra un mensaje y se les envia un correo electronico También, la reunión no tiene mas ningún pedido pendiente para mostrar:



Aqui esta la notificacion por email de Pedir Cambios Aceptado:



Muchas cosas pasan en Request::accept()
mira abajo:
1 |
public function accept($request_response) { |
2 |
// to do - this will need to change when there are multiple participants
|
3 |
$this->status = Request::STATUS_ACCEPTED; |
4 |
$this->update(); |
5 |
$m = Meeting::findOne($this->meeting_id); |
6 |
// is there a new time
|
7 |
switch ($this->time_adjustment) { |
8 |
case Request::TIME_ADJUST_ABIT: |
9 |
// create a new meeting time with alternate_time
|
10 |
$this->meeting_time_id = MeetingTime::addFromRequest($this->id); |
11 |
$this->update(); |
12 |
// mark as selected
|
13 |
MeetingTime::setChoice($this->meeting_id,$this->meeting_time_id,$request_response->responder_id); |
14 |
break; |
15 |
case Request::TIME_ADJUST_OTHER: |
16 |
// mark as selected
|
17 |
MeetingTime::setChoice($this->meeting_id,$this->meeting_time_id,$request_response->responder_id); |
18 |
break; |
19 |
}
|
20 |
// is there a different place
|
21 |
if ($this->place_adjustment == Request::PLACE_ADJUST_OTHER || $this->meeting_place_id !=0 ) { |
22 |
MeetingPlace::setChoice($this->meeting_id,$this->meeting_place_id,$request_response->responder_id); |
23 |
}
|
24 |
if ($m->isOwner($request_response->responder_id)) { |
25 |
// they are an organizer
|
26 |
$this->completed_by =$request_response->responder_id; |
27 |
$this->update(); |
28 |
MeetingLog::add($this->meeting_id,MeetingLog::ACTION_REQUEST_ORGANIZER_ACCEPT,$request_response->responder_id,$this->id); |
29 |
} else { |
30 |
// they are a participant
|
31 |
MeetingLog::add($this->meeting_id,MeetingLog::ACTION_REQUEST_ACCEPT,$request_response->responder_id,$this->id); |
32 |
}
|
33 |
$user_id = $request_response->responder_id; |
34 |
$subject = Request::buildSubject($this->id, true); |
35 |
$p1 = MiscHelpers::getDisplayName($user_id).Yii::t('frontend',' accepted the request: ').$subject; |
36 |
$p2 = $request_response->note; |
37 |
$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.'); |
38 |
$content=[ |
39 |
'subject' => Yii::t('frontend','Accepted Requested Change to Meeting'), |
40 |
'heading' => Yii::t('frontend','Requested Change Accepted'), |
41 |
'p1' => $p1, |
42 |
'p2' => $p2, |
43 |
'p3' => $p3, |
44 |
'plain_text' => $p1.' '.$p2.' '.$p3.'...'.Yii::t('frontend','View the meeting here: '), |
45 |
];
|
46 |
$button= [ |
47 |
'text' => Yii::t('frontend','View the Meeting'), |
48 |
'command' => Meeting::COMMAND_VIEW, |
49 |
'obj_id' => 0, |
50 |
];
|
51 |
$this->notify($user_id,$this->meeting_id, $content,$button); |
52 |
// Make changes to the Meeting
|
53 |
$m->increaseSequence(); |
54 |
// resend the finalization - which also needs to be done for resend invitation
|
55 |
$m->finalize($m->owner_id); |
56 |
}
|
Antes de enviar el correo electrónico, el horario de la reunión se actualiza para reflejar cualquier nueva fecha / hora y / o lugar nuevo. Después de enviar el correo electrónico, la reunión se finaliza. Esto entrega una nueva actualización de reunión con el calendario actualizado a todos los participantes:



¿Que sigue?
Espero que hayas disfrutado de este tutorial. Construir esta característica tardó más de lo que esperaba, pero resultó bastante bien. Creo que añade una dimensión a la programación con Meeting Planner incomparable por otros servicios.
Si aún no lo ha hecho, vaya a programar su primera reunión con Meeting Planner. He seguido haciendo progresos increíbles hacia la versión beta, a pesar de las distracciones (la programacion es difícil):
También estoy planeando escribir un tutorial sobre crowdfunding, así que considere seguir nuestra página de WeFunder Meeting Planner.
Por favor comparte tus comentarios abajo. Siempre estoy abierto a nuevas ideas de características y sugerencias de temas para futuros tutoriales. También puede contactar conmigo @reifman.
Manténgase en sintonía para todo esto y más en tutoriales próximos, echa un vistazo a la serie construya su startup con PHP. Y definitivamente echa un vistazo a nuestra Programación con la serie Yii2 (Envato Tuts +).