diff --git a/client/src/actions/admin-data-actions.js b/client/src/actions/admin-data-actions.js index 94c9cde5..8926b1f4 100644 --- a/client/src/actions/admin-data-actions.js +++ b/client/src/actions/admin-data-actions.js @@ -12,12 +12,12 @@ export default { }; }, - retrieveMyTickets() { + retrieveMyTickets(closed = 0) { return { type: 'MY_TICKETS', payload: API.call({ path: '/staff/get-tickets', - data: {} + data: {closed} }) }; }, @@ -32,12 +32,12 @@ export default { }; }, - retrieveAllTickets(page = 1) { + retrieveAllTickets(page = 1, query = '', closed = 0) { return { type: 'ALL_TICKETS', payload: API.call({ path: '/staff/get-all-tickets', - data: {page} + data: {page, query, closed} }) }; }, diff --git a/client/src/app-components/ticket-list.js b/client/src/app-components/ticket-list.js index c3d95be4..86ee0403 100644 --- a/client/src/app-components/ticket-list.js +++ b/client/src/app-components/ticket-list.js @@ -9,6 +9,7 @@ import Table from 'core-components/table'; import Button from 'core-components/button'; import Tooltip from 'core-components/tooltip'; import DropDown from 'core-components/drop-down'; +import Checkbox from 'core-components/checkbox'; class TicketList extends React.Component { static propTypes = { @@ -21,7 +22,9 @@ class TicketList extends React.Component { type: React.PropTypes.oneOf([ 'primary', 'secondary' - ]) + ]), + closedTicketsShown: React.PropTypes.bool, + onClosedTicketsShownChange: React.PropTypes.func }; static defaultProps = { @@ -30,7 +33,8 @@ class TicketList extends React.Component { tickets: [], departments: [], ticketPath: '/dashboard/ticket/', - type: 'primary' + type: 'primary', + closedTicketsShown: false }; state = { @@ -40,12 +44,20 @@ class TicketList extends React.Component { render() { return (
- {(this.props.type === 'secondary' && this.props.showDepartmentDropdown) ? this.renderDepartmentsDropDown() : null} +
+ {(this.props.type === 'secondary' && this.props.showDepartmentDropdown) ? this.renderDepartmentsDropDown() : null} + {this.props.onClosedTicketsShownChange ? this.renderFilterCheckbox() : null} +
); } + + renderFilterCheckbox() { + return + } + renderDepartmentsDropDown() { return (
diff --git a/client/src/app-components/ticket-list.scss b/client/src/app-components/ticket-list.scss index e0cb9ceb..33783043 100644 --- a/client/src/app-components/ticket-list.scss +++ b/client/src/app-components/ticket-list.scss @@ -2,8 +2,19 @@ .ticket-list { - &__department-selector { + &__filters { margin-bottom: 25px; + text-align: left; + } + + &__department-selector { + display: inline-block; + margin-right: 25px; + text-align: center; + } + + &__checkbox { + display: inline-block; } &__number { @@ -52,4 +63,4 @@ &__priority-high { background-color: $primary-red; } -} \ No newline at end of file +} diff --git a/client/src/app-components/ticket-viewer.js b/client/src/app-components/ticket-viewer.js index 7450ed1a..79cf0048 100644 --- a/client/src/app-components/ticket-viewer.js +++ b/client/src/app-components/ticket-viewer.js @@ -34,6 +34,7 @@ class TicketViewer extends React.Component { userId: React.PropTypes.number, userStaff: React.PropTypes.bool, userDepartments: React.PropTypes.array, + userLevel: React.PropTypes.number }; static defaultProps = { @@ -77,7 +78,7 @@ class TicketViewer extends React.Component {
{ticket.events && ticket.events.map(this.renderTicketEvent.bind(this))}
- {(!this.props.ticket.closed && (this.props.editable || !this.props.assignmentAllowed)) ? this.renderResponseField() : null} + {(!this.props.ticket.closed && (this.props.editable || !this.props.assignmentAllowed)) ? this.renderResponseField() : (this.showDeleteButton())? : null}
); } @@ -226,7 +227,10 @@ class TicketViewer extends React.Component { {(this.props.allowAttachments) ? : null}
{i18n('RESPOND_TICKET')} - +
+ + {(this.showDeleteButton())? : null} +
{(this.state.commentError) ? this.renderCommentError() : null} @@ -339,6 +343,10 @@ class TicketViewer extends React.Component { event.preventDefault(); AreYouSure.openModal(null, this.closeTicket.bind(this)); } + onDeleteTicketClick(event) { + event.preventDefault(); + AreYouSure.openModal(null, this.deleteTicket.bind(this)); + } reopenTicket() { API.call({ @@ -357,6 +365,14 @@ class TicketViewer extends React.Component { } }).then(this.onTicketModification.bind(this)); } + deleteTicket() { + API.call({ + path: '/ticket/delete', + data: { + ticketNumber: this.props.ticket.ticketNumber + } + }).then(this.onTicketModification.bind(this)); + } changeDepartment(index) { API.call({ @@ -459,6 +475,18 @@ class TicketViewer extends React.Component { return staffAssignmentItems; } + + showDeleteButton() { + if(!this.props.ticket.owner) { + if(this.props.userLevel == 3) return true; + if(this.props.userId == this.props.ticket.author.id) { + if((this.props.userStaff && this.props.ticket.author.staff) || (!this.props.userStaff && !this.props.ticket.author.staff)){ + return true; + } + } + } + return false; + } } export default connect((store) => { @@ -469,6 +497,7 @@ export default connect((store) => { staffMembers: store.adminData.staffMembers, staffMembersLoaded: store.adminData.staffMembersLoaded, allowAttachments: store.config['allow-attachments'], - userSystemEnabled: store.config['user-system-enabled'] + userSystemEnabled: store.config['user-system-enabled'], + userLevel: store.session.userLevel }; })(TicketViewer); diff --git a/client/src/app-components/ticket-viewer.scss b/client/src/app-components/ticket-viewer.scss index a1b3ac36..17de6a8c 100644 --- a/client/src/app-components/ticket-viewer.scss +++ b/client/src/app-components/ticket-viewer.scss @@ -97,4 +97,7 @@ } } + &__delete-button { + margin-left: 10px; + } } diff --git a/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js index ec6be25d..bbc7ef70 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js +++ b/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js @@ -20,11 +20,12 @@ class AdminPanelAllTickets extends React.Component { state = { page: 1, - query: '' + query: '', + closedTicketsShown: false }; componentDidMount() { - this.props.dispatch(AdminDataAction.retrieveAllTickets()); + this.updateTicketList(); } render() { @@ -41,6 +42,14 @@ class AdminPanelAllTickets extends React.Component { ); } + updateTicketList() { + this.props.dispatch(AdminDataAction.retrieveAllTickets( + this.state.page, + this.state.query, + this.state.closedTicketsShown*1 + )); + } + getTicketListProps() { return { userId: this.props.userId, @@ -52,28 +61,32 @@ class AdminPanelAllTickets extends React.Component { ticketPath: '/admin/panel/tickets/view-ticket/', onPageChange: this.onPageChange.bind(this), page: this.state.page, - pages: this.props.pages + pages: this.props.pages, + closedTicketsShown: this.state.closedTicketsShown, + onClosedTicketsShownChange: this.onClosedTicketsShownChange.bind(this) }; } - onSearch(query) { - this.setState({query, page: 1}); + onClosedTicketsShownChange() { + this.setState(function(state) { + return { + closedTicketsShown: !state.closedTicketsShown + }; + }, () => { + this.updateTicketList(); + }); + } - if(query) { - this.props.dispatch(AdminDataAction.searchTickets(query)); - } else { - this.props.dispatch(AdminDataAction.retrieveAllTickets()); - } + onSearch(query) { + this.setState({query, page: 1}, () => { + this.updateTicketList(); + }); } onPageChange(event) { - this.setState({page: event.target.value}); - - if(this.state.query) { - this.props.dispatch(AdminDataAction.searchTickets(this.state.query, event.target.value)); - } else { - this.props.dispatch(AdminDataAction.retrieveAllTickets(event.target.value)); - } + this.setState({page: event.target.value}, () => { + this.updateTicketList(); + }); } } diff --git a/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js index 2b85418b..a5f827ac 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js +++ b/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js @@ -21,6 +21,10 @@ class AdminPanelMyTickets extends React.Component { tickets: [] }; + state = { + closedTicketsShown: false + }; + componentDidMount() { this.props.dispatch(AdminDataAction.retrieveMyTickets()); } @@ -46,10 +50,22 @@ class AdminPanelMyTickets extends React.Component { tickets: this.props.tickets, type: 'secondary', loading: this.props.loading, - ticketPath: '/admin/panel/tickets/view-ticket/' + ticketPath: '/admin/panel/tickets/view-ticket/', + closedTicketsShown: this.state.closedTicketsShown, + onClosedTicketsShownChange: this.onClosedTicketsShownChange.bind(this) }; } + onClosedTicketsShownChange() { + this.setState(function(state) { + return { + closedTicketsShown: !state.closedTicketsShown + }; + }, () => { + this.props.dispatch(AdminDataAction.retrieveMyTickets(this.state.closedTicketsShown * 1)); + }); + } + onCreateTicket() { ModalContainer.openModal(
diff --git a/client/src/data/languages/br.js b/client/src/data/languages/br.js index d5679841..f3a89d5b 100644 --- a/client/src/data/languages/br.js +++ b/client/src/data/languages/br.js @@ -30,6 +30,7 @@ export default { 'RESPOND': 'Responder', 'RESPOND_TICKET': 'Responder chamado', 'CLOSE_TICKET': 'Fechar ticket', + 'DELETE_TICKET': 'Apagar ticket', 'NO_ATTACHMENT': 'Nenhum anexo', 'STAFF': 'Equipe', 'CUSTOMER': 'Cliente', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'privado', 'ENABLE_USER': 'Ativar usuário', 'DISABLE_USER': 'Desativar usuário', + 'SHOW_CLOSED_TICKETS': 'Mostrar ingressos fechados', 'IMAGE_HEADER_URL': 'URL do cabeçalho da imagem', 'CHART_CREATE_TICKET': 'Chamados criados', diff --git a/client/src/data/languages/cn.js b/client/src/data/languages/cn.js index b2158680..0b62cb42 100644 --- a/client/src/data/languages/cn.js +++ b/client/src/data/languages/cn.js @@ -30,6 +30,7 @@ export default { 'RESPOND': '響應', 'RESPOND_TICKET': '響應故障單', 'CLOSE_TICKET': '關門票', + 'DELETE_TICKET': '删除票证', 'NO_ATTACHMENT': '沒有文件附件', 'STAFF': '員工', 'CUSTOMER': '顧客', @@ -188,6 +189,7 @@ export default { 'PRIVATE': '私人的', 'ENABLE_USER': '启用用户', 'DISABLE_USER': '禁用用户', + 'SHOW_CLOSED_TICKETS': '显示已关闭的门票', 'IMAGE_HEADER_URL': '图片标题网址', 'CHART_CREATE_TICKET': '已創建門票', diff --git a/client/src/data/languages/de.js b/client/src/data/languages/de.js index b291d9af..e7ec08a6 100644 --- a/client/src/data/languages/de.js +++ b/client/src/data/languages/de.js @@ -30,6 +30,7 @@ export default { 'RESPOND': 'Antworten', 'RESPOND_TICKET': 'Ticket beantworten', 'CLOSE_TICKET': 'Ticket schließen', + 'DELETE_TICKET': 'Ticket löschen', 'NO_ATTACHMENT': 'Keine Dateianlage', 'STAFF': 'Personal', 'CUSTOMER': 'Kunde', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'Privatgelände', 'ENABLE_USER': 'Benutzer aktivieren', 'DISABLE_USER': 'Benutzer deaktivieren', + 'SHOW_CLOSED_TICKETS': 'Geschlossene Tickets anzeigen', 'IMAGE_HEADER_URL': 'URL des Image-Headers', 'CHART_CREATE_TICKET': 'Tickets erstellt', diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 39ca0115..e5d90c44 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -30,6 +30,7 @@ export default { 'RESPOND': 'Respond', 'RESPOND_TICKET': 'Respond Ticket', 'CLOSE_TICKET': 'Close ticket', + 'DELETE_TICKET': 'Delete ticket', 'NO_ATTACHMENT': 'No file attachment', 'STAFF': 'Staff', 'CUSTOMER': 'Customer', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'Private', 'ENABLE_USER': 'Enable User', 'DISABLE_USER': 'Disable User', + 'SHOW_CLOSED_TICKETS': 'Show Closed Tickets', 'IMAGE_HEADER_URL': 'Image header URL', 'CHART_CREATE_TICKET': 'Tickets created', diff --git a/client/src/data/languages/es.js b/client/src/data/languages/es.js index aaf71692..d86b7ee5 100644 --- a/client/src/data/languages/es.js +++ b/client/src/data/languages/es.js @@ -21,15 +21,16 @@ export default { 'VIEW_ARTICLES': 'Ver Artículos', 'EDIT_PROFILE': 'Editar Perfil', 'CLOSE_SESSION': 'Cerrar sesión', - 'CREATE_TICKET': 'Crear Ticket', - 'TICKET_LIST': 'Lista de Tickets', + 'CREATE_TICKET': 'Crear ticket', + 'TICKET_LIST': 'Lista de tickets', 'SUPPORT_CENTER': 'Centro de Soporte', 'DEPARTMENT': 'Departamento', 'AUTHOR': 'Autor', 'DATE': 'Fecha', 'RESPOND': 'Responder', - 'RESPOND_TICKET': 'Responder Ticket', - 'CLOSE_TICKET': 'Cerrar Ticket', + 'RESPOND_TICKET': 'Responder ticket', + 'CLOSE_TICKET': 'Cerrar ticket', + 'DELETE_TICKET': 'Borrar ticket', 'NO_ATTACHMENT': 'No hay archivo adjunto', 'STAFF': 'Staff', 'CUSTOMER': 'Cliente', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'privado', 'ENABLE_USER': 'Habilitar usuario', 'DISABLE_USER': 'Deshabilitar usuario', + 'SHOW_CLOSED_TICKETS': 'Mostrar Tickets Cerrados', 'IMAGE_HEADER_URL': 'URL del encabezado de la imagen', 'CHART_CREATE_TICKET': 'Tickets creados', diff --git a/client/src/data/languages/fr.js b/client/src/data/languages/fr.js index 480c5979..eb71b3ef 100644 --- a/client/src/data/languages/fr.js +++ b/client/src/data/languages/fr.js @@ -30,6 +30,7 @@ export default { 'RESPOND': 'Répondre', 'RESPOND_TICKET': 'Répondre au ticket', 'CLOSE_TICKET': 'Fermer ticket', + 'DELETE_TICKET': 'Supprimer le ticket', 'NO_ATTACHMENT': 'Aucune pièce jointe', 'STAFF': 'Administrateur', 'CUSTOMER': 'Client', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'privé', 'ENABLE_USER': 'Activer l\'utilisateur', 'DISABLE_USER': 'Désactiver l\'utilisateur', + 'SHOW_CLOSED_TICKETS': 'Afficher les billets fermés', 'IMAGE_HEADER_URL': 'URL de l\'en-tête de l\'image', 'CHART_CREATE_TICKET': 'Tickets créés', diff --git a/client/src/data/languages/gr.js b/client/src/data/languages/gr.js index e5b97abb..0cf8a74c 100644 --- a/client/src/data/languages/gr.js +++ b/client/src/data/languages/gr.js @@ -30,6 +30,7 @@ 'RESPOND': 'Απάντηση', 'RESPOND_TICKET': 'Απάντηση Εισιτηρίου', 'CLOSE_TICKET': 'κλειστό εισιτήριο', + 'DELETE_TICKET': 'Διαγραφή εισιτηρίου', 'NO_ATTACHMENT': 'Χωρίς Συνημμένα Αρχεία', 'STAFF': 'Προσωπικό', 'CUSTOMER': 'Πελάτης', @@ -188,6 +189,7 @@ 'PRIVATE': 'ιδιωτικός', 'ENABLE_USER': 'Ενεργοποίηση χρήστη', 'DISABLE_USER': 'Απενεργοποίηση χρήστη', + 'SHOW_CLOSED_TICKETS': 'Εμφάνιση κλειστών εισιτηρίων', 'IMAGE_HEADER_URL': 'Διεύθυνση URL κεφαλίδας εικόνας', 'CHART_CREATE_TICKET': 'Τα εισιτήρια δημιουργήθηκαν', diff --git a/client/src/data/languages/in.js b/client/src/data/languages/in.js index a5fe8524..b5957e12 100644 --- a/client/src/data/languages/in.js +++ b/client/src/data/languages/in.js @@ -30,6 +30,7 @@ export default { 'RESPOND': 'प्रतिक्रिया', 'RESPOND_TICKET': 'प्रतिक्रिया टिकट', 'CLOSE_TICKET': 'करीबी टिकट', + 'DELETE_TICKET': 'टिकट हटाएं', 'NO_ATTACHMENT': 'कोई फ़ाइल अनुलग्नक नहीं', 'STAFF': 'कर्मचारी', 'CUSTOMER': 'ग्राहक', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'निजी', 'ENABLE_USER': 'उपयोगकर्ता सक्षम करें', 'DISABLE_USER': 'उपयोगकर्ता को अक्षम करें', + 'SHOW_CLOSED_TICKETS': 'बंद टिकट दिखाएं', 'IMAGE_HEADER_URL': 'छवि शीर्षलेख यूआरएल', 'CHART_CREATE_TICKET': 'टिकट बनाया', diff --git a/client/src/data/languages/it.js b/client/src/data/languages/it.js index 86c9efb3..ecf97745 100644 --- a/client/src/data/languages/it.js +++ b/client/src/data/languages/it.js @@ -30,6 +30,7 @@ export default { 'RESPOND': 'Rispondi', 'RESPOND_TICKET': 'Rispondi al ticket', 'CLOSE_TICKET': 'Ticket vicino', + 'DELETE_TICKET': 'Elimina ticket', 'NO_ATTACHMENT': 'Nessun file allegato', 'STAFF': 'Staff', 'CUSTOMER': 'Customer', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'privato', 'ENABLE_USER': 'Abilita utente', 'DISABLE_USER': 'Disabilita utente', + 'SHOW_CLOSED_TICKETS': 'Mostra biglietti chiusi', 'IMAGE_HEADER_URL': 'URL dell\'intestazione dell\'immagine', 'CHART_CREATE_TICKET': 'Tickets creato', diff --git a/client/src/data/languages/jp.js b/client/src/data/languages/jp.js index 9cc7489e..868c6606 100644 --- a/client/src/data/languages/jp.js +++ b/client/src/data/languages/jp.js @@ -30,6 +30,7 @@ export default { 'RESPOND': '応答する', 'RESPOND_TICKET': 'チケット応答', 'CLOSE_TICKET': 'クローズチケット', + 'DELETE_TICKET': 'チケットを削除する', 'NO_ATTACHMENT': '添付ファイルがありません', 'STAFF': 'スタッフ', 'CUSTOMER': '顧客', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'プライベート', 'ENABLE_USER': 'ユーザーを有効にする', 'DISABLE_USER': 'ユーザーを無効にする', + 'SHOW_CLOSED_TICKETS': 'クローズドチケットを表示する', 'IMAGE_HEADER_URL': '画像のヘッダーURL', 'CHART_CREATE_TICKET': '作成されたチケット', diff --git a/client/src/data/languages/nl.js b/client/src/data/languages/nl.js index 72cea7e1..37fe968a 100644 --- a/client/src/data/languages/nl.js +++ b/client/src/data/languages/nl.js @@ -30,6 +30,7 @@ export default { 'RESPOND': 'Actie', 'RESPOND_TICKET': 'Reageren', 'CLOSE_TICKET': 'Sluit ticket', + 'DELETE_TICKET': 'Ticket verwijderen', 'NO_ATTACHMENT': 'Geen bijlage', 'STAFF': 'Management', 'CUSTOMER': 'Klant', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'privaat', 'ENABLE_USER': 'Schakel gebruiker in', 'DISABLE_USER': 'Gebruiker uitschakelen', + 'SHOW_CLOSED_TICKETS': 'Toon gesloten tickets', 'IMAGE_HEADER_URL': 'Image header URL', 'CHART_CREATE_TICKET': 'Aangemaakte incidenten', diff --git a/client/src/data/languages/pt.js b/client/src/data/languages/pt.js index 7e323c17..462f65fb 100644 --- a/client/src/data/languages/pt.js +++ b/client/src/data/languages/pt.js @@ -30,6 +30,7 @@ export default { 'RESPOND': 'Responder', 'RESPOND_TICKET': 'Respond Ticket', 'CLOSE_TICKET': 'Fechar passagem', + 'DELETE_TICKET': 'Apagar ticket', 'NO_ATTACHMENT': 'Nenhum anexo de arquivo', 'STAFF': 'Funcionários', 'CUSTOMER': 'Cliente', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'privado', 'ENABLE_USER': 'Ativar usuário', 'DISABLE_USER': 'Desativar usuário', + 'SHOW_CLOSED_TICKETS': 'Mostrar ingressos fechados', 'IMAGE_HEADER_URL': 'URL do cabeçalho da imagem', 'CHART_CREATE_TICKET': 'Ingressos criados', diff --git a/client/src/data/languages/ru.js b/client/src/data/languages/ru.js index a4eb3f83..325859b1 100644 --- a/client/src/data/languages/ru.js +++ b/client/src/data/languages/ru.js @@ -30,6 +30,7 @@ export default { 'RESPOND': 'Отвечать', 'RESPOND_TICKET': 'Ответить билет', 'CLOSE_TICKET': 'закрыть билет', + 'DELETE_TICKET': 'Удалить билет', 'NO_ATTACHMENT': 'Нет вложений файлов', 'STAFF': 'Сотрудники', 'CUSTOMER': 'Клиент', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'частный', 'ENABLE_USER': 'Включить пользователя', 'DISABLE_USER': 'Отключить пользователя', + 'SHOW_CLOSED_TICKETS': 'Показать закрытые билеты', 'IMAGE_HEADER_URL': 'URL заголовка изображения', 'CHART_CREATE_TICKET': 'Билеты создано', diff --git a/client/src/data/languages/tr.js b/client/src/data/languages/tr.js index 47fbe9eb..c0b01802 100644 --- a/client/src/data/languages/tr.js +++ b/client/src/data/languages/tr.js @@ -30,6 +30,7 @@ export default { 'RESPOND': 'Yanıtla', 'RESPOND_TICKET': 'Bilete Gider', 'CLOSE_TICKET': 'Yakın bilet', + 'DELETE_TICKET': 'Bilet sil', 'NO_ATTACHMENT': 'Dosya eki yok', 'STAFF': 'Personel', 'CUSTOMER': 'Müşteri', @@ -188,6 +189,7 @@ export default { 'PRIVATE': 'gizli', 'ENABLE_USER': 'Kullanıcıyı Etkinleştir', 'DISABLE_USER': 'Kullanıcıyı Devre Dışı Bırak', + 'SHOW_CLOSED_TICKETS': 'Kapalı Biletleri Göster', 'IMAGE_HEADER_URL': 'Resim başlığı URL\'si', 'CHART_CREATE_TICKET': 'Biletler oluşturuldu', diff --git a/server/controllers/staff/get-all-tickets.php b/server/controllers/staff/get-all-tickets.php index 03d2c8bf..7c38a489 100755 --- a/server/controllers/staff/get-all-tickets.php +++ b/server/controllers/staff/get-all-tickets.php @@ -2,18 +2,20 @@ use Respect\Validation\Validator as DataValidator; /** - * @api {post} /staff/get-all-tickets Get all tickets + * @api {post} /staff/get-all-tickets Get all tickets according to search * @apiVersion 4.3.0 * * @apiName Get all tickets * * @apiGroup Staff * - * @apiDescription This path retrieves all tickets. + * @apiDescription This path retrieves all tickets according to search and opened/closed filters. * * @apiPermission staff1 * * @apiParam {Number} page The page number. + * @apiParam {String} query Query string to search. + * @apiParam {Boolean} closed Include closed tickets. * * @apiUse NO_PERMISSION * @apiUse INVALID_PAGE @@ -58,10 +60,24 @@ class GetAllTicketsStaffController extends Controller { private function getTicketList() { $page = Controller::request('page'); - $query = $this->getStaffDepartmentsQueryFilter(); - $query .= 'ORDER BY id DESC LIMIT 10 OFFSET ' . (($page-1)*10); + $query = $this->getSearchQuery(); + $query .= $this->getStaffDepartmentsQueryFilter(); + $query .= $this->getClosedFilter(); + $query .= "ORDER BY CASE WHEN (title LIKE ?) THEN 1 ELSE 2 END ASC, id DESC LIMIT 10 OFFSET " . (($page-1)*10); - return Ticket::find($query); + return Ticket::find($query, [ + Controller::request('query') . '%', + '%' . Controller::request('query') . '%', + Controller::request('query') . '%' + ]); + } + + private function getSearchQuery() { + $page = Controller::request('page'); + + $query = " (title LIKE ? OR title LIKE ?) AND "; + + return $query; } private function getTotalPages() { @@ -81,4 +97,13 @@ class GetAllTicketsStaffController extends Controller { return $query; } + + private function getClosedFilter() { + $closed = Controller::request('closed')*1; + if ($closed) { + return ''; + } else { + return " AND (closed = '0')"; + } + } } diff --git a/server/controllers/staff/get-tickets.php b/server/controllers/staff/get-tickets.php index 1f768042..137cb7eb 100755 --- a/server/controllers/staff/get-tickets.php +++ b/server/controllers/staff/get-tickets.php @@ -13,6 +13,8 @@ use Respect\Validation\Validator as DataValidator; * * @apiPermission staff1 * + * @apiParam {bool} closed Include closed tickets in the response. + * * @apiUse NO_PERMISSION * * @apiSuccess {[Ticket](#api-Data_Structures-ObjectTicket)[]} data Array of tickets assigned to the staff @@ -32,6 +34,11 @@ class GetTicketStaffController extends Controller { public function handler() { $user = Controller::getLoggedUser(); - Response::respondSuccess($user->sharedTicketList->toArray()); + $closed = Controller::request('closed'); + if ($closed) { + Response::respondSuccess($user->sharedTicketList->toArray()); + } else { + Response::respondSuccess($user->withCondition('closed = ?', ['0'])->sharedTicketList->toArray()); + } } -} \ No newline at end of file +} diff --git a/server/controllers/ticket.php b/server/controllers/ticket.php index 2c545a1b..2cce64c5 100755 --- a/server/controllers/ticket.php +++ b/server/controllers/ticket.php @@ -12,6 +12,7 @@ include 'ticket/close.php'; include 'ticket/re-open.php'; include 'ticket/change-priority.php'; include 'ticket/seen.php'; +include 'ticket/delete.php'; $ticketControllers = new ControllerGroup(); $ticketControllers->setGroupPath('/ticket'); @@ -29,5 +30,6 @@ $ticketControllers->addController(new CloseController); $ticketControllers->addController(new ReOpenController); $ticketControllers->addController(new ChangePriorityController); $ticketControllers->addController(new SeenController); +$ticketControllers->addController(new DeleteController); -$ticketControllers->finalize(); \ No newline at end of file +$ticketControllers->finalize(); diff --git a/server/controllers/ticket/delete.php b/server/controllers/ticket/delete.php new file mode 100644 index 00000000..ee35d34c --- /dev/null +++ b/server/controllers/ticket/delete.php @@ -0,0 +1,63 @@ + 'user', + 'requestData' => [ + 'ticketNumber' => [ + 'validation' => DataValidator::validTicketNumber(), + 'error' => ERRORS::INVALID_TICKET + ] + ] + ]; + } + + public function handler() { + $user = Controller::getLoggedUser(); + $ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber')); + $ticketAuthor = $ticket->authorToArray(); + + if($ticket->owner) { + throw new Exception(ERRORS::NO_PERMISSION); + } + + if(Controller::isStaffLogged() && $user->level < 3) { + throw new Exception(ERRORS::NO_PERMISSION); + } + + if(!Controller::isStaffLogged() && ($user->email !== $ticketAuthor['email'] || $ticketAuthor['staff'])) { + throw new Exception(ERRORS::NO_PERMISSION); + } + + $ticket->delete(); + + Response::respondSuccess(); + } +} diff --git a/server/models/DataStore.php b/server/models/DataStore.php index 43ec9d22..d4310fdc 100755 --- a/server/models/DataStore.php +++ b/server/models/DataStore.php @@ -150,6 +150,10 @@ abstract class DataStore { } } + public function withCondition($condition, $values) { + return new static($this->_bean->withCondition($condition, $values)); + } + private function updateBeanProp($key, $value) { if ($value instanceof DataStoreList) { $this->_bean[$key] = $value->toBeanList(); diff --git a/server/models/Ticket.php b/server/models/Ticket.php index 10f5c0d2..40597a44 100755 --- a/server/models/Ticket.php +++ b/server/models/Ticket.php @@ -90,17 +90,21 @@ class Ticket extends DataStore { parent::store(); } + public function delete() { + parent::delete(); + } + public function generateUniqueTicketNumber() { $linearCongruentialGenerator = new LinearCongruentialGenerator(); - $ticketQuantity = Ticket::count(); - if ($ticketQuantity === 0) { + if (Ticket::count() === 0) { $ticketNumber = Setting::getSetting('ticket-first-number')->value; } else { + $lastTicketId = Ticket::findOne(' ORDER BY id DESC')->id; $linearCongruentialGenerator->setGap(Setting::getSetting('ticket-gap')->value); $linearCongruentialGenerator->setFirst(Setting::getSetting('ticket-first-number')->value); - $ticketNumber = $linearCongruentialGenerator->generate($ticketQuantity); + $ticketNumber = $linearCongruentialGenerator->generate($lastTicketId + 1); } return $ticketNumber; diff --git a/tests/init.rb b/tests/init.rb index f4b8e84b..cd719892 100644 --- a/tests/init.rb +++ b/tests/init.rb @@ -27,6 +27,7 @@ require './ticket/custom-response.rb' require './ticket/change-department.rb' require './ticket/close.rb' require './ticket/re-open.rb' +require './ticket/delete.rb' require './staff/add.rb' require './staff/get.rb' require './staff/edit.rb' diff --git a/tests/scripts.rb b/tests/scripts.rb index 50e0d8cb..64872afc 100644 --- a/tests/scripts.rb +++ b/tests/scripts.rb @@ -35,6 +35,17 @@ class Scripts raise response['message'] end end + def self.deleteStaff(staffId) + response = request('/staff/delete', { + staffId: staffId, + csrf_userid: $csrf_userid, + csrf_token: $csrf_token + }) + + if response['status'] === 'fail' + raise response['message'] + end + end def self.login(email = 'steve@jobs.com', password = 'custompassword', staff = false) request('/user/logout') diff --git a/tests/staff/assign-ticket.rb b/tests/staff/assign-ticket.rb index 0af17f4b..64e612b4 100644 --- a/tests/staff/assign-ticket.rb +++ b/tests/staff/assign-ticket.rb @@ -31,10 +31,12 @@ describe '/staff/assign-ticket' do (staff_ticket['ticket_id']).should.equal('1') end it 'should assign ticket if a staff choose another to assing a ticket ' do + staffId = $database.getRow('staff','ayra2@opensupports.com','email')['id'] + ticket = $database.getRow('ticket', 3 , 'id') result = request('/staff/assign-ticket', { ticketNumber: ticket['ticket_number'], - staffId:4, + staffId: staffId, csrf_userid: $csrf_userid, csrf_token: $csrf_token }) @@ -42,10 +44,9 @@ describe '/staff/assign-ticket' do ticket = $database.getRow('ticket', 3 , 'id') - (ticket['owner_id']).should.equal('4') + (ticket['owner_id']).should.equal(staffId) (ticket['unread']).should.equal('1') - end it 'should fail if ticket is already owned' do diff --git a/tests/staff/delete.rb b/tests/staff/delete.rb index 25ba48de..5bada355 100644 --- a/tests/staff/delete.rb +++ b/tests/staff/delete.rb @@ -1,28 +1,30 @@ describe'/staff/delete' do request('/user/logout') Scripts.login($staff[:email], $staff[:password], true) + @staffId = $database.getRow('staff','littlelannister@opensupports.com','email')['id'] it 'should delete staff member' do result= request('/staff/delete', { csrf_userid: $csrf_userid, csrf_token: $csrf_token, - staffId: 3 + staffId: @staffId }) (result['status']).should.equal('success') - row = $database.getRow('staff', 3, 'id') + row = $database.getRow('staff', @staffId, 'id') (row).should.equal(nil) row = $database.getRow('department', 1, 'id') (row['owners']).should.equal('3') end + it 'should fail delete if staff member is does not exist' do - result= request('/staff/delete', { + result = request('/staff/delete', { csrf_userid: $csrf_userid, csrf_token: $csrf_token, - staffId: 3 + staffId: @staffId }) (result['status']).should.equal('fail') @@ -31,4 +33,4 @@ describe'/staff/delete' do row = $database.getRow('department', 1, 'id') (row['owners']).should.equal('3') end -end \ No newline at end of file +end diff --git a/tests/staff/edit.rb b/tests/staff/edit.rb index 5c398936..f22bd6ac 100644 --- a/tests/staff/edit.rb +++ b/tests/staff/edit.rb @@ -3,23 +3,24 @@ describe'/staff/edit' do Scripts.login($staff[:email], $staff[:password], true) it 'should edit another staff member' do + staffId = $database.getRow('staff','tyrion@opensupports.com','email')['id'] result= request('/staff/edit', { csrf_userid: $csrf_userid, csrf_token: $csrf_token, email: 'LittleLannister@opensupports.com', level: 1, departments: '[1, 2]', - staffId: 3 + staffId: staffId }) (result['status']).should.equal('success') - row = $database.getRow('staff', 3, 'id') + row = $database.getRow('staff', staffId, 'id') (row['email']).should.equal('littlelannister@opensupports.com') (row['level']).should.equal('1') - rows = $database.getRow('department_staff', 3, 'staff_id') + rows = $database.getRow('department_staff', staffId, 'staff_id') (rows['department_id']).should.equal('1') @@ -28,7 +29,6 @@ describe'/staff/edit' do row = $database.getRow('department', 2, 'id') (row['owners']).should.equal('2') - end it 'should edit staff member ' do @@ -43,7 +43,7 @@ describe'/staff/edit' do departments: '[1]' }) - row = $database.getRow('staff', 'Arya Stark', 'name') + row = $database.getRow('staff', 'arya@opensupports.com', 'email') result = request('/staff/edit', { csrf_userid: $csrf_userid, diff --git a/tests/staff/get-all.rb b/tests/staff/get-all.rb index 6e1070d6..5f3d3267 100644 --- a/tests/staff/get-all.rb +++ b/tests/staff/get-all.rb @@ -20,7 +20,7 @@ describe'/staff/get-all' do (result['data'][0]['departments'][1]['name']).should.equal('Suggestions') (result['data'][0]['departments'][2]['id']).should.equal('3') (result['data'][0]['departments'][2]['name']).should.equal('Tech support') - (result['data'][0]['assignedTickets']).should.equal(4) + (result['data'][0]['assignedTickets']).should.equal(6) (result['data'][0]['closedTickets']).should.equal(0) (result['data'][2]['name']).should.equal('Arya Stark') diff --git a/tests/staff/get-new-tickets.rb b/tests/staff/get-new-tickets.rb index debe36f0..7561aa1b 100644 --- a/tests/staff/get-new-tickets.rb +++ b/tests/staff/get-new-tickets.rb @@ -3,14 +3,12 @@ describe '/staff/get-new-tickets' do Scripts.login($staff[:email], $staff[:password], true) it 'should get news tickets' do - result = request('/staff/get-new-tickets', { csrf_userid: $csrf_userid, csrf_token: $csrf_token }) (result['status']).should.equal('success') - (result['data'].size).should.equal(8) - + (result['data'].size).should.equal(9) end end diff --git a/tests/staff/get-tickets.rb b/tests/staff/get-tickets.rb index cd896a6b..af0bce51 100644 --- a/tests/staff/get-tickets.rb +++ b/tests/staff/get-tickets.rb @@ -22,6 +22,6 @@ describe '/staff/get-tickets' do }) (result['status']).should.equal('success') - (result['data'].size).should.equal(3) + (result['data'].size).should.equal(5) end end diff --git a/tests/staff/get.rb b/tests/staff/get.rb index 6470ea25..b4d0cef5 100644 --- a/tests/staff/get.rb +++ b/tests/staff/get.rb @@ -16,10 +16,11 @@ describe '/staff/get/' do (result['data']['sendEmailOnNewTicket']).should.equal('1') end it 'should return staff member data with staff Id' do + staff = $database.getRow('staff','tyrion@opensupports.com','email') result = request('/staff/get', { csrf_userid: $csrf_userid, csrf_token: $csrf_token, - staffId: 3 + staffId: staff['id'] }) (result['status']).should.equal('success') @@ -29,4 +30,4 @@ describe '/staff/get/' do (result['data']['level']).should.equal('2') (result['data']['sendEmailOnNewTicket']).should.equal('0') end -end \ No newline at end of file +end diff --git a/tests/system/disable-user-system.rb b/tests/system/disable-user-system.rb index e96adb56..703a71b4 100644 --- a/tests/system/disable-user-system.rb +++ b/tests/system/disable-user-system.rb @@ -19,7 +19,7 @@ describe'system/disable-user-system' do numberOftickets= $database.query("SELECT * FROM ticket WHERE author_id IS NULL AND author_email IS NOT NULL AND author_name IS NOT NULL") - (numberOftickets.num_rows).should.equal(39) + (numberOftickets.num_rows).should.equal(40) request('/user/logout') @@ -127,7 +127,7 @@ describe'system/disable-user-system' do numberOftickets= $database.query("SELECT * FROM ticket WHERE author_email IS NULL AND author_name IS NULL AND author_id IS NOT NULL" ) - (numberOftickets.num_rows).should.equal(40) + (numberOftickets.num_rows).should.equal(41) end diff --git a/tests/ticket/delete.rb b/tests/ticket/delete.rb new file mode 100644 index 00000000..6b0c5acf --- /dev/null +++ b/tests/ticket/delete.rb @@ -0,0 +1,114 @@ +describe '/ticket/delete' do + + it 'should delete ticket if it is not assigned and is logged a staff lvl 3 ' do + request('/user/logout') + Scripts.login($staff[:email], $staff[:password], true) + Scripts.createTicket('ticket_to_delete') + ticket = $database.getRow('ticket', 'ticket_to_delete', 'title') + + request('/staff/add', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + name: 'Ned Stark', + password: 'headless', + email: 'ned@opensupports.com', + level: 3, + profilePic: '', + departments: '[1]' + }) + + request('/user/logout') + Scripts.login('ned@opensupports.com', 'headless', true) + + result = request('/ticket/delete', { + ticketNumber: ticket['ticket_number'], + csrf_userid: $csrf_userid, + csrf_token: $csrf_token + }) + + (result['status']).should.equal('success') + end + + it 'should delete ticket if it is yours and it is not assigned' do + request('/user/logout') + Scripts.createUser('deleter@opensupports.com', 'deleterpassword', 'Delter') + Scripts.login('deleter@opensupports.com', 'deleterpassword') + + Scripts.createTicket('ticket_to_delete_2') + ticket = $database.getRow('ticket', 'ticket_to_delete_2', 'title'); + result = request('/ticket/delete', { + ticketNumber: ticket['ticket_number'], + csrf_userid: $csrf_userid, + csrf_token: $csrf_token + }) + (result['status']).should.equal('success') + end + + it 'should not delete ticket if it is assigned' do + request('/user/logout') + Scripts.login('deleter@opensupports.com', 'deleterpassword') + + Scripts.createTicket('ticket_to_delete_3') + ticket = $database.getRow('ticket', 'ticket_to_delete_3', 'title'); + + request('/user/logout') + Scripts.login($staff[:email], $staff[:password], true) + + result = request('/staff/assign-ticket', { + ticketNumber: ticket['ticket_number'], + csrf_userid: $csrf_userid, + csrf_token: $csrf_token + }) + + request('/user/logout') + Scripts.login('deleter@opensupports.com', 'deleterpassword') + + result = request('/ticket/delete', { + ticketNumber: ticket['ticket_number'], + csrf_userid: $csrf_userid, + csrf_token: $csrf_token + }) + + (result['status']).should.equal('fail') + (result['message']).should.equal('NO_PERMISSION') + end + + it 'should not delete ticket if the staff logged is not lvl 3' do + request('/user/logout') + Scripts.login($staff[:email], $staff[:password], true) + Scripts.createTicket('ticket_to_delete_4') + + ticket = $database.getRow('ticket', 'ticket_to_delete_4', 'title'); + + request('/staff/add', { + csrf_userid: $csrf_userid, + csrf_token: $csrf_token, + name: 'Joan Chris', + password: 'theyaregonnafireme', + email: 'uselessstaff@opensupports.com', + level: 2, + profilePic: '', + departments: '[1]' + }) + request('/user/logout') + + Scripts.login('uselessstaff@opensupports.com', 'theyaregonnafireme',true) + + result = request('/ticket/delete', { + ticketNumber: ticket['ticket_number'], + csrf_userid: $csrf_userid, + csrf_token: $csrf_token + }) + + (result['status']).should.equal('fail') + (result['message']).should.equal('NO_PERMISSION') + + request('/user/logout') + Scripts.login($staff[:email], $staff[:password], true) + staff = $database.getRow('staff', 'ned@opensupports.com', 'email') + Scripts.deleteStaff(staff['id']) + + staff = $database.getRow('staff', 'uselessstaff@opensupports.com', 'email') + Scripts.deleteStaff(staff['id']) + end +end diff --git a/tests/user/get-users-test.rb b/tests/user/get-users-test.rb index a9049dac..b20bbec1 100644 --- a/tests/user/get-users-test.rb +++ b/tests/user/get-users-test.rb @@ -36,7 +36,7 @@ describe '/user/get-users' do }) (result['status']).should.equal('success') - (result['data']['users'].size).should.equal(5) + (result['data']['users'].size).should.equal(6) end it 'should get users with order by tickets and asc' do