<?php | |||||
/** | |||||
* Copyright distrib (2018) | |||||
* | |||||
* contact@opendistrib.net | |||||
* | |||||
* Ce logiciel est un programme informatique servant à aider les producteurs | |||||
* à distribuer leur production en circuits courts. | |||||
* | |||||
* Ce logiciel est régi par la licence CeCILL soumise au droit français et | |||||
* respectant les principes de diffusion des logiciels libres. Vous pouvez | |||||
* utiliser, modifier et/ou redistribuer ce programme sous les conditions | |||||
* de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA | |||||
* sur le site "http://www.cecill.info". | |||||
* | |||||
* En contrepartie de l'accessibilité au code source et des droits de copie, | |||||
* de modification et de redistribution accordés par cette licence, il n'est | |||||
* offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons, | |||||
* seule une responsabilité restreinte pèse sur l'auteur du programme, le | |||||
* titulaire des droits patrimoniaux et les concédants successifs. | |||||
* | |||||
* A cet égard l'attention de l'utilisateur est attirée sur les risques | |||||
* associés au chargement, à l'utilisation, à la modification et/ou au | |||||
* développement et à la reproduction du logiciel par l'utilisateur étant | |||||
* donné sa spécificité de logiciel libre, qui peut le rendre complexe à | |||||
* manipuler et qui le réserve donc à des développeurs et des professionnels | |||||
* avertis possédant des connaissances informatiques approfondies. Les | |||||
* utilisateurs sont donc invités à charger et tester l'adéquation du | |||||
* logiciel à leurs besoins dans des conditions permettant d'assurer la | |||||
* sécurité de leurs systèmes et ou de leurs données et, plus généralement, | |||||
* à l'utiliser et l'exploiter dans les mêmes conditions de sécurité. | |||||
* | |||||
* Le fait que vous puissiez accéder à cet en-tête signifie que vous avez | |||||
* pris connaissance de la licence CeCILL, et que vous en avez accepté les | |||||
* termes. | |||||
*/ | |||||
namespace backend\controllers; | |||||
use common\logic\Ticket\Ticket\Model\Ticket; | |||||
use common\logic\Ticket\Ticket\Model\TicketSearch; | |||||
use yii\filters\VerbFilter; | |||||
use yii\filters\AccessControl; | |||||
/** | |||||
* UserController implements the CRUD actions for User model. | |||||
*/ | |||||
class SupportAdminController extends SupportController | |||||
{ | |||||
public function behaviors() | |||||
{ | |||||
return [ | |||||
'verbs' => [ | |||||
'class' => VerbFilter::class, | |||||
'actions' => [], | |||||
], | |||||
'access' => [ | |||||
'class' => AccessControl::class, | |||||
'rules' => [ | |||||
[ | |||||
'allow' => true, | |||||
'roles' => ['@'], | |||||
'matchCallback' => function ($rule, $action) { | |||||
return $this->getUserManager()->isCurrentAdmin(); | |||||
} | |||||
] | |||||
], | |||||
], | |||||
]; | |||||
} | |||||
public function actionIndex() | |||||
{ | |||||
$searchTicket = new TicketSearch(); | |||||
$dataProviderTicketOpen = $searchTicket->search('admin', ['TicketSearch' => ['status' => Ticket::STATUS_OPEN]]); | |||||
$dataProviderTicketClosed = $searchTicket->search('admin', ['TicketSearch' => ['status' => Ticket::STATUS_CLOSED]]); | |||||
return $this->render('@backend/views/support/index', [ | |||||
'context' => 'admin', | |||||
'searchTicket' => $searchTicket, | |||||
'dataProviderTicketOpen' => $dataProviderTicketOpen, | |||||
'dataProviderTicketClosed' => $dataProviderTicketClosed | |||||
]); | |||||
} | |||||
} |
namespace backend\controllers; | namespace backend\controllers; | ||||
use common\logic\Ticket\Ticket\Model\Ticket; | |||||
use common\logic\Ticket\Ticket\Model\TicketSearch; | use common\logic\Ticket\Ticket\Model\TicketSearch; | ||||
use yii\filters\AccessControl; | use yii\filters\AccessControl; | ||||
use yii\filters\VerbFilter; | use yii\filters\VerbFilter; | ||||
public function actionIndex() | public function actionIndex() | ||||
{ | { | ||||
$searchTicket = new TicketSearch(); | $searchTicket = new TicketSearch(); | ||||
$dataProviderTicket = $searchTicket->search(\Yii::$app->request->queryParams); | |||||
$dataProviderTicketOpen = $searchTicket->search('producer', ['TicketSearch' => ['status' => Ticket::STATUS_OPEN]]); | |||||
$dataProviderTicketClosed = $searchTicket->search('producer', ['TicketSearch' => ['status' => Ticket::STATUS_CLOSED]]); | |||||
return $this->render('index', [ | return $this->render('index', [ | ||||
'context' => 'producer', | |||||
'searchTicket' => $searchTicket, | 'searchTicket' => $searchTicket, | ||||
'dataProviderTicket' => $dataProviderTicket | |||||
'dataProviderTicketOpen' => $dataProviderTicketOpen, | |||||
'dataProviderTicketClosed' => $dataProviderTicketClosed | |||||
]); | ]); | ||||
} | } | ||||
$this->setFlash('success', 'Le ticket a bien été créé.'); | $this->setFlash('success', 'Le ticket a bien été créé.'); | ||||
return $this->redirect(['view', 'id' => $ticket->id]); | return $this->redirect(['view', 'id' => $ticket->id]); | ||||
} else { | } else { | ||||
return $this->render('create', [ | |||||
return $this->render('@backend/views/support/create', [ | |||||
'ticket' => $ticket, | 'ticket' => $ticket, | ||||
]); | ]); | ||||
} | } | ||||
public function actionView(int $id) | public function actionView(int $id) | ||||
{ | { | ||||
$ticketManager = $this->getTicketManager(); | |||||
$ticketMessageManager = $this->getTicketMessageManager(); | $ticketMessageManager = $this->getTicketMessageManager(); | ||||
$ticket = $this->findTicket($id); | $ticket = $this->findTicket($id); | ||||
$ticketManager->viewTicket($ticket, $this->getUserCurrent()); | |||||
$ticketMessage = $ticketMessageManager->instanciateTicketMessage($ticket, $this->getUserCurrent()); | $ticketMessage = $ticketMessageManager->instanciateTicketMessage($ticket, $this->getUserCurrent()); | ||||
if ($ticketMessage->load(\Yii::$app->request->post()) && $ticketMessageManager->saveCreate($ticketMessage)) { | |||||
if ($ticketMessage->load(\Yii::$app->request->post()) && $ticketManager->createTicketMessage($ticketMessage)) { | |||||
return $this->redirect(['view', 'id' => $ticket->id, '#' => 'bottom']); | return $this->redirect(['view', 'id' => $ticket->id, '#' => 'bottom']); | ||||
} | } | ||||
return $this->render('view', [ | |||||
return $this->render('@backend/views/support/view', [ | |||||
'ticket' => $ticket, | 'ticket' => $ticket, | ||||
'ticketMessageResponse' => $ticketMessage | 'ticketMessageResponse' => $ticketMessage | ||||
]); | ]); |
*/ | */ | ||||
use common\helpers\GlobalParam; | use common\helpers\GlobalParam; | ||||
use common\logic\Ticket\Ticket\Wrapper\TicketManager; | |||||
use yii\helpers\Html; | use yii\helpers\Html; | ||||
$producerManager = $this->getProducerManager(); | $producerManager = $this->getProducerManager(); | ||||
$userManager = $this->getUserManager(); | $userManager = $this->getUserManager(); | ||||
$ticketManager = $this->getTicketManager(); | |||||
?> | ?> | ||||
<?php | <?php | ||||
$producer = GlobalParam::getCurrentProducer(); | $producer = GlobalParam::getCurrentProducer(); | ||||
$newVersionOpendistribTemplate = ''; | |||||
$newVersionOpendistribLabel = ''; | |||||
if ($producer && !$producerManager->isUpToDateWithOpendistribVersion($producer)) { | if ($producer && !$producerManager->isUpToDateWithOpendistribVersion($producer)) { | ||||
$newVersionOpendistribTemplate = '<span class="pull-right-container"><small class="label pull-right bg-orange"> </small></span>'; | |||||
$newVersionOpendistribLabel = '<span class="pull-right-container"><small class="label pull-right bg-green">'.GlobalParam::getOpendistribVersion().'</small></span>'; | |||||
} | } | ||||
$countTicketsProducerUnreadLabel = ''; | |||||
$countTicketsProducerUnread = $ticketManager->countTicketsUnreadByUser($this->getUserCurrent()); | |||||
if($countTicketsProducerUnread && !$userManager->isCurrentAdmin()) { | |||||
$countTicketsProducerUnreadLabel = '<span class="pull-right-container"><small class="label pull-right bg-green">'.$countTicketsProducerUnread.'</small></span>'; | |||||
} | |||||
$countTicketsAdminUnreadLabel = ''; | |||||
$countTicketsAdminUnread = $ticketManager->countTicketsAdminUnreadByUser($this->getUserCurrent()); | |||||
if($countTicketsAdminUnread && $userManager->isCurrentAdmin()) { | |||||
$countTicketsAdminUnreadLabel = '<span class="pull-right-container"><small class="label pull-right bg-green">'.$countTicketsAdminUnread.'</small></span>'; | |||||
} | |||||
?> | ?> | ||||
<?= dmstr\widgets\Menu::widget( | <?= dmstr\widgets\Menu::widget( | ||||
'options' => ['class' => 'sidebar-menu tree', 'data-widget' => 'tree'], | 'options' => ['class' => 'sidebar-menu tree', 'data-widget' => 'tree'], | ||||
'items' => [ | 'items' => [ | ||||
['label' => "Besoin d'aide ?", 'options' => ['class' => 'header'], 'visible' => $userManager->isCurrentProducer()], | ['label' => "Besoin d'aide ?", 'options' => ['class' => 'header'], 'visible' => $userManager->isCurrentProducer()], | ||||
['label' => 'Support', 'icon' => 'comments', 'url' => ['support/index'], 'visible' => $userManager->isCurrentProducer()], | |||||
[ | |||||
'label' => 'Support', | |||||
'icon' => 'comments', | |||||
'url' => ['support/index'], | |||||
'visible' => $userManager->isCurrentProducer(), | |||||
'template' => '<a href="{url}">{icon} {label}' . $countTicketsProducerUnreadLabel . '</a>' | |||||
], | |||||
['label' => $producer->name, 'options' => ['class' => 'header'], 'visible' => $userManager->isCurrentProducer()], | ['label' => $producer->name, 'options' => ['class' => 'header'], 'visible' => $userManager->isCurrentProducer()], | ||||
['label' => 'Tableau de bord', 'icon' => 'dashboard', 'url' => ['/site/index'], 'visible' => $userManager->isCurrentProducer()], | ['label' => 'Tableau de bord', 'icon' => 'dashboard', 'url' => ['/site/index'], 'visible' => $userManager->isCurrentProducer()], | ||||
['label' => 'Distributions', 'icon' => 'calendar', 'url' => ['/distribution/index'], 'visible' => $userManager->isCurrentProducer()], | ['label' => 'Distributions', 'icon' => 'calendar', 'url' => ['/distribution/index'], 'visible' => $userManager->isCurrentProducer()], | ||||
'url' => ['/development/index'], | 'url' => ['/development/index'], | ||||
'visible' => $userManager->isCurrentProducer(), | 'visible' => $userManager->isCurrentProducer(), | ||||
'active' => Yii::$app->controller->id == 'development', | 'active' => Yii::$app->controller->id == 'development', | ||||
'template' => '<a href="{url}">{icon} {label}' . $newVersionOpendistribTemplate . '</a>' | |||||
'template' => '<a href="{url}">{icon} {label}' . $newVersionOpendistribLabel . '</a>' | |||||
], | ], | ||||
['label' => 'Tarifs', 'icon' => 'euro', 'url' => ['/producer/billing'], 'visible' => $userManager->isCurrentProducer()], | ['label' => 'Tarifs', 'icon' => 'euro', 'url' => ['/producer/billing'], 'visible' => $userManager->isCurrentProducer()], | ||||
['label' => 'Administration', 'options' => ['class' => 'header'], 'visible' => $userManager->isCurrentAdmin()], | ['label' => 'Administration', 'options' => ['class' => 'header'], 'visible' => $userManager->isCurrentAdmin()], | ||||
[ | |||||
'label' => 'Tickets', | |||||
'icon' => 'comments', | |||||
'url' => ['support-admin/index'], | |||||
'visible' => $userManager->isCurrentAdmin(), | |||||
'template' => '<a href="{url}">{icon} {label}' . $countTicketsAdminUnreadLabel . '</a>' | |||||
], | |||||
['label' => 'Producteurs', 'icon' => 'th-list', 'url' => ['/producer-admin/index'], 'visible' => $userManager->isCurrentAdmin()], | ['label' => 'Producteurs', 'icon' => 'th-list', 'url' => ['/producer-admin/index'], 'visible' => $userManager->isCurrentAdmin()], | ||||
[ | [ | ||||
'label' => 'Statistiques', | 'label' => 'Statistiques', |
use yii\widgets\ActiveForm; | use yii\widgets\ActiveForm; | ||||
$this->setTitle('Créer un ticket') ; | $this->setTitle('Créer un ticket') ; | ||||
$this->addBreadcrumb(['label' => 'ticket', 'url' => ['index']]) ; | |||||
$this->addBreadcrumb('Créer') ; | |||||
$this->addBreadcrumb(['label' => 'Support', 'url' => ['index']]) ; | |||||
$this->addBreadcrumb('Créer un ticket') ; | |||||
?> | ?> | ||||
<div class="ticket-create"> | <div class="ticket-create"> | ||||
<?php $form = ActiveForm::begin(); ?> | |||||
<?= $form->field($ticket, 'subject'); ?> | |||||
<?= $form->field($ticket, 'message')->textarea(['rows' => 6]); ?> | |||||
<div class="form-group"> | |||||
<?= Html::submitButton('Créer', ['class' => 'btn btn-success']) ?> | |||||
<div class="box"> | |||||
<?php $form = ActiveForm::begin(); ?> | |||||
<div class="box-body"> | |||||
<?= $form->field($ticket, 'subject'); ?> | |||||
<?= $form->field($ticket, 'message')->textarea(['rows' => 6]); ?> | |||||
</div> | |||||
<div class="box-footer"> | |||||
<?= Html::submitButton('Créer', ['class' => 'btn btn-success']) ?> | |||||
</div> | |||||
<?php ActiveForm::end(); ?> | |||||
</div> | </div> | ||||
<?php ActiveForm::end(); ?> | |||||
</div> | </div> |
use common\logic\Ticket\Ticket\Wrapper\TicketManager; | use common\logic\Ticket\Ticket\Wrapper\TicketManager; | ||||
use yii\helpers\Html; | use yii\helpers\Html; | ||||
use yii\grid\GridView; | use yii\grid\GridView; | ||||
use common\logic\Subscription\Subscription\Model\Subscription; | |||||
$ticketManager = TicketManager::getInstance(); | $ticketManager = TicketManager::getInstance(); | ||||
$userCurrent = $this->getUserCurrent(); | |||||
$this->setTitle('Support'); | $this->setTitle('Support'); | ||||
$this->addBreadcrumb($this->getTitle()); | $this->addBreadcrumb($this->getTitle()); | ||||
//$this->addButton(['label' => 'Créer un ticket <span class="glyphicon glyphicon-plus"></span>', 'url' => 'support/create', 'class' => 'btn btn-primary']) ; | |||||
?> | ?> | ||||
<div class="support-index"> | <div class="support-index"> | ||||
<?php if($context == 'producer'): ?> | |||||
<div> | <div> | ||||
<div class="col-md-4"> | <div class="col-md-4"> | ||||
<div class="info-box"> | <div class="info-box"> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="clr"></div> | <div class="clr"></div> | ||||
<?php endif; ?> | |||||
<div class="box box-table-tickets"> | |||||
<div class="box-header with-border"> | |||||
<h3 class="box-title">Tickets</h3> | |||||
</div> | |||||
<div class="box-body"> | |||||
<?= GridView::widget([ | |||||
'summary' => '', | |||||
'filterModel' => $searchTicket, | |||||
'dataProvider' => $dataProviderTicket, | |||||
'columns' => [ | |||||
[ | |||||
'attribute' => 'created_at', | |||||
'value' => function ($ticket) { | |||||
return date('d/m/Y', strtotime($ticket->created_at)); | |||||
} | |||||
], | |||||
[ | |||||
'attribute' => 'subject', | |||||
'format' => 'raw', | |||||
'value' => function ($ticket) { | |||||
return Html::a($ticket->subject, ['support/view', 'id' => $ticket->id]); | |||||
} | |||||
], | |||||
[ | |||||
'label' => 'Messages', | |||||
'value' => function ($ticket) { | |||||
return count($ticket->ticketMessages); | |||||
} | |||||
], | |||||
[ | |||||
'attribute' => 'status', | |||||
'filter' => [Ticket::STATUS_OPEN => 'Ouvert', Ticket::STATUS_CLOSED => 'Fermé'], | |||||
'value' => function ($ticket) { | |||||
$label = $ticket->status == Ticket::STATUS_OPEN ? 'Ouvert' : 'Fermé'; | |||||
return $label; | |||||
} | |||||
], | |||||
[ | |||||
'class' => 'yii\grid\ActionColumn', | |||||
'template' => '{close-open}', | |||||
'headerOptions' => ['class' => 'column-actions'], | |||||
'contentOptions' => ['class' => 'column-actions'], | |||||
'buttons' => [ | |||||
'close-open' => function ($url, $ticket) use ($ticketManager) { | |||||
if ($ticketManager->isTicketOpen($ticket)) { | |||||
$title = 'Fermer'; | |||||
$url = ['support/close', 'id' => $ticket->id]; | |||||
$glyphicon = 'glyphicon-folder-close'; | |||||
} else { | |||||
$title = 'Ré-ouvrir'; | |||||
$url = ['support/open', 'id' => $ticket->id]; | |||||
$glyphicon = 'glyphicon-folder-open'; | |||||
} | |||||
return Html::a('<span class="glyphicon ' . $glyphicon . '"></span>', $url, [ | |||||
'title' => $title, 'class' => 'btn btn-default' | |||||
]); | |||||
} | |||||
], | |||||
], | |||||
], | |||||
]); ?> | |||||
<div class="nav-tabs-custom ticket-list"> | |||||
<ul class="nav nav-tabs pull-right"> | |||||
<li><a href="#tab_2-2" data-toggle="tab">Fermés <span | |||||
class="label label-default"><?= $dataProviderTicketClosed->totalCount ?></span></a></li> | |||||
<li class="active"><a href="#tab_1-1" data-toggle="tab">Ouverts <span | |||||
class="label label-default"><?= $dataProviderTicketOpen->totalCount ?></span></a></li> | |||||
<li class="pull-left header"><i class="fa fa-comments"></i> Tickets</li> | |||||
</ul> | |||||
<div class="tab-content"> | |||||
<div class="tab-pane active" id="tab_1-1"> | |||||
<?= ticketList($context, $searchTicket, $dataProviderTicketOpen, $userCurrent); ?> | |||||
</div> | |||||
<div class="tab-pane" id="tab_2-2"> | |||||
<?= ticketList($context, $searchTicket, $dataProviderTicketClosed, $userCurrent); ?> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<?php | |||||
function ticketList($context, $searchTicket, $dataProviderTicket, $userCurrent) | |||||
{ | |||||
$ticketManager = TicketManager::getInstance(); | |||||
$columnCreatedAt = [ | |||||
'attribute' => 'created_at', | |||||
'headerOptions' => ['class' => 'td-created-at column-hide-on-mobile'], | |||||
'value' => function ($ticket) { | |||||
return date('d/m/Y', strtotime($ticket->created_at)); | |||||
} | |||||
]; | |||||
$columnSubject = [ | |||||
'attribute' => 'subject', | |||||
'headerOptions' => ['class' => 'td-subject'], | |||||
'format' => 'raw', | |||||
'value' => function ($ticket) use ($ticketManager, $userCurrent) { | |||||
if($ticketManager->isTicketUnread($ticket, $userCurrent)) { | |||||
$firstTicketMessageUnread = $ticketManager->getFirstTicketMessageUnread($ticket, $userCurrent); | |||||
$link = '<strong>'.Html::a($ticket->subject, ['view', 'id' => $ticket->id, '#' => $firstTicketMessageUnread->id]).'</strong>'; | |||||
} | |||||
else { | |||||
$link = Html::a($ticket->subject, ['view', 'id' => $ticket->id]); | |||||
} | |||||
return $link; | |||||
} | |||||
]; | |||||
$columnLastMessage = [ | |||||
'label' => 'Dernier message', | |||||
'headerOptions' => ['class' => 'td-last-message column-hide-on-mobile'], | |||||
'value' => function ($ticket) { | |||||
$ticketMessages = $ticket->ticketMessages; | |||||
if ($ticketMessages && is_array($ticketMessages)) { | |||||
$lastTicketMessage = end($ticketMessages); | |||||
$origin = date_create(date('Y-m-d', strtotime($lastTicketMessage->created_at))); | |||||
$target = date_create(); | |||||
$interval = date_diff($origin, $target); | |||||
$days = (int)$interval->format('%R%a'); | |||||
if ($days == 0) { | |||||
return "Aujourd'hui"; | |||||
} elseif ($days == 1) { | |||||
return "Hier"; | |||||
} else { | |||||
return $days . ' jours'; | |||||
} | |||||
} | |||||
return ''; | |||||
} | |||||
]; | |||||
$columnMessages = [ | |||||
'label' => 'Messages', | |||||
'headerOptions' => ['class' => 'td-messages column-hide-on-mobile'], | |||||
'value' => function ($ticket) { | |||||
return count($ticket->ticketMessages); | |||||
} | |||||
]; | |||||
$columnButtonActions = [ | |||||
'class' => 'yii\grid\ActionColumn', | |||||
'template' => '{view} {close-open}', | |||||
'headerOptions' => ['class' => 'column-actions'], | |||||
'contentOptions' => ['class' => 'column-actions'], | |||||
'buttons' => [ | |||||
'view' => function ($url, $ticket) { | |||||
$url = ['view', 'id' => $ticket->id]; | |||||
return Html::a('<span class="glyphicon glyphicon-eye-open"></span>', $url, [ | |||||
'title' => 'Voir le ticket', 'class' => 'btn btn-default' | |||||
]); | |||||
}, | |||||
'close-open' => function ($url, $ticket) use ($ticketManager) { | |||||
if ($ticketManager->isTicketOpen($ticket)) { | |||||
$title = 'Fermer'; | |||||
$url = ['close', 'id' => $ticket->id]; | |||||
$glyphicon = 'glyphicon-folder-close'; | |||||
} else { | |||||
$title = 'Ré-ouvrir'; | |||||
$url = ['open', 'id' => $ticket->id]; | |||||
$glyphicon = 'glyphicon-folder-open'; | |||||
} | |||||
return Html::a('<span class="glyphicon ' . $glyphicon . '"></span>', $url, [ | |||||
'title' => $title, 'class' => 'btn btn-default' | |||||
]); | |||||
} | |||||
], | |||||
]; | |||||
$columnProducer = [ | |||||
'attribute' => 'id_producer', | |||||
'headerOptions' => ['class' => 'td-producer'], | |||||
'value' => function ($ticket) { | |||||
return $ticket->producer->name; | |||||
} | |||||
]; | |||||
if($context == 'producer') { | |||||
$columns = [ | |||||
$columnCreatedAt, | |||||
$columnSubject, | |||||
$columnLastMessage, | |||||
$columnMessages, | |||||
$columnButtonActions | |||||
]; | |||||
} | |||||
elseif($context == 'admin') { | |||||
$columns = [ | |||||
$columnCreatedAt, | |||||
$columnProducer, | |||||
$columnSubject, | |||||
$columnLastMessage, | |||||
$columnMessages, | |||||
$columnButtonActions | |||||
]; | |||||
} | |||||
$optionsGridView = [ | |||||
'summary' => '', | |||||
'filterModel' => $searchTicket, | |||||
'dataProvider' => $dataProviderTicket, | |||||
'columns' => $columns, | |||||
]; | |||||
return GridView::widget($optionsGridView); | |||||
} | |||||
?> |
$ticketManager = TicketManager::getInstance(); | $ticketManager = TicketManager::getInstance(); | ||||
$userManager = UserManager::getInstance(); | $userManager = UserManager::getInstance(); | ||||
$this->setTitle('Voir un ticket'); | $this->setTitle('Voir un ticket'); | ||||
$this->addBreadcrumb(['label' => 'Support', 'url' => ['support/index']]); | |||||
$this->addBreadcrumb(['label' => 'Support', 'url' => ['index']]); | |||||
$this->addBreadcrumb('Voir un ticket'); | $this->addBreadcrumb('Voir un ticket'); | ||||
?> | ?> | ||||
<i class="fa fa-user <?= $userManager->isAdmin($ticketMessage->user) ? 'bg-orange' : 'bg-aqua'; ?>"></i> | <i class="fa fa-user <?= $userManager->isAdmin($ticketMessage->user) ? 'bg-orange' : 'bg-aqua'; ?>"></i> | ||||
<div class="timeline-item"> | <div class="timeline-item"> | ||||
<span class="time"><i class="fa fa-clock-o"></i> <?= date('d/m/Y à H:i', strtotime($ticketMessage->created_at)) ?></span> | <span class="time"><i class="fa fa-clock-o"></i> <?= date('d/m/Y à H:i', strtotime($ticketMessage->created_at)) ?></span> | ||||
<h3 class="timeline-header">Guillaume Bourgeois</h3> | |||||
<h3 class="timeline-header"><?= Html::encode($userManager->getUsername($ticketMessage->user)); ?></h3> | |||||
<div class="timeline-body"> | <div class="timeline-body"> | ||||
<?= nl2br($ticketMessage->message); ?> | <?= nl2br($ticketMessage->message); ?> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="box box-danger"> | |||||
<div class="box <?= $ticketManager->isTicketOpen($ticket) ? 'box-danger' : 'box-success'; ?>"> | |||||
<div class="box-header"> | <div class="box-header"> | ||||
<h3 class="box-title"><i class="fa fa-folder"></i> Cliquez ici si vous souhaitez fermer le ticket</h3> | |||||
<h3 class="box-title"> | |||||
<?php if($ticketManager->isTicketOpen($ticket)): ?> | |||||
<i class="fa fa-folder"></i> Cliquez ici si vous souhaitez fermer le ticket | |||||
<?php else: ?> | |||||
<i class="fa fa-folder-open"></i> Cliquez ici si vous souhaitez rouvrir le ticket | |||||
<?php endif; ?> | |||||
</h3> | |||||
</div> | </div> | ||||
<div class="box-body"> | <div class="box-body"> | ||||
<?= Html::a('Fermer le ticket', ['support/close', 'id' => $ticket->id], ['class' => 'btn btn-danger btn-sm']) ?> | |||||
<?php if($ticketManager->isTicketOpen($ticket)): ?> | |||||
<?= Html::a('Fermer le ticket', ['close', 'id' => $ticket->id], ['class' => 'btn btn-danger btn-sm']) ?> | |||||
<?php else: ?> | |||||
<?= Html::a('Rouvrir le ticket', ['open', 'id' => $ticket->id], ['class' => 'btn btn-success btn-sm']) ?> | |||||
<?php endif; ?> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
padding-left: 25px; | padding-left: 25px; | ||||
} | } | ||||
/* line 6, ../sass/support/_index.scss */ | |||||
.support-index .ticket-list .nav-tabs .label { | |||||
position: relative; | |||||
top: -2px; | |||||
left: 2px; | |||||
padding: 0.3em 0.6em 0.2em 0.6em; | |||||
} | |||||
/* line 15, ../sass/support/_index.scss */ | |||||
.support-index .ticket-list .table .filters { | |||||
display: none; | |||||
} | |||||
/* line 19, ../sass/support/_index.scss */ | |||||
.support-index .ticket-list .table .td-created-at, | |||||
.support-index .ticket-list .table .td-last-message, | |||||
.support-index .ticket-list .table .td-messages { | |||||
width: 100px; | |||||
} | |||||
/* line 24, ../sass/support/_index.scss */ | |||||
.support-index .ticket-list .table .td-producer { | |||||
width: 200px; | |||||
} | |||||
/* line 5, ../sass/support/_view.scss */ | /* line 5, ../sass/support/_view.scss */ | ||||
.ticket-view .table tr:first-child td { | .ticket-view .table tr:first-child td { | ||||
border-top: 0px none; | border-top: 0px none; |
.support-index { | .support-index { | ||||
.ticket-list { | |||||
.nav-tabs { | |||||
.label { | |||||
position: relative; | |||||
top: -2px; | |||||
left: 2px; | |||||
padding: 0.3em 0.6em 0.2em 0.6em; | |||||
} | |||||
} | |||||
.table { | |||||
.filters { | |||||
display: none; | |||||
} | |||||
.td-created-at, | |||||
.td-last-message, | |||||
.td-messages { | |||||
width: 100px; | |||||
} | |||||
.td-producer { | |||||
width: 200px; | |||||
} | |||||
} | |||||
} | |||||
} | } |
namespace common\components ; | namespace common\components ; | ||||
use common\logic\User\User\Model\User; | |||||
class View extends \yii\web\View | class View extends \yii\web\View | ||||
{ | { | ||||
use BusinessLogicTrait; | use BusinessLogicTrait; | ||||
{ | { | ||||
return \Yii::$app->urlManagerBackend; | return \Yii::$app->urlManagerBackend; | ||||
} | } | ||||
public function getUserCurrent(): ?User | |||||
{ | |||||
return \Yii::$app->user->identity; | |||||
} | |||||
} | } |
} | } | ||||
], | ], | ||||
'language' => 'fr-FR', | 'language' => 'fr-FR', | ||||
'timeZone' => 'Europe/Paris', | |||||
]; | ]; |
{ | { | ||||
$this->query->createQuery(); | $this->query->createQuery(); | ||||
$this->defaultWith(); | |||||
$this->defaultJoinWith(); | |||||
return $this->query; | return $this->query; | ||||
} | } | ||||
{ | { | ||||
$this->createQuery(); | $this->createQuery(); | ||||
$defaultOptions = $this->getDefaultOptionsSearch(); | |||||
$this->defaultWith(); | |||||
$this->defaultJoinWith(); | |||||
$this->defaultFilterProducerContext(); | |||||
$this->defaultOrderBy(); | |||||
return $this->query; | |||||
} | |||||
// with | |||||
public function defaultWith(): void | |||||
{ | |||||
$defaultOptions = $this->getDefaultOptionsSearch(); | |||||
if (is_array($defaultOptions['with']) && count($defaultOptions['with'])) { | if (is_array($defaultOptions['with']) && count($defaultOptions['with'])) { | ||||
$this->query->with($defaultOptions['with']); | $this->query->with($defaultOptions['with']); | ||||
} | } | ||||
} | |||||
// join with | |||||
public function defaultJoinWith(): void | |||||
{ | |||||
$defaultOptions = $this->getDefaultOptionsSearch(); | |||||
if (is_array($defaultOptions['join_with']) && count($defaultOptions['join_with'])) { | if (is_array($defaultOptions['join_with']) && count($defaultOptions['join_with'])) { | ||||
$this->query->joinWith($defaultOptions['join_with']); | $this->query->joinWith($defaultOptions['join_with']); | ||||
} | } | ||||
} | |||||
// id producer contexte | |||||
public function defaultFilterProducerContext(): void | |||||
{ | |||||
$defaultOptions = $this->getDefaultOptionsSearch(); | |||||
if(isset($defaultOptions['attribute_id_producer']) && $defaultOptions['attribute_id_producer']) { | if(isset($defaultOptions['attribute_id_producer']) && $defaultOptions['attribute_id_producer']) { | ||||
$this->query->andWhere([$defaultOptions['attribute_id_producer'] => $this->getProducerContextId()]); | $this->query->andWhere([$defaultOptions['attribute_id_producer'] => $this->getProducerContextId()]); | ||||
} | } | ||||
} | |||||
// order by | |||||
public function defaultOrderBy(): void | |||||
{ | |||||
$defaultOptions = $this->getDefaultOptionsSearch(); | |||||
if(isset($defaultOptions['orderby']) && $defaultOptions['orderby']) { | if(isset($defaultOptions['orderby']) && $defaultOptions['orderby']) { | ||||
$this->query->orderBy($defaultOptions['orderby']); | $this->query->orderBy($defaultOptions['orderby']); | ||||
} | } | ||||
return $this->query; | |||||
} | } | ||||
} | } |
use common\components\ActiveRecordCommon; | use common\components\ActiveRecordCommon; | ||||
use common\logic\Producer\Producer\Model\Producer; | use common\logic\Producer\Producer\Model\Producer; | ||||
use common\logic\Ticket\TicketMessage\Model\TicketMessage; | use common\logic\Ticket\TicketMessage\Model\TicketMessage; | ||||
use common\logic\Ticket\TicketUser\Model\TicketUser; | |||||
use common\logic\User\User\Model\User; | use common\logic\User\User\Model\User; | ||||
class Ticket extends ActiveRecordCommon | class Ticket extends ActiveRecordCommon | ||||
public function getTicketMessages() | public function getTicketMessages() | ||||
{ | { | ||||
return $this->hasMany(TicketMessage::class, ['id_ticket' => 'id']); | |||||
return $this->hasMany(TicketMessage::class, ['id_ticket' => 'id']) | |||||
->orderBy(['created_at' => SORT_ASC]); | |||||
} | |||||
public function getTicketUsers() | |||||
{ | |||||
return $this->hasMany(TicketUser::class, ['id_ticket' => 'id']); | |||||
} | } | ||||
public function populateProducer(Producer $producer): void | public function populateProducer(Producer $producer): void |
]; | ]; | ||||
} | } | ||||
public function search($params) | |||||
public function search($context, $params) | |||||
{ | { | ||||
$ticketRepository = TicketRepository::getInstance(); | $ticketRepository = TicketRepository::getInstance(); | ||||
$optionsSearch = $ticketRepository->getDefaultOptionsSearch(); | $optionsSearch = $ticketRepository->getDefaultOptionsSearch(); | ||||
$query = Ticket::find() | $query = Ticket::find() | ||||
->with($optionsSearch['with']) | ->with($optionsSearch['with']) | ||||
->innerJoinWith($optionsSearch['join_with'], true) | |||||
->where(['ticket.id_producer' => GlobalParam::getCurrentProducerId()]); | |||||
->innerJoinWith($optionsSearch['join_with'], true); | |||||
if($context == 'producer') { | |||||
$query->where(['ticket.id_producer' => GlobalParam::getCurrentProducerId()]); | |||||
} | |||||
$dataProvider = new ActiveDataProvider([ | $dataProvider = new ActiveDataProvider([ | ||||
'query' => $query, | 'query' => $query, | ||||
'sort' => ['attributes' => ['updated_at']], | |||||
'sort' => [ | |||||
'attributes' => ['updated_at'], | |||||
'defaultOrder' => [ | |||||
'updated_at' => SORT_DESC, | |||||
] | |||||
], | |||||
'pagination' => [ | 'pagination' => [ | ||||
'pageSize' => 20, | 'pageSize' => 20, | ||||
], | ], | ||||
$this->load($params); | $this->load($params); | ||||
if(!$this->status) { | |||||
$this->status = Ticket::STATUS_OPEN; | |||||
} | |||||
if (!$this->validate()) { | if (!$this->validate()) { | ||||
return $dataProvider; | return $dataProvider; | ||||
} | } |
namespace common\logic\Ticket\Ticket\Repository; | namespace common\logic\Ticket\Ticket\Repository; | ||||
use common\logic\AbstractRepository; | use common\logic\AbstractRepository; | ||||
use common\logic\Ticket\Ticket\Model\Ticket; | |||||
use common\logic\Ticket\Ticket\Service\TicketSolver; | |||||
use common\logic\User\User\Model\User; | |||||
class TicketRepository extends AbstractRepository | class TicketRepository extends AbstractRepository | ||||
{ | { | ||||
protected TicketRepositoryQuery $query; | protected TicketRepositoryQuery $query; | ||||
protected TicketSolver $ticketSolver; | |||||
public function loadDependencies(): void | public function loadDependencies(): void | ||||
{ | { | ||||
$this->loadQuery(TicketRepositoryQuery::class); | $this->loadQuery(TicketRepositoryQuery::class); | ||||
$this->ticketSolver = $this->loadService(TicketSolver::class); | |||||
} | } | ||||
/** | /** | ||||
public function getDefaultOptionsSearch(): array | public function getDefaultOptionsSearch(): array | ||||
{ | { | ||||
return [ | return [ | ||||
self::WITH => ['user', 'producer', 'ticketMessages'], | |||||
self::WITH => ['user', 'producer', 'ticketMessages', 'ticketUsers'], | |||||
self::JOIN_WITH => [], | self::JOIN_WITH => [], | ||||
self::ORDER_BY => '', | self::ORDER_BY => '', | ||||
self::ATTRIBUTE_ID_PRODUCER => '' | |||||
self::ATTRIBUTE_ID_PRODUCER => 'ticket.id_producer' | |||||
]; | ]; | ||||
} | } | ||||
public function findOneTicketById(int $id) | |||||
public function findOneTicketById(int $id): ?Ticket | |||||
{ | { | ||||
return $this->createQuery() | return $this->createQuery() | ||||
->filterById($id) | ->filterById($id) | ||||
->findOne(); | ->findOne(); | ||||
} | } | ||||
public function countTicketsUnreadByUser(User $user): int | |||||
{ | |||||
$ticketsArray = $this->createDefaultQuery()->find(); | |||||
return $this->ticketSolver->countTicketsUnreadByUserFromArray($ticketsArray, $user); | |||||
} | |||||
public function countTicketsAdminUnreadByUser(User $user): int | |||||
{ | |||||
$ticketsArray = $this->createQuery()->find(); | |||||
return $this->ticketSolver->countTicketsUnreadByUserFromArray($ticketsArray, $user); | |||||
} | |||||
} | } |
use common\logic\AbstractBuilder; | use common\logic\AbstractBuilder; | ||||
use common\logic\Producer\Producer\Model\Producer; | use common\logic\Producer\Producer\Model\Producer; | ||||
use common\logic\Ticket\Ticket\Model\Ticket; | use common\logic\Ticket\Ticket\Model\Ticket; | ||||
use common\logic\Ticket\TicketMessage\Model\TicketMessage; | |||||
use common\logic\Ticket\TicketMessage\Service\TicketMessageBuilder; | |||||
use common\logic\Ticket\TicketUser\Service\TicketUserBuilder; | |||||
use common\logic\User\User\Model\User; | use common\logic\User\User\Model\User; | ||||
class TicketBuilder extends AbstractBuilder | class TicketBuilder extends AbstractBuilder | ||||
{ | { | ||||
protected TicketMessageBuilder $ticketMessageBuilder; | |||||
protected TicketUserBuilder $ticketUserBuilder; | |||||
public function loadDependencies(): void | |||||
{ | |||||
$this->ticketMessageBuilder = $this->loadService(TicketMessageBuilder::class); | |||||
$this->ticketUserBuilder = $this->loadService(TicketUserBuilder::class); | |||||
} | |||||
public function instanciateTicket(Producer $producer, User $user): Ticket | public function instanciateTicket(Producer $producer, User $user): Ticket | ||||
{ | { | ||||
$ticket = new Ticket(); | $ticket = new Ticket(); | ||||
{ | { | ||||
$ticket->status = $status; | $ticket->status = $status; | ||||
$this->saveUpdate($ticket); | $this->saveUpdate($ticket); | ||||
$this->updateTicketUpdatedAt($ticket); | |||||
} | } | ||||
public function closeTicket(Ticket $ticket) | public function closeTicket(Ticket $ticket) | ||||
{ | { | ||||
$this->updateTicketStatus($ticket, Ticket::STATUS_OPEN); | $this->updateTicketStatus($ticket, Ticket::STATUS_OPEN); | ||||
} | } | ||||
public function updateTicketUpdatedAt(Ticket $ticket) | |||||
{ | |||||
$ticket->updated_at = date('Y-m-d H:i:s'); | |||||
$this->saveUpdate($ticket); | |||||
} | |||||
public function createTicketMessage(TicketMessage $ticketMessage) | |||||
{ | |||||
$this->updateTicketUpdatedAt($ticketMessage->ticket); | |||||
return $this->saveCreate($ticketMessage); | |||||
} | |||||
public function viewTicket(Ticket $ticket, User $user) | |||||
{ | |||||
$ticketUser = $this->ticketUserBuilder->createTicketUserIfNotExist($ticket, $user); | |||||
$this->ticketUserBuilder->updateTicketUserReadAt($ticketUser); | |||||
} | |||||
} | } |
use common\logic\AbstractSolver; | use common\logic\AbstractSolver; | ||||
use common\logic\Ticket\Ticket\Model\Ticket; | use common\logic\Ticket\Ticket\Model\Ticket; | ||||
use common\logic\Ticket\TicketMessage\Model\TicketMessage; | |||||
use common\logic\Ticket\TicketUser\Model\TicketUser; | |||||
use common\logic\User\User\Model\User; | use common\logic\User\User\Model\User; | ||||
use common\logic\User\User\Service\UserSolver; | use common\logic\User\User\Service\UserSolver; | ||||
{ | { | ||||
$classLabel = 'label-success'; | $classLabel = 'label-success'; | ||||
$statusLabel = 'Ouvert'; | $statusLabel = 'Ouvert'; | ||||
if($this->isTicketClosed($ticket)) { | |||||
if ($this->isTicketClosed($ticket)) { | |||||
$classLabel = 'label-danger'; | $classLabel = 'label-danger'; | ||||
$statusLabel = 'Fermé'; | $statusLabel = 'Fermé'; | ||||
} | } | ||||
return '<span class="label '.$classLabel.'">'.$statusLabel.'</span>'; | |||||
return '<span class="label ' . $classLabel . '">' . $statusLabel . '</span>'; | |||||
} | |||||
public function getTicketUser(Ticket $ticket, User $user) | |||||
{ | |||||
foreach($ticket->ticketUsers as $ticketUser) { | |||||
if($ticketUser->id_user == $user->id) { | |||||
return $ticketUser; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
public function isTicketMessageUnread(TicketMessage $ticketMessage, TicketUser $ticketUser) | |||||
{ | |||||
if($ticketMessage->created_at > $ticketUser->read_at) { | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
public function isTicketUnread(Ticket $ticket, User $user): int | |||||
{ | |||||
$ticketUser = $this->getTicketUser($ticket, $user); | |||||
if($ticketUser) { | |||||
foreach($ticket->ticketMessages as $ticketMessage) { | |||||
if($this->isTicketMessageUnread($ticketMessage, $ticketUser)) { | |||||
return true; | |||||
} | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
public function getFirstTicketMessageUnread(Ticket $ticket, User $user): ?TicketMessage | |||||
{ | |||||
$ticketUser = $this->getTicketUser($ticket, $user); | |||||
if($ticketUser) { | |||||
foreach($ticket->ticketMessages as $ticketMessage) { | |||||
if($this->isTicketMessageUnread($ticketMessage, $ticketUser)) { | |||||
return $ticketMessage; | |||||
} | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
public function countTicketsUnreadByUserFromArray(array $ticketsArray, User $user): int | |||||
{ | |||||
$count = 0; | |||||
foreach($ticketsArray as $ticket) { | |||||
if($this->isTicketUnread($ticket, $user)) { | |||||
$count++; | |||||
} | |||||
} | |||||
return $count; | |||||
} | } | ||||
} | } |
namespace common\logic\Ticket\TicketUser\Repository; | namespace common\logic\Ticket\TicketUser\Repository; | ||||
use common\logic\AbstractRepository; | use common\logic\AbstractRepository; | ||||
use common\logic\Ticket\Ticket\Model\Ticket; | |||||
use common\logic\Ticket\TicketUser\Model\TicketUser; | |||||
use common\logic\User\User\Model\User; | |||||
class TicketUserRepository extends AbstractRepository | class TicketUserRepository extends AbstractRepository | ||||
{ | { | ||||
public function getDefaultOptionsSearch(): array | public function getDefaultOptionsSearch(): array | ||||
{ | { | ||||
return [ | return [ | ||||
self::WITH => ['user', 'producer', 'ticketMessages'], | |||||
self::WITH => ['ticket', 'user'], | |||||
self::JOIN_WITH => [], | self::JOIN_WITH => [], | ||||
self::ORDER_BY => '', | self::ORDER_BY => '', | ||||
self::ATTRIBUTE_ID_PRODUCER => '' | self::ATTRIBUTE_ID_PRODUCER => '' | ||||
]; | ]; | ||||
} | } | ||||
public function findOneTicketUser(Ticket $ticket, User $user): ?TicketUser | |||||
{ | |||||
return $this->createDefaultQuery() | |||||
->filterByTicket($ticket) | |||||
->filterByUser($user) | |||||
->findOne(); | |||||
} | |||||
} | } |
namespace common\logic\Ticket\TicketUser\Repository; | namespace common\logic\Ticket\TicketUser\Repository; | ||||
use common\logic\AbstractRepositoryQuery; | use common\logic\AbstractRepositoryQuery; | ||||
use common\logic\Ticket\Ticket\Model\Ticket; | |||||
use common\logic\Ticket\TicketUser\Service\TicketUserDefinition; | use common\logic\Ticket\TicketUser\Service\TicketUserDefinition; | ||||
use common\logic\User\User\Model\User; | |||||
class TicketUserRepositoryQuery extends AbstractRepositoryQuery | class TicketUserRepositoryQuery extends AbstractRepositoryQuery | ||||
{ | { | ||||
{ | { | ||||
$this->loadDefinition(TicketUserDefinition::class); | $this->loadDefinition(TicketUserDefinition::class); | ||||
} | } | ||||
public function filterByTicket(Ticket $ticket): self | |||||
{ | |||||
$this->andWhere(['ticket_user.id_ticket' => $ticket->id]); | |||||
return $this; | |||||
} | |||||
public function filterByUser(User $user): self | |||||
{ | |||||
$this->andWhere(['ticket_user.id_user' => $user->id]); | |||||
return $this; | |||||
} | |||||
} | } |
use common\logic\AbstractBuilder; | use common\logic\AbstractBuilder; | ||||
use common\logic\Ticket\Ticket\Model\Ticket; | use common\logic\Ticket\Ticket\Model\Ticket; | ||||
use common\logic\Ticket\TicketUser\Model\TicketUser; | use common\logic\Ticket\TicketUser\Model\TicketUser; | ||||
use common\logic\Ticket\TicketUser\Repository\TicketUserRepository; | |||||
use common\logic\User\User\Model\User; | |||||
class TicketUserBuilder extends AbstractBuilder | class TicketUserBuilder extends AbstractBuilder | ||||
{ | { | ||||
public function instanciateTicketUser(): TicketUser | |||||
protected TicketUserRepository $ticketUserRepository; | |||||
public function loadDependencies(): void | |||||
{ | |||||
$this->ticketUserRepository = $this->loadService(TicketUserRepository::class); | |||||
} | |||||
public function instanciateTicketUser(Ticket $ticket, User $user): TicketUser | |||||
{ | { | ||||
$ticketUser = new TicketUser(); | $ticketUser = new TicketUser(); | ||||
$ticketUser->populateTicket($ticket); | |||||
$ticketUser->populateUser($user); | |||||
return $ticketUser; | return $ticketUser; | ||||
} | } | ||||
public function createTicketUser(): Ticket | |||||
public function createTicketUser(Ticket $ticket, User $user): TicketUser | |||||
{ | { | ||||
$ticketUser = $this->instanciateTicketUser(); | |||||
$ticketUser = $this->instanciateTicketUser($ticket, $user); | |||||
$this->saveCreate($ticketUser); | $this->saveCreate($ticketUser); | ||||
return $ticketUser; | return $ticketUser; | ||||
} | } | ||||
public function createTicketUserIfNotExist(Ticket $ticket, User $user): TicketUser | |||||
{ | |||||
return $this->ticketUserRepository->findOneTicketUser($ticket, $user) | |||||
?? $this->createTicketUser($ticket, $user); | |||||
} | |||||
public function updateTicketUserReadAt(TicketUser $ticketUser) | |||||
{ | |||||
$ticketUser->read_at = date('Y-m-d H:i:s'); | |||||
$this->saveUpdate($ticketUser); | |||||
} | |||||
} | } |