| @@ -15,10 +15,8 @@ | ||
| 15 | 15 | protected $table = 'fs_tickets'; |
| 16 | 16 | |
| 17 | 17 | protected $dates = ['waiting_since']; |
| 18 | 18 | |
| 19 | - protected $appends = ['display_ticket_number']; | |
| 20 | - | |
| 21 | 19 | /** |
| 22 | 20 | * The attributes that are mass assignable. |
| 23 | 21 | * |
| 24 | 22 | * @var array |
| @@ -46,11 +44,9 @@ | ||
| 46 | 44 | 'first_response_time', |
| 47 | 45 | 'total_close_time', |
| 48 | 46 | 'resolved_at', |
| 49 | 47 | 'closed_by', |
| 50 | - 'created_by', | |
| 51 | - 'serial_number', | |
| 52 | - 'ticket_number' | |
| 48 | + 'created_by' | |
| 53 | 49 | ]; |
| 54 | 50 | |
| 55 | 51 | public static function boot() |
| 56 | 52 | { |
| @@ -70,14 +66,8 @@ | ||
| 70 | 66 | $model->waiting_since = current_time('mysql'); |
| 71 | 67 | |
| 72 | 68 | }); |
| 73 | 69 | |
| 74 | - static::created(function ($model) { | |
| 75 | - if (empty($model->serial_number) || empty($model->ticket_number)) { | |
| 76 | - $model->assignTicketNumber(); | |
| 77 | - } | |
| 78 | - }); | |
| 79 | - | |
| 80 | 70 | static::deleting(function ($model) { |
| 81 | 71 | //Delete the ticket meta |
| 82 | 72 | Meta::where('object_type', 'ticket_meta')->where('object_id', $model->id)->delete(); |
| 83 | 73 | //Delete all cc info for the ticket |
| @@ -83,10 +73,8 @@ | ||
| 83 | 73 | //Delete all cc info for the ticket |
| 84 | 74 | Meta::where('object_type', 'ticket')->where('object_id', $model->id)->delete(); |
| 85 | 75 | //Delete draft info |
| 86 | 76 | Meta::where('object_type', '_fs_auto_draft')->where('object_id', $model->id)->delete(); |
| 87 | - //Delete internal notifications and notification recipient rows for the ticket | |
| 88 | - Notification::deleteByTicketId($model->id); | |
| 89 | 77 | //delete the responses first (their attachments are cleaned up by Conversation::deleting) |
| 90 | 78 | Conversation::deleteAll($model->id); |
| 91 | 79 | // Delete ticket-level attachments (conversation_id IS NULL) and remove the ticket upload directory |
| 92 | 80 | $class = __NAMESPACE__ . '\Attachment'; |
| @@ -103,11 +91,9 @@ | ||
| 103 | 91 | protected $searchable = [ |
| 104 | 92 | 'content', |
| 105 | 93 | 'title', |
| 106 | 94 | 'slug', |
| 107 | - 'id', | |
| 108 | - 'serial_number', | |
| 109 | - 'ticket_number' | |
| 95 | + 'id' | |
| 110 | 96 | ]; |
| 111 | 97 | |
| 112 | 98 | /** |
| 113 | 99 | * Local scope to filter tickets by search/query string |
| @@ -219,41 +205,8 @@ | ||
| 219 | 205 | return $query; |
| 220 | 206 | } |
| 221 | 207 | |
| 222 | 208 | /** |
| 223 | - * Who replied last on this ticket, derived from the already-loaded | |
| 224 | - * last_agent_response / last_customer_response timestamp columns. | |
| 225 | - * | |
| 226 | - * Returns 'agent', 'customer', or null. This is the per-ticket value behind | |
| 227 | - * the `waiting_for_reply` filter and mirrors the timestamp comparison in | |
| 228 | - * scopeWaitingOnly() (which lives in SQL, so it can't share this PHP code). | |
| 229 | - * | |
| 230 | - * Note: boot() seeds last_customer_response on creation, so a brand-new | |
| 231 | - * ticket with no agent reply correctly resolves to 'customer' (awaiting an | |
| 232 | - * agent). null is reserved for the rare case where neither timestamp is set. | |
| 233 | - * | |
| 234 | - * @return string|null | |
| 235 | - */ | |
| 236 | - public function getLastReplyByAttribute() | |
| 237 | - { | |
| 238 | - $agentAt = $this->last_agent_response; | |
| 239 | - $customerAt = $this->last_customer_response; | |
| 240 | - | |
| 241 | - if (!$agentAt && !$customerAt) { | |
| 242 | - return null; | |
| 243 | - } | |
| 244 | - if (!$agentAt) { | |
| 245 | - return 'customer'; | |
| 246 | - } | |
| 247 | - if (!$customerAt) { | |
| 248 | - return 'agent'; | |
| 249 | - } | |
| 250 | - | |
| 251 | - // Tie (same second) resolves to 'customer' — the waiting bias used by scopeWaitingOnly. | |
| 252 | - return strtotime($customerAt) >= strtotime($agentAt) ? 'customer' : 'agent'; | |
| 253 | - } | |
| 254 | - | |
| 255 | - /** | |
| 256 | 209 | * Local scope to filter tickets by not response by agent |
| 257 | 210 | * @param $query |
| 258 | 211 | * @return mixed |
| 259 | 212 | */ |
| @@ -800,76 +753,8 @@ | ||
| 800 | 753 | // Delete the ticket |
| 801 | 754 | $this->delete(); |
| 802 | 755 | } |
| 803 | 756 | |
| 804 | - public static function getNextSerialNumber() | |
| 805 | - { | |
| 806 | - $businessSettings = Helper::getOption('global_business_settings', []); | |
| 807 | - $minNumber = (int) ($businessSettings['min_serial_number'] ?? 1); | |
| 808 | - $minNumber = (int) apply_filters('fluent_support/min_serial_number', $minNumber); | |
| 809 | - | |
| 810 | - try { | |
| 811 | - $lastTicketNumber = self::query()->max('serial_number'); | |
| 812 | - } catch (\Exception $e) { | |
| 813 | - $lastTicketNumber = null; | |
| 814 | - } | |
| 815 | - | |
| 816 | - $nextNumber = ((int) $lastTicketNumber) + 1; | |
| 817 | - | |
| 818 | - return max($nextNumber, $minNumber); | |
| 819 | - } | |
| 820 | - | |
| 821 | - public static function isMinimumSerialNumberEnabled() | |
| 822 | - { | |
| 823 | - $businessSettings = Helper::getOption('global_business_settings', []); | |
| 824 | - return ($businessSettings['enable_min_serial_number'] ?? 'no') === 'yes'; | |
| 825 | - } | |
| 826 | - | |
| 827 | - public static function getTicketPrefix($ticket = null) | |
| 828 | - { | |
| 829 | - $businessSettings = Helper::getOption('global_business_settings', []); | |
| 830 | - $prefix = self::isMinimumSerialNumberEnabled() ? trim((string) ($businessSettings['ticket_prefix'] ?? '')) : ''; | |
| 831 | - | |
| 832 | - $productId = $ticket ? $ticket->product_id : null; | |
| 833 | - | |
| 834 | - return apply_filters('fluent_support/ticket_prefix', $prefix, $ticket, $productId); | |
| 835 | - } | |
| 836 | - | |
| 837 | - public function getDisplayTicketNumberAttribute() | |
| 838 | - { | |
| 839 | - return $this->ticket_number ?: ($this->serial_number ?: $this->id); | |
| 840 | - } | |
| 841 | - | |
| 842 | - public function scopeWherePublicIdentifier($query, $identifier) | |
| 843 | - { | |
| 844 | - return $query->where('serial_number', $identifier); | |
| 845 | - } | |
| 846 | - | |
| 847 | - protected function assignTicketNumber() | |
| 848 | - { | |
| 849 | - for ($attempt = 0; $attempt < 5; $attempt++) { | |
| 850 | - $nextNumber = $this->serial_number ?: (self::isMinimumSerialNumberEnabled() ? self::getNextSerialNumber() : $this->id); | |
| 851 | - $ticketNumber = $this->ticket_number ?: (self::getTicketPrefix($this) . $nextNumber); | |
| 852 | - | |
| 853 | - try { | |
| 854 | - self::where('id', $this->id)->update([ | |
| 855 | - 'serial_number' => $nextNumber, | |
| 856 | - 'ticket_number' => $ticketNumber | |
| 857 | - ]); | |
| 858 | - $this->serial_number = $nextNumber; | |
| 859 | - $this->ticket_number = $ticketNumber; | |
| 860 | - | |
| 861 | - return $nextNumber; | |
| 862 | - } catch (\Exception $e) { | |
| 863 | - if (stripos($e->getMessage(), 'duplicate') === false) { | |
| 864 | - throw $e; | |
| 865 | - } | |
| 866 | - } | |
| 867 | - } | |
| 868 | - | |
| 869 | - throw new \RuntimeException('Could not allocate a unique ticket number.'); | |
| 870 | - } | |
| 871 | - | |
| 872 | 757 | public static function slugify($title) |
| 873 | 758 | { |
| 874 | 759 | $slug = sanitize_title($title, 'support-ticket-' . time(), 'display'); |
| 875 | 760 | if (Ticket::where('slug', $slug)->first()) { |
| @@ -1216,4 +1101,5 @@ | ||
| 1216 | 1101 | |
| 1217 | 1102 | } |
| 1218 | 1103 | |
| 1219 | 1104 | } |
| 1105 | + | |