Browse Source

[Administration] Support : finalisation système de tickets

feature/souke
Guillaume Bourgeois 1 year ago
parent
commit
97b79ee79e
19 changed files with 575 additions and 115 deletions
  1. +87
    -0
      backend/controllers/SupportAdminController.php
  2. +11
    -6
      backend/controllers/SupportController.php
  3. +32
    -4
      backend/views/layouts/left.php
  4. +12
    -8
      backend/views/support/create.php
  5. +149
    -66
      backend/views/support/index.php
  6. +15
    -6
      backend/views/support/view.php
  7. +22
    -0
      backend/web/css/screen.css
  8. +25
    -0
      backend/web/sass/support/_index.scss
  9. +7
    -0
      common/components/View.php
  10. +1
    -0
      common/config/main.php
  11. +25
    -7
      common/logic/AbstractRepository.php
  12. +8
    -1
      common/logic/Ticket/Ticket/Model/Ticket.php
  13. +12
    -8
      common/logic/Ticket/Ticket/Model/TicketSearch.php
  14. +20
    -3
      common/logic/Ticket/Ticket/Repository/TicketRepository.php
  15. +32
    -0
      common/logic/Ticket/Ticket/Service/TicketBuilder.php
  16. +64
    -2
      common/logic/Ticket/Ticket/Service/TicketSolver.php
  17. +12
    -1
      common/logic/Ticket/TicketUser/Repository/TicketUserRepository.php
  18. +14
    -0
      common/logic/Ticket/TicketUser/Repository/TicketUserRepositoryQuery.php
  19. +27
    -3
      common/logic/Ticket/TicketUser/Service/TicketUserBuilder.php

+ 87
- 0
backend/controllers/SupportAdminController.php View File

<?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
]);
}

}

+ 11
- 6
backend/controllers/SupportController.php View File



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
]); ]);

+ 32
- 4
backend/views/layouts/left.php View File

*/ */


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">&nbsp;</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',

+ 12
- 8
backend/views/support/create.php View File

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>

+ 149
- 66
backend/views/support/index.php View File

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);
}

?>

+ 15
- 6
backend/views/support/view.php View File

$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>



+ 22
- 0
backend/web/css/screen.css View File

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;

+ 25
- 0
backend/web/sass/support/_index.scss View File



.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;
}
}
}
} }

+ 7
- 0
common/components/View.php View File



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;
}
} }

+ 1
- 0
common/config/main.php View File

} }
], ],
'language' => 'fr-FR', 'language' => 'fr-FR',
'timeZone' => 'Europe/Paris',
]; ];

+ 25
- 7
common/logic/AbstractRepository.php View File

{ {
$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;
} }
} }

+ 8
- 1
common/logic/Ticket/Ticket/Model/Ticket.php View File

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

+ 12
- 8
common/logic/Ticket/Ticket/Model/TicketSearch.php View File

]; ];
} }


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;
} }

+ 20
- 3
common/logic/Ticket/Ticket/Repository/TicketRepository.php View File

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);
}
} }

+ 32
- 0
common/logic/Ticket/Ticket/Service/TicketBuilder.php View File

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);
}
} }

+ 64
- 2
common/logic/Ticket/Ticket/Service/TicketSolver.php View File



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;
} }
} }

+ 12
- 1
common/logic/Ticket/TicketUser/Repository/TicketUserRepository.php View File

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();
}
} }

+ 14
- 0
common/logic/Ticket/TicketUser/Repository/TicketUserRepositoryQuery.php View File

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;
}
} }

+ 27
- 3
common/logic/Ticket/TicketUser/Service/TicketUserBuilder.php View File

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);
}
} }

Loading…
Cancel
Save