mirror of
https://github.com/opensupports/opensupports.git
synced 2025-10-28 01:53:56 +01:00
* Adds first_closed_at and last_closed_at to Ticket * Fixes ticket isClosed function * Adds reopened column to Ticket table * Adds stats path * Adds stats for instant tickets * Adds basic connection with frontend * Creates cards to display ticketData * Adds tooltips with descriptions and i18n * Adds date range filter to backend * Adds DateRange filter on frontend * Documents and better structures code * Makes $dateRange local * Adds departments filter on backend * Adds stats path to menu * Adds first UI for departments filter in stats * Implements departments filter on frontend * Improves styling by adding bootstrap rows * Improves structure of dynamics queries * Adds tags filter on backend * Adding TagFilter for statistics WIP * Adds missing `id` to propTypes TagSelector * Removes console.warns * Adapts form to pass tagnames as value as FormField * Sends tags to API too * Makes tag-selector change form with tagnames only * Fixes tag-selector from ticket-viewer * Removes console.warn * Removes logs * Adds owner filter on backend * Connects owners frontend with backend for stats * Style changes for date-selector * Adds tickets by hours stat to /system/stats path * Adds chart for tickets created on each hour * Adds better wrap for ticketdata cards * Adds getAverageFirstReply to backend stats path * Adds getNumberOfCreatedTicketsByWeekday to backend * Adds created tickets by weekday chart * Disables clicking on the legend to toggle data * Adds base functions for efficiency stats * Adds getAverageFirstClosed to backend stats * Adds getAverageLastClosed to backend stats * Adds table, filters, and groupBy variables to queries * Adds response structure with mocks * Adds totalOwners and totalDepartments * Adds SQL queries to get department/staff hops of a ticket * Changes incorrect name * Rolls back addition of near useless function * Improves tag array management from redux store * Fix bug in autocomplete filters. * Sets default date range to current month. improves date.js. * Adds i18n * wip * Add media query in admin-panel-stats.scss * Updates date handling in search-ticket-utils * Makes tooltip open on hover of the entire block * Fix date range mobile style. * Add Loading * Add submit button and clear button in admin panel stats * Adds tests for stats and comments old ones * Add test for stats after a ticket has been created * Makes default dateRange for stats go to the end of the day * Factors out function to create ticket and adds test * Adds instant ticket test * Adds reopened test * Commit to save technique to test created_by_hour but is prohibitively slow. * Updates test of created_by_hour to be more lightweight * Adds test for created_by_weekday * Fixes default date and renames a function * Fixes hover bug by extracting card-stat to its own component * Fix drawbacks with previous change in style - mobile Co-authored-by: LautaroCesso <lautaro_cesso@hotmail.com> * Set up 0 as a minimum number for bar chart * Moves styles from stats cards to the component * Removes old /system/get-stats path * Changes name from /system/stats to /system/get-stats * Restore getCurrentDate in date transformer Co-authored-by: LautaroCesso <lautaro_cesso@hotmail.com> Co-authored-by: Ivan Diaz <ivan@opensupports.com>
237 lines
7.8 KiB
PHP
Executable File
237 lines
7.8 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* @api {OBJECT} Ticket Ticket
|
|
* @apiVersion 4.8.0
|
|
* @apiGroup Data Structures
|
|
* @apiParam {Number} ticketNumber The number of the ticket.
|
|
* @apiParam {String} title The title of the ticket.
|
|
* @apiParam {String} content The content of the ticket.
|
|
* @apiParam {Object} department The department of the ticket.
|
|
* @apiParam {Number} department.id The id of the department of the ticket.
|
|
* @apiParam {String} department.name The department's name of the ticket.
|
|
* @apiParam {String} file The filename of the ticket if attached.
|
|
* @apiParam {String} language The language of the ticket.
|
|
* @apiParam {Boolean} unread Indicates if the user has already read the last comment.
|
|
* @apiParam {Boolean} unreadStaff Indicates if the staff has already read the last comment.
|
|
* @apiParam {Boolean} closed Indicates if the ticket is closed.
|
|
* @apiParam {Object} author The author of the ticket.
|
|
* @apiParam {Number} author.id The id of the author of the ticket.
|
|
* @apiParam {String} author.name The author's name of the ticket.
|
|
* @apiParam {String} author.email The author's email of the ticket.
|
|
* @apiParam {Object} owner The owner of the ticket.
|
|
* @apiParam {Number} owner.id The owner's id of the ticket.
|
|
* @apiParam {String} owner.name The owner's name of the ticket.
|
|
* @apiParam {String} owner.email The owner's email of the ticket.
|
|
* @apiParam {[TicketEvent](#api-Data_Structures-ObjectTicketevent)[]} events Events related to the ticket.
|
|
*/
|
|
use RedBeanPHP\Facade as RedBean;
|
|
|
|
class Ticket extends DataStore {
|
|
const TABLE = 'ticket';
|
|
|
|
public static function getProps() {
|
|
return array(
|
|
'ticketNumber',
|
|
'title',
|
|
'content',
|
|
'language',
|
|
'department',
|
|
'file',
|
|
'date',
|
|
'unread',
|
|
'closed',
|
|
'first_closed_at',
|
|
'last_closed_at',
|
|
'reopened',
|
|
'author',
|
|
'authorStaff',
|
|
'owner',
|
|
'ownTicketeventList',
|
|
'unreadStaff',
|
|
'language',
|
|
'authorEmail',
|
|
'authorName',
|
|
'sharedTagList',
|
|
'editedContent',
|
|
'editedTitle',
|
|
'totalDepartments',
|
|
'totalOwners'
|
|
);
|
|
}
|
|
|
|
public static function getFetchAs() {
|
|
return [
|
|
'author' => 'user',
|
|
'authorStaff' => 'staff',
|
|
'owner' => 'staff',
|
|
];
|
|
}
|
|
|
|
public static function getTicket($value, $property = 'id') {
|
|
return parent::getDataStore($value, $property);
|
|
}
|
|
|
|
public static function getByTicketNumber($value) {
|
|
return Ticket::getTicket($value, 'ticketNumber');
|
|
}
|
|
|
|
public function setAuthor($author) {
|
|
if($author instanceof User) {
|
|
$this->author = $author;
|
|
} else if($author instanceof Staff) {
|
|
$this->authorStaff = $author;
|
|
}
|
|
}
|
|
|
|
public function getAuthor() {
|
|
if($this->author && !$this->author->isNull()) {
|
|
return $this->author;
|
|
} else {
|
|
return $this->authorStaff;
|
|
}
|
|
}
|
|
|
|
public function getDefaultProps() {
|
|
return array(
|
|
'unread' => false,
|
|
'unreadStaff' => true,
|
|
'ticketNumber' => $this->generateUniqueTicketNumber()
|
|
);
|
|
}
|
|
|
|
public function store() {
|
|
parent::store();
|
|
}
|
|
|
|
public function delete() {
|
|
parent::delete();
|
|
}
|
|
|
|
public function generateUniqueTicketNumber() {
|
|
$linearCongruentialGenerator = new LinearCongruentialGenerator();
|
|
|
|
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($lastTicketId + 1);
|
|
}
|
|
|
|
return $ticketNumber;
|
|
}
|
|
|
|
public function toArray($minimized = false) {
|
|
return [
|
|
'ticketNumber' => $this->ticketNumber,
|
|
'title' => $this->title,
|
|
'content' => $minimized ? strip_tags($this->content) : $this->content,
|
|
'department' => [
|
|
'id' => $this->department->id,
|
|
'name' => $this->department->name
|
|
],
|
|
'date' => $this->date,
|
|
'file' => $this->file,
|
|
'language' => $this->language,
|
|
'unread' => !!$this->unread,
|
|
'unreadStaff' => !!$this->unreadStaff,
|
|
'closed' => !!$this->closed,
|
|
'author' => $this->authorToArray(),
|
|
'owner' => $this->ownerToArray(),
|
|
'events' => $minimized ? [] : $this->eventsToArray(),
|
|
'tags' => $this->sharedTagList->toArray(true),
|
|
'edited' => $this->editedContent,
|
|
'editedTitle' => $this->editedTitle
|
|
];
|
|
}
|
|
|
|
public function authorToArray() {
|
|
$author = $this->getAuthor();
|
|
|
|
if ($author && !$author->isNull()) {
|
|
return [
|
|
'id' => $author->id,
|
|
'name' => $author->name,
|
|
'staff' => $author instanceof Staff,
|
|
'profilePic' => ($author instanceof Staff) ? $author->profilePic : null,
|
|
'email' => $author->email,
|
|
'customfields' => $author->xownCustomfieldvalueList ? $author->xownCustomfieldvalueList->toArray() : [],
|
|
];
|
|
} else {
|
|
return [
|
|
'id' => NULL,
|
|
'staff' => false,
|
|
'name' => $this->authorName,
|
|
'email' => $this->authorEmail
|
|
];
|
|
}
|
|
}
|
|
|
|
public function ownerToArray() {
|
|
$owner = $this->owner;
|
|
|
|
if ($owner && !$owner->isNull()) {
|
|
return [
|
|
'id' => $owner->id,
|
|
'name' => $owner->name,
|
|
'email' => $owner->email
|
|
];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public function eventsToArray() {
|
|
$events = [];
|
|
|
|
foreach ($this->ownTicketeventList as $ticketEvent) {
|
|
$event = [
|
|
'type' => $ticketEvent->type,
|
|
'content'=> $ticketEvent->content,
|
|
'author' => [],
|
|
'date'=> $ticketEvent->date,
|
|
'file'=> $ticketEvent->file,
|
|
'private'=> $ticketEvent->private,
|
|
'edited' => $ticketEvent->editedContent,
|
|
'id' => $ticketEvent->id
|
|
];
|
|
|
|
$author = $ticketEvent->getAuthor();
|
|
if($author && !$author->isNull()) {
|
|
$event['author'] = [
|
|
'id'=> $author->id,
|
|
'name' => $author->name,
|
|
'email' =>$author->email,
|
|
'profilePic' => ($author instanceof Staff) ? $author->profilePic : null,
|
|
'staff' => $author instanceof Staff
|
|
];
|
|
}
|
|
|
|
if(!Controller::isStaffLogged() && $ticketEvent->private) {
|
|
continue;
|
|
}
|
|
|
|
$events[] = $event;
|
|
}
|
|
|
|
return $events;
|
|
}
|
|
|
|
public function addEvent(Ticketevent $event) {
|
|
$this->ownTicketeventList->add($event);
|
|
}
|
|
|
|
public function isAuthor($user) {
|
|
$ticketAuthor = $this->authorToArray();
|
|
if(is_string($user)) return $user == $ticketAuthor['email'];
|
|
if(!($user instanceof DataStore) || $user->isNull()) return false;
|
|
return $user->id == $ticketAuthor['id'] && ($user instanceof Staff) == $ticketAuthor['staff'];
|
|
}
|
|
|
|
public function isOwner($user) {
|
|
return !$user->isNull() && $this->owner && $user->id == $this->owner->id && ($user instanceof Staff);
|
|
}
|
|
}
|