mirror of
https://github.com/freescout-helpdesk/freescout.git
synced 2025-01-31 20:11:38 +01:00
Conversation status change
This commit is contained in:
parent
4825d58d4a
commit
5a38ed1249
@ -3,6 +3,7 @@
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Folder;
|
||||
|
||||
class Conversation extends Model
|
||||
{
|
||||
@ -101,7 +102,7 @@ class Conversation extends Model
|
||||
/**
|
||||
* Automatically converted into Carbon dates.
|
||||
*/
|
||||
protected $dates = ['created_at', 'updated_at', 'last_reply_at'];
|
||||
protected $dates = ['created_at', 'updated_at', 'last_reply_at', 'closed_at'];
|
||||
|
||||
/**
|
||||
* Attributes which are not fillable using fill() method.
|
||||
@ -165,6 +166,30 @@ class Conversation extends Model
|
||||
return $this->belongsTo('App\Customer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user who created the conversations.
|
||||
*/
|
||||
public function created_by_user()
|
||||
{
|
||||
return $this->belongsTo('App\User');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customer who created the conversations.
|
||||
*/
|
||||
public function created_by_customer()
|
||||
{
|
||||
return $this->belongsTo('App\Customer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user who closed the conversations.
|
||||
*/
|
||||
public function closed_by_user()
|
||||
{
|
||||
return $this->belongsTo('App\User');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set preview text.
|
||||
*
|
||||
@ -244,6 +269,25 @@ class Conversation extends Model
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set conersation status and all related fields.
|
||||
*
|
||||
* @param integer $status
|
||||
*/
|
||||
public function setStatus($status, $user = null)
|
||||
{
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$this->status = $status;
|
||||
$this->updateFolder();
|
||||
$this->user_updated_at = $now;
|
||||
|
||||
if ($user && $status == self::STATUS_CLOSED) {
|
||||
$this->closed_by_user_id = $user->id;
|
||||
$this->closed_at = $now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next active conversation.
|
||||
*
|
||||
@ -299,4 +343,33 @@ class Conversation extends Model
|
||||
|
||||
return $query_prev->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set folder according to the status, sate and user of the conversation.
|
||||
*/
|
||||
public function updateFolder()
|
||||
{
|
||||
if ($this->state == self::STATE_DRAFT) {
|
||||
$folder_type = Folder::TYPE_DRAFTS;
|
||||
} elseif ($this->state == self::STATE_DELETED) {
|
||||
$folder_type = Folder::TYPE_DELETED;
|
||||
} elseif ($this->status == self::STATUS_SPAM) {
|
||||
$folder_type = Folder::TYPE_SPAM;
|
||||
} elseif ($this->status == self::STATUS_CLOSED) {
|
||||
$folder_type = Folder::TYPE_CLOSED;
|
||||
} elseif ($this->user_id) {
|
||||
$folder_type = Folder::TYPE_ASSIGNED;
|
||||
} else {
|
||||
$folder_type = Folder::TYPE_UNASSIGNED;
|
||||
}
|
||||
|
||||
// Find folder
|
||||
$folder = $this->mailbox->folders()
|
||||
->where('type', $folder_type)
|
||||
->first();
|
||||
|
||||
if ($folder) {
|
||||
$this->folder_id = $folder->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
app/Events/ConversationStatusChanged.php
Normal file
20
app/Events/ConversationStatusChanged.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Conversation;
|
||||
|
||||
class ConversationStatusChanged
|
||||
{
|
||||
public $conversation;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Conversation $conversation)
|
||||
{
|
||||
$this->conversation = $conversation;
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Event
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return \Illuminate\Broadcasting\Channel|array
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
return new PrivateChannel('channel-name');
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Conversation;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Events\ConversationStatusChanged;
|
||||
|
||||
class ConversationsController extends Controller
|
||||
{
|
||||
@ -30,7 +31,7 @@ class ConversationsController extends Controller
|
||||
'conversation' => $conversation,
|
||||
'mailbox' => $conversation->mailbox,
|
||||
'customer' => $conversation->customer,
|
||||
'threads' => $conversation->threads,
|
||||
'threads' => $conversation->threads()->orderBy('created_at', 'desc')->get(),
|
||||
'folder' => $conversation->folder,
|
||||
'folders' => $conversation->mailbox->getAssesibleFolders(),
|
||||
]);
|
||||
@ -43,17 +44,20 @@ class ConversationsController extends Controller
|
||||
'msg' => '',
|
||||
);
|
||||
|
||||
$user = auth()->user();
|
||||
|
||||
switch ($request->action) {
|
||||
case 'change_status':
|
||||
$conversation = Conversation::find($request->conversation_id);
|
||||
$new_status = (int)$request->status;
|
||||
|
||||
if (!$conversation) {
|
||||
$response['msg'] = 'Conversation not found';
|
||||
}
|
||||
if (!$response['msg'] && $conversation->status == $new_status) {
|
||||
$response['msg'] = 'Status already set';
|
||||
}
|
||||
if (!$response['msg'] && !auth()->user()->can('update', $conversation)) {
|
||||
if (!$response['msg'] && !$user->can('update', $conversation)) {
|
||||
$response['msg'] = 'Not enough permissions';
|
||||
}
|
||||
if (!$response['msg'] && !in_array((int)$request->status, array_keys(Conversation::$statuses))) {
|
||||
@ -63,9 +67,11 @@ class ConversationsController extends Controller
|
||||
// Next conversation has to be determined before updating status for current one
|
||||
$next_conversation = $conversation->getNearby();
|
||||
|
||||
$conversation->status = $new_status;
|
||||
$conversation->user_updated_at = date('Y-m-d H:i:s');
|
||||
$conversation->setStatus($new_status, $user);
|
||||
$conversation->save();
|
||||
|
||||
event(new ConversationStatusChanged($conversation));
|
||||
|
||||
$response['status'] = 'success';
|
||||
|
||||
// Flash
|
||||
|
42
app/Listeners/CreateThreadStatusChanged.php
Normal file
42
app/Listeners/CreateThreadStatusChanged.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\ConversationStatusChanged;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use App\Thread;
|
||||
|
||||
class CreateThreadStatusChanged
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param ConversationStatusChanged $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(ConversationStatusChanged $event)
|
||||
{
|
||||
$thread = new Thread();
|
||||
$thread->conversation_id = $event->conversation->id;
|
||||
$thread->type = Thread::TYPE_LINEITEM;
|
||||
$thread->state = Thread::STATE_PUBLISHED;
|
||||
$thread->action_type = Thread::ACTION_TYPE_STATUS_CHANGED;
|
||||
$thread->source_via = Thread::PERSON_USER;
|
||||
// todo: this need to be changed for API
|
||||
$thread->source_type = Thread::SOURCE_TYPE_WEB;
|
||||
$thread->customer_id = $event->conversation->customer_id;
|
||||
$thread->created_by_user_id = auth()->user()->id;
|
||||
$thread->save();
|
||||
}
|
||||
}
|
30
app/Listeners/UpdateMailboxCounters.php
Normal file
30
app/Listeners/UpdateMailboxCounters.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class UpdateMailboxCounters
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
$event->conversation->mailbox->updateFoldersCounters();
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ class MailboxObserver
|
||||
*/
|
||||
public function created(Mailbox $mailbox)
|
||||
{
|
||||
// Create folders
|
||||
foreach (Folder::$public_types as $type) {
|
||||
$folder = new Folder();
|
||||
$folder->mailbox_id = $mailbox->id;
|
||||
|
@ -36,6 +36,11 @@ class EventServiceProvider extends ServiceProvider
|
||||
'Illuminate\Auth\Events\PasswordReset' => [
|
||||
'App\Listeners\LogPasswordReset',
|
||||
],
|
||||
|
||||
'App\Events\ConversationStatusChanged' => [
|
||||
'App\Listeners\CreateThreadStatusChanged',
|
||||
'App\Listeners\UpdateMailboxCounters',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
180
app/Thread.php
180
app/Thread.php
@ -7,18 +7,18 @@ use Illuminate\Database\Eloquent\Model;
|
||||
class Thread extends Model
|
||||
{
|
||||
/**
|
||||
* By whom action performed (source_via).
|
||||
* By whom action performed (source_via)
|
||||
*/
|
||||
const PERSON_CUSTOMER = 1;
|
||||
const PERSON_USER = 2;
|
||||
|
||||
public static $persons = [
|
||||
|
||||
public static $persons = array(
|
||||
self::PERSON_CUSTOMER => 'customer',
|
||||
self::PERSON_USER => 'user',
|
||||
];
|
||||
self::PERSON_USER => 'user',
|
||||
);
|
||||
|
||||
/**
|
||||
* Thread types.
|
||||
* Thread types
|
||||
*/
|
||||
// Email from customer
|
||||
const TYPE_CUSTOMER = 1;
|
||||
@ -35,23 +35,23 @@ class Thread extends Model
|
||||
|
||||
public static $types = [
|
||||
// Thread by customer
|
||||
self::TYPE_CUSTOMER => 'customer',
|
||||
self::TYPE_CUSTOMER => 'customer',
|
||||
// Thread by user
|
||||
self::TYPE_MESSAGE => 'message',
|
||||
self::TYPE_NOTE => 'note',
|
||||
self::TYPE_MESSAGE => 'message',
|
||||
self::TYPE_NOTE => 'note',
|
||||
// lineitem represents a change of state on the conversation. This could include, but not limited to, the conversation was assigned, the status changed, the conversation was moved from one mailbox to another, etc. A line item won’t have a body, to/cc/bcc lists, or attachments.
|
||||
self::TYPE_LINEITEM => 'lineitem',
|
||||
self::TYPE_PHONE => 'phone',
|
||||
self::TYPE_LINEITEM => 'lineitem',
|
||||
self::TYPE_PHONE => 'phone',
|
||||
// When a conversation is forwarded, a new conversation is created to represent the forwarded conversation.
|
||||
// forwardparent is the type set on the thread of the original conversation that initiated the forward event.
|
||||
self::TYPE_FORWARDPARENT => 'forwardparent',
|
||||
self::TYPE_FORWARDPARENT => 'forwardparent',
|
||||
// forwardchild is the type set on the first thread of the new forwarded conversation.
|
||||
self::TYPE_FORWARDCHILD => 'forwardchild',
|
||||
self::TYPE_CHAT => 'chat',
|
||||
self::TYPE_FORWARDCHILD => 'forwardchild',
|
||||
self::TYPE_CHAT => 'chat',
|
||||
];
|
||||
|
||||
/**
|
||||
* Statuses.
|
||||
* Statuses
|
||||
*/
|
||||
const STATUS_ACTIVE = 1;
|
||||
const STATUS_CLOSED = 2;
|
||||
@ -59,79 +59,85 @@ class Thread extends Model
|
||||
const STATUS_PENDING = 4;
|
||||
const STATUS_SPAM = 5;
|
||||
|
||||
public static $statuses = [
|
||||
self::STATUS_ACTIVE => 'active',
|
||||
self::STATUS_CLOSED => 'closed',
|
||||
self::STATUS_NOCHANGE => 'nochange',
|
||||
self::STATUS_PENDING => 'pending',
|
||||
self::STATUS_SPAM => 'spam',
|
||||
];
|
||||
public static $statuses = array(
|
||||
self::STATUS_ACTIVE => 'active',
|
||||
self::STATUS_CLOSED => 'closed',
|
||||
self::STATUS_NOCHANGE => 'nochange',
|
||||
self::STATUS_PENDING => 'pending',
|
||||
self::STATUS_SPAM => 'spam',
|
||||
);
|
||||
|
||||
/**
|
||||
* States.
|
||||
* States
|
||||
*/
|
||||
const STATE_DRAFT = 1;
|
||||
const STATE_PUBLISHED = 2;
|
||||
const STATE_HIDDEN = 3;
|
||||
const STATE_REVIEW = 4;
|
||||
|
||||
public static $states = [
|
||||
self::STATE_DRAFT => 'draft',
|
||||
self::STATE_PUBLISHED => 'published',
|
||||
self::STATE_HIDDEN => 'hidden',
|
||||
self::STATE_REVIEW => 'review',
|
||||
];
|
||||
|
||||
public static $states = array(
|
||||
self::STATE_DRAFT => 'draft',
|
||||
self::STATE_PUBLISHED => 'published',
|
||||
self::STATE_HIDDEN => 'hidden',
|
||||
self::STATE_REVIEW => 'review',
|
||||
);
|
||||
|
||||
/**
|
||||
* Action associated with the line item.
|
||||
* Action associated with the line item
|
||||
*/
|
||||
// Conversation's status changed
|
||||
const ACTION_TYPE_STATUS_CHANGED = 1;
|
||||
// Conversation's assignee changed
|
||||
const ACTION_TYPE_USER_CHANGED = 2;
|
||||
// The conversation was moved from another mailbox
|
||||
const ACTION_TYPE_MOVED_FROM_MAILBOX = 1;
|
||||
const ACTION_TYPE_MOVED_FROM_MAILBOX = 3;
|
||||
// Another conversation was merged with this conversation
|
||||
const ACTION_TYPE_MERGED = 2;
|
||||
const ACTION_TYPE_MERGED = 4;
|
||||
// The conversation was imported (no email notifications were sent)
|
||||
const ACTION_TYPE_IMPORTED = 3;
|
||||
const ACTION_TYPE_IMPORTED = 5;
|
||||
// A workflow was run on this conversation (either automatic or manual)
|
||||
const ACTION_TYPE_WORKFLOW_MANUAL = 4;
|
||||
const ACTION_TYPE_WORKFLOW_AUTO = 5;
|
||||
const ACTION_TYPE_WORKFLOW_MANUAL = 6;
|
||||
const ACTION_TYPE_WORKFLOW_AUTO = 7;
|
||||
// The ticket was imported from an external Service
|
||||
const ACTION_TYPE_IMPORTED_EXTERNAL = 6;
|
||||
const ACTION_TYPE_IMPORTED_EXTERNAL = 8;
|
||||
// The customer associated with the ticket was changed
|
||||
const ACTION_TYPE_CHANGED_TICKET_CUSTOMER = 7;
|
||||
const ACTION_TYPE_CHANGED_TICKET_CUSTOMER = 9;
|
||||
// The ticket was deleted
|
||||
const ACTION_TYPE_DELETED_TICKET = 8;
|
||||
const ACTION_TYPE_DELETED_TICKET = 10;
|
||||
// The ticket was restored
|
||||
const ACTION_TYPE_RESTORE_TICKET = 9;
|
||||
const ACTION_TYPE_RESTORE_TICKET = 11;
|
||||
|
||||
// Describes an optional action associated with the line item
|
||||
// todo: values need to be checked via HelpScout API
|
||||
public static $action_types = [
|
||||
self::ACTION_TYPE_MOVED_FROM_MAILBOX => 'moved-from-mailbox',
|
||||
self::ACTION_TYPE_MERGED => 'merged',
|
||||
self::ACTION_TYPE_IMPORTED => 'imported',
|
||||
self::ACTION_TYPE_WORKFLOW_MANUAL => 'manual-workflow',
|
||||
self::ACTION_TYPE_WORKFLOW_AUTO => 'automatic-workflow',
|
||||
self::ACTION_TYPE_IMPORTED_EXTERNAL => 'imported-external',
|
||||
self::ACTION_TYPE_CHANGED_TICKET_CUSTOMER => 'changed-ticket-customer',
|
||||
self::ACTION_TYPE_DELETED_TICKET => 'deleted-ticket',
|
||||
self::ACTION_TYPE_RESTORE_TICKET => 'restore-ticket',
|
||||
self::ACTION_TYPE_STATUS_CHANGED => 'changed-ticket-status',
|
||||
self::ACTION_TYPE_USER_CHANGED => 'changed-ticket-assignee',
|
||||
self::ACTION_TYPE_MOVED_FROM_MAILBOX => 'moved-from-mailbox',
|
||||
self::ACTION_TYPE_MERGED => 'merged',
|
||||
self::ACTION_TYPE_IMPORTED => 'imported',
|
||||
self::ACTION_TYPE_WORKFLOW_MANUAL => 'manual-workflow',
|
||||
self::ACTION_TYPE_WORKFLOW_AUTO => 'automatic-workflow',
|
||||
self::ACTION_TYPE_IMPORTED_EXTERNAL => 'imported-external',
|
||||
self::ACTION_TYPE_CHANGED_TICKET_CUSTOMER => 'changed-ticket-customer',
|
||||
self::ACTION_TYPE_DELETED_TICKET => 'deleted-ticket',
|
||||
self::ACTION_TYPE_RESTORE_TICKET => 'restore-ticket',
|
||||
];
|
||||
|
||||
/**
|
||||
* Source types (equal to thread source types).
|
||||
/**
|
||||
* Source types (equal to thread source types)
|
||||
*/
|
||||
const SOURCE_TYPE_EMAIL = 1;
|
||||
const SOURCE_TYPE_WEB = 2;
|
||||
const SOURCE_TYPE_API = 3;
|
||||
|
||||
|
||||
public static $source_types = [
|
||||
self::SOURCE_TYPE_EMAIL => 'email',
|
||||
self::SOURCE_TYPE_WEB => 'web',
|
||||
self::SOURCE_TYPE_API => 'api',
|
||||
self::SOURCE_TYPE_EMAIL => 'email',
|
||||
self::SOURCE_TYPE_WEB => 'web',
|
||||
self::SOURCE_TYPE_API => 'api',
|
||||
];
|
||||
|
||||
/**
|
||||
* Status of the email sent to the customer or user, to whom the thread is assigned.
|
||||
/**
|
||||
* Status of the email sent to the customer or user, to whom the thread is assigned
|
||||
*/
|
||||
const SEND_STATUS_TOSEND = 1;
|
||||
const SEND_STATUS_SENT = 2;
|
||||
@ -139,7 +145,7 @@ class Thread extends Model
|
||||
const SEND_STATUS_DELIVERY_ERROR = 4;
|
||||
|
||||
/**
|
||||
* The user assigned to this thread (assignedTo).
|
||||
* The user assigned to this thread (assignedTo)
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
@ -147,7 +153,7 @@ class Thread extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the thread customer.
|
||||
* Get the thread customer
|
||||
*/
|
||||
public function customer()
|
||||
{
|
||||
@ -155,7 +161,7 @@ class Thread extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation.
|
||||
* Get conversation
|
||||
*/
|
||||
public function conversation()
|
||||
{
|
||||
@ -163,8 +169,23 @@ class Thread extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sanitized body HTML.
|
||||
*
|
||||
* Get user who created the thread.
|
||||
*/
|
||||
public function created_by_user()
|
||||
{
|
||||
return $this->belongsTo('App\User');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customer who created the thread.
|
||||
*/
|
||||
public function created_by_customer()
|
||||
{
|
||||
return $this->belongsTo('App\Customer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sanitized body HTML
|
||||
* @return string
|
||||
*/
|
||||
public function getCleanBody()
|
||||
@ -174,7 +195,7 @@ class Thread extends Model
|
||||
|
||||
/**
|
||||
* Get thread recipients.
|
||||
*
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTos()
|
||||
@ -188,7 +209,7 @@ class Thread extends Model
|
||||
|
||||
/**
|
||||
* Get thread CC recipients.
|
||||
*
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCcs()
|
||||
@ -202,7 +223,7 @@ class Thread extends Model
|
||||
|
||||
/**
|
||||
* Get thread BCC recipients.
|
||||
*
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getBccs()
|
||||
@ -215,33 +236,42 @@ class Thread extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status name. Made as a function to allow status names translation.
|
||||
*
|
||||
* @param int $status
|
||||
*
|
||||
* Get thread's status name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getStatusName($status)
|
||||
public function getStatusName()
|
||||
{
|
||||
return self::statusCodeToName($this->status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status name. Made as a function to allow status names translation.
|
||||
*
|
||||
* @param integer $status
|
||||
* @return string
|
||||
*/
|
||||
public static function statusCodeToName($status)
|
||||
{
|
||||
switch ($status) {
|
||||
case self::STATUS_ACTIVE:
|
||||
return __('Active');
|
||||
return __("Active");
|
||||
break;
|
||||
|
||||
case self::STATUS_PENDING:
|
||||
return __('Pending');
|
||||
return __("Pending");
|
||||
break;
|
||||
|
||||
case self::STATUS_CLOSED:
|
||||
return __('Closed');
|
||||
return __("Closed");
|
||||
break;
|
||||
|
||||
case self::STATUS_SPAM:
|
||||
return __('Spam');
|
||||
return __("Spam");
|
||||
break;
|
||||
|
||||
case self::STATUS_NOCHANGE:
|
||||
return __('Not changed');
|
||||
return __("Not changed");
|
||||
break;
|
||||
|
||||
default:
|
||||
|
114
app/User.php
114
app/User.php
@ -3,13 +3,13 @@
|
||||
* User model class.
|
||||
* Class also responsible for dates conversion and representation.
|
||||
*/
|
||||
|
||||
namespace App;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use App\Mailbox;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
@ -19,41 +19,41 @@ class User extends Authenticatable
|
||||
// const UPDATED_AT = 'modified_at';
|
||||
|
||||
/**
|
||||
* Roles.
|
||||
* Roles
|
||||
*/
|
||||
const ROLE_USER = 1;
|
||||
const ROLE_ADMIN = 2;
|
||||
|
||||
public static $roles = [
|
||||
public static $roles = array(
|
||||
self::ROLE_ADMIN => 'admin',
|
||||
self::ROLE_USER => 'user',
|
||||
];
|
||||
self::ROLE_USER => 'user'
|
||||
);
|
||||
|
||||
/**
|
||||
* Types.
|
||||
* Types
|
||||
*/
|
||||
const TYPE_USER = 1;
|
||||
const TYPE_TEAM = 2;
|
||||
|
||||
|
||||
/**
|
||||
* Invite states.
|
||||
* Invite states
|
||||
*/
|
||||
const INVITE_STATE_ACTIVATED = 0;
|
||||
const INVITE_STATE_NOT_INVITED = 1;
|
||||
const INVITE_STATE_SENT = 2;
|
||||
|
||||
/**
|
||||
* Time formats.
|
||||
* Time formats
|
||||
*/
|
||||
const TIME_FORMAT_12 = 1;
|
||||
const TIME_FORMAT_24 = 2;
|
||||
|
||||
|
||||
/**
|
||||
* The attributes that are not mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = ['role'];
|
||||
protected $guarded = ['role'];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for arrays, excluded from the model's JSON form.
|
||||
@ -65,14 +65,13 @@ class User extends Authenticatable
|
||||
];
|
||||
|
||||
/**
|
||||
* Attributes fillable using fill() method.
|
||||
*
|
||||
* Attributes fillable using fill() method
|
||||
* @var [type]
|
||||
*/
|
||||
protected $fillable = ['role', 'first_name', 'last_name', 'email', 'password', 'role', 'timezone', 'photo_url', 'type', 'emails', 'job_title', 'phone', 'time_format', 'enable_kb_shortcuts'];
|
||||
protected $fillable = ['role', 'first_name', 'last_name', 'email', 'password', 'role', 'timezone', 'photo_url', 'type', 'emails', 'job_title', 'phone', 'time_format', 'enable_kb_shortcuts'];
|
||||
|
||||
/**
|
||||
* Get mailboxes to which usre has access.
|
||||
* Get mailboxes to which usre has access
|
||||
*/
|
||||
public function mailboxes()
|
||||
{
|
||||
@ -80,7 +79,7 @@ class User extends Authenticatable
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversations assigned to user.
|
||||
* Get conversations assigned to user
|
||||
*/
|
||||
public function conversations()
|
||||
{
|
||||
@ -88,7 +87,7 @@ class User extends Authenticatable
|
||||
}
|
||||
|
||||
/**
|
||||
* User's folders.
|
||||
* User's folders
|
||||
*/
|
||||
public function folders()
|
||||
{
|
||||
@ -96,8 +95,8 @@ class User extends Authenticatable
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user role.
|
||||
*
|
||||
* Get user role
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRoleName($ucfirst = false)
|
||||
@ -106,32 +105,30 @@ class User extends Authenticatable
|
||||
if ($ucfirst) {
|
||||
$role_name = ucfirst($role_name);
|
||||
}
|
||||
|
||||
return $role_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is admin.
|
||||
*
|
||||
* @return bool
|
||||
* Check if user is admin
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAdmin()
|
||||
{
|
||||
return $this->role == self::ROLE_ADMIN;
|
||||
return ($this->role == self::ROLE_ADMIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user full name.
|
||||
*
|
||||
* Get user full name
|
||||
* @return string
|
||||
*/
|
||||
public function getFullName()
|
||||
{
|
||||
return $this->first_name.' '.$this->last_name;
|
||||
return $this->first_name . ' ' . $this->last_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mailboxes to which user has access.
|
||||
* Get mailboxes to which user has access
|
||||
*/
|
||||
public function mailboxesCanView()
|
||||
{
|
||||
@ -143,22 +140,18 @@ class User extends Authenticatable
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random password for the user.
|
||||
*
|
||||
* @param int $length
|
||||
*
|
||||
* Generate random password for the user
|
||||
* @param integer $length
|
||||
* @return string
|
||||
*/
|
||||
public function generatePassword($length = 8)
|
||||
{
|
||||
$this->password = Hash::make(str_random($length));
|
||||
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL for editing user.
|
||||
*
|
||||
* Get URL for editing user
|
||||
* @return string
|
||||
*/
|
||||
public function urlEdit()
|
||||
@ -168,9 +161,9 @@ class User extends Authenticatable
|
||||
|
||||
/**
|
||||
* Create personal folders for user mailboxes.
|
||||
*
|
||||
* @param int $mailbox_id
|
||||
* @param mixed $users
|
||||
*
|
||||
* @param integer $mailbox_id
|
||||
* @param mixed $users
|
||||
*/
|
||||
public function syncPersonalFolders($mailboxes)
|
||||
{
|
||||
@ -192,7 +185,7 @@ class User extends Authenticatable
|
||||
continue;
|
||||
}
|
||||
foreach (Folder::$personal_types as $type) {
|
||||
$folder = new Folder();
|
||||
$folder = new Folder;
|
||||
$folder->mailbox_id = $mailbox_id;
|
||||
$folder->user_id = $this->id;
|
||||
$folder->type = $type;
|
||||
@ -203,11 +196,10 @@ class User extends Authenticatable
|
||||
|
||||
/**
|
||||
* Format date according to user's timezone and time format.
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @param string $format
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @param string $format
|
||||
* @return string
|
||||
*/
|
||||
public static function dateFormat($date, $format)
|
||||
{
|
||||
@ -215,16 +207,16 @@ class User extends Authenticatable
|
||||
if ($user) {
|
||||
if ($user->time_format == self::TIME_FORMAT_12) {
|
||||
$format = strtr($format, [
|
||||
'H' => 'h',
|
||||
'G' => 'g',
|
||||
':i' => ':ia',
|
||||
'H' => 'h',
|
||||
'G' => 'g',
|
||||
':i' => ':ia',
|
||||
':ia:s' => ':i:sa',
|
||||
]);
|
||||
} else {
|
||||
$format = strtr($format, [
|
||||
'h' => 'H',
|
||||
'g' => 'G',
|
||||
':ia' => ':i',
|
||||
'h' => 'H',
|
||||
'g' => 'G',
|
||||
':ia' => ':i',
|
||||
':i:sa' => ':i:s',
|
||||
]);
|
||||
}
|
||||
@ -238,31 +230,33 @@ class User extends Authenticatable
|
||||
|
||||
/**
|
||||
* Convert date into human readable format.
|
||||
*
|
||||
* @param Carbon $date
|
||||
*
|
||||
*
|
||||
* @param Carbon $date
|
||||
* @return string
|
||||
*/
|
||||
public static function dateDiffForHumans($date)
|
||||
{
|
||||
if (!$date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
if ($user) {
|
||||
$date->setTimezone($user->timezone);
|
||||
}
|
||||
|
||||
if ($date->diffInSeconds(Carbon::now()) <= 60) {
|
||||
return __('Just now');
|
||||
return __("Just now");
|
||||
} elseif ($date->diffInDays(Carbon::now()) > 7) {
|
||||
// Exact date
|
||||
if (Carbon::now()->year == $date->year) {
|
||||
return self::dateFormat($date, 'M j');
|
||||
return User::dateFormat($date, "M j");
|
||||
} else {
|
||||
return self::dateFormat($date, 'M j, Y');
|
||||
return User::dateFormat($date, "M j, Y");
|
||||
}
|
||||
} else {
|
||||
$diff_text = $date->diffForHumans();
|
||||
$diff_text = preg_replace('/minutes?/', 'min', $diff_text);
|
||||
|
||||
$diff_text = preg_replace("/minutes?/", 'min', $diff_text);
|
||||
return $diff_text;
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ use App\Conversation;
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(Conversation::class, function (Faker $faker, $params) {
|
||||
if (!empty($params['created_by'])) {
|
||||
$created_by = $params['created_by'];
|
||||
if (!empty($params['created_by_user_id'])) {
|
||||
$created_by_user_id = $params['created_by_user_id'];
|
||||
} else {
|
||||
// Pick random user
|
||||
$created_by = App\User::inRandomOrder()->first()->id;
|
||||
$created_by_user_id = App\User::inRandomOrder()->first()->id;
|
||||
}
|
||||
$folder_id = null;
|
||||
if (!empty($params['folder_id'])) {
|
||||
@ -26,14 +26,14 @@ $factory->define(Conversation::class, function (Faker $faker, $params) {
|
||||
return [
|
||||
'type' => $faker->randomElement([Conversation::TYPE_EMAIL, Conversation::TYPE_PHONE]),
|
||||
'folder_id' => $folder_id,
|
||||
'state' => $faker->randomElement(array_keys(Conversation::$states)),
|
||||
'state' => Conversation::STATE_PUBLISHED, // $faker->randomElement(array_keys(Conversation::$states)),
|
||||
'subject' => $faker->sentence(7),
|
||||
// todo: cc and bcc must be equal to first (or last?) thread of conversation
|
||||
'cc' => json_encode([$faker->unique()->safeEmail]),
|
||||
'bcc' => json_encode([$faker->unique()->safeEmail]),
|
||||
'preview' => $faker->text(Conversation::PREVIEW_MAXLENGTH),
|
||||
'imported' => true,
|
||||
'created_by' => $created_by,
|
||||
'created_by_user_id' => $created_by_user_id,
|
||||
'source_via' => Conversation::PERSON_CUSTOMER,
|
||||
'source_type' => Conversation::SOURCE_TYPE_EMAIL,
|
||||
];
|
||||
|
@ -34,6 +34,6 @@ $factory->define(Thread::class, function (Faker $faker, $params) {
|
||||
'bcc' => json_encode([$faker->unique()->safeEmail]),
|
||||
'source_via' => Thread::PERSON_CUSTOMER,
|
||||
'source_type' => Thread::SOURCE_TYPE_EMAIL,
|
||||
'created_by' => $customer_id,
|
||||
'created_by_customer_id' => $customer_id,
|
||||
];
|
||||
});
|
||||
|
@ -42,13 +42,14 @@ class CreateConversationsTable extends Migration
|
||||
// Originating source of the conversation - user or customer
|
||||
// ID of the customer or user who created the conversation
|
||||
// createdBy in the API
|
||||
$table->integer('created_by');
|
||||
$table->integer('created_by_user_id')->nullable();
|
||||
$table->integer('created_by_customer_id')->nullable();
|
||||
// source.via - Originating source of the conversation - user or customer
|
||||
$table->unsignedTinyInteger('source_via');
|
||||
// source.type - Originating type of the conversation (email, web, API etc)
|
||||
$table->unsignedTinyInteger('source_type');
|
||||
// closedBy - ID of the user who closed the conversation
|
||||
$table->integer('closed_by_user')->nullable();
|
||||
$table->integer('closed_by_user_id')->nullable();
|
||||
// UTC time when the conversation was closed
|
||||
$table->timestamp('closed_at')->nullable();
|
||||
// UTC time when the last user update occurred
|
||||
|
@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
use App\Thread;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use App\Thread;
|
||||
|
||||
class CreateThreadsTable extends Migration
|
||||
{
|
||||
@ -18,6 +18,7 @@ class CreateThreadsTable extends Migration
|
||||
$table->increments('id');
|
||||
$table->integer('conversation_id');
|
||||
// assignedTo - The user assigned to this thread
|
||||
// used to display user who was assigned to the thread in the conversation
|
||||
$table->integer('user_id')->nullable();
|
||||
$table->unsignedTinyInteger('type');
|
||||
$table->unsignedTinyInteger('status')->default(Thread::STATUS_ACTIVE);
|
||||
@ -25,7 +26,8 @@ class CreateThreadsTable extends Migration
|
||||
// Describes an optional action associated with the line item
|
||||
$table->unsignedTinyInteger('action_type')->nullable();
|
||||
$table->string('action_text', 255)->nullable();
|
||||
$table->text('body', 65535);
|
||||
// lineitems do not have body
|
||||
$table->text('body', 65535)->nullable();
|
||||
// Original body after thread text is changed
|
||||
$table->text('body_original', 65535)->nullable();
|
||||
$table->text('to')->nullable(); // JSON
|
||||
@ -38,9 +40,10 @@ class CreateThreadsTable extends Migration
|
||||
// customer - If thread type is message, this is the customer associated with the thread.
|
||||
// If thread type is customer, this is the the customer who initiated the thread.
|
||||
$table->integer('customer_id');
|
||||
// Who created this thread. The type property will specify whether it was created by a user or a customer.
|
||||
// Who created this thread. The source_via property will specify whether it was created by a user or a customer.
|
||||
// See source_via
|
||||
$table->integer('created_by');
|
||||
$table->integer('created_by_user_id')->nullable();
|
||||
$table->integer('created_by_customer_id')->nullable();
|
||||
// ID of Saved reply that was used to create this Thread (savedReplyId)
|
||||
$table->integer('saved_reply_id')->nullable();
|
||||
// Status of the email sent to customer or user, to whom the thread is assigned
|
||||
|
@ -12,7 +12,10 @@ class DatabaseSeeder extends Seeder
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
//$this->call(MailboxesTableSeeder::class);
|
||||
// Create users
|
||||
factory(App\User::class, 3)->create();
|
||||
|
||||
// Create mailboxes, conversations, etc
|
||||
factory(App\Mailbox::class, 3)->create()->each(function ($m) {
|
||||
$user = factory(App\User::class)->create();
|
||||
$m->users()->save($user);
|
||||
@ -22,7 +25,7 @@ class DatabaseSeeder extends Seeder
|
||||
|
||||
$customer->emails()->save(factory(App\Email::class)->make());
|
||||
|
||||
$conversation = factory(App\Conversation::class)->create(['created_by' => $user->id, 'mailbox_id' => $m->id, 'customer_id' => $customer->id, 'user_id' => $user->id, 'status' => array_rand([Conversation::STATUS_ACTIVE => 1, Conversation::STATUS_PENDING => 1])]);
|
||||
$conversation = factory(App\Conversation::class)->create(['created_by_user_id' => $user->id, 'mailbox_id' => $m->id, 'customer_id' => $customer->id, 'user_id' => $user->id, 'status' => array_rand([Conversation::STATUS_ACTIVE => 1, Conversation::STATUS_PENDING => 1])]);
|
||||
|
||||
$thread = factory(App\Thread::class)->make(['customer_id' => $customer->id, 'to' => $customer->getMainEmail(), 'conversation_id' => $conversation->id]);
|
||||
$conversation->threads()->save($thread);
|
||||
|
20
public/css/style.css
vendored
20
public/css/style.css
vendored
@ -632,7 +632,7 @@ a h4 {
|
||||
text-decoration: none;
|
||||
}
|
||||
.customer-data {
|
||||
padding: 0px 20px 0 18px;
|
||||
padding: 0px 26px 0 18px;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
@ -983,6 +983,7 @@ table.table-conversations td.conv-attachment {
|
||||
background-color: #f1f3f5;
|
||||
width: 280px;
|
||||
height: 100%;
|
||||
z-index: 9;
|
||||
}
|
||||
#conv-toolbar {
|
||||
border-bottom: 1px solid #e3e8eb;
|
||||
@ -1143,6 +1144,10 @@ table.table-conversations td.conv-attachment {
|
||||
}
|
||||
.thread {
|
||||
position: relative;
|
||||
border-bottom: 1px solid #e3e8eb;
|
||||
}
|
||||
.thread:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.thread-header {
|
||||
overflow: hidden;
|
||||
@ -1201,6 +1206,19 @@ table.table-conversations td.conv-attachment {
|
||||
right: 13px;
|
||||
color: #a5b2bd;
|
||||
}
|
||||
.thread-lineitem {
|
||||
background: #f9fafa;
|
||||
overflow: auto;
|
||||
}
|
||||
.thread-lineitem .thread-message {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 9px;
|
||||
color: #a5b2bd;
|
||||
font-size: 12px;
|
||||
}
|
||||
.thread-lineitem .thread-header {
|
||||
min-height: auto;
|
||||
}
|
||||
@media (max-width:1100px) {
|
||||
#conv-layout-header,
|
||||
#conv-layout-main,
|
||||
|
5
resources/views/conversations/thread_by.blade.php
Normal file
5
resources/views/conversations/thread_by.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
@if ($thread->created_by_user->id == Auth::user()->id)
|
||||
{{ __("you") }}
|
||||
@else
|
||||
{{ $thread->created_by_user->getFullName() }}
|
||||
@endif
|
@ -128,9 +128,14 @@
|
||||
<div class="thread-message">
|
||||
<div class="thread-header">
|
||||
<div class="thread-title">
|
||||
@include('conversations/thread_by')
|
||||
@if ($thread->action_type == App\Thread::ACTION_TYPE_STATUS_CHANGED)
|
||||
{{ __("marked as") }} {{ $thread->getStatusName() }}
|
||||
@elseif ($thread->action_type == App\Thread::ACTION_TYPE_USER_CHANGED)
|
||||
@endif
|
||||
</div>
|
||||
<div class="thread-info">
|
||||
<span class="thread-date"></span>
|
||||
<span class="thread-date">{{ App\User::dateDiffForHumans($thread->created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -148,11 +153,7 @@
|
||||
@if ($thread->type == App\Thread::TYPE_CUSTOMER)
|
||||
{{ $thread->customer->getFullName() }}
|
||||
@else
|
||||
@if ($thread->user->id == Auth::user()->id)
|
||||
{{ __("you") }}
|
||||
@else
|
||||
{{ $thread->user->getFullName() }}
|
||||
@endif
|
||||
@include('conversations/thread_by')
|
||||
@endif
|
||||
</strong>
|
||||
@if ($loop->index == 0)
|
||||
@ -208,7 +209,7 @@
|
||||
@endif
|
||||
@endif
|
||||
@if (!empty($show_status))
|
||||
{{ App\Thread::getStatusName($thread->status) }}
|
||||
{{ $thread->getStatusName() }}
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user