M3 Integration fixes
This commit is contained in:
Regular → Executable
+97
-68
@@ -10,6 +10,7 @@ use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
use Modules\Helpdesk\Exceptions\TicketPersistenceException;
|
||||
use Modules\Helpdesk\Models\Comment;
|
||||
use Modules\Helpdesk\Models\KanbanBoard;
|
||||
use Modules\Helpdesk\Models\Status;
|
||||
@@ -45,7 +46,7 @@ final class TicketService
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Unable to create a unique Helpdesk ticket reference.');
|
||||
throw new TicketPersistenceException('Unable to create a unique Helpdesk ticket reference.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,32 +57,8 @@ final class TicketService
|
||||
*/
|
||||
private function createInsideTransaction(User $user, array $data, array $files = []): Ticket
|
||||
{
|
||||
$defaultStatusSlug = (string) config('helpdesk.default_status_slug', 'new');
|
||||
$status = Schema::hasTable('helpdesk_statuses') && Schema::hasColumn('helpdesk_statuses', 'is_default')
|
||||
? Status::query()->where('is_default', true)->orderBy('sort_order')->first()
|
||||
: null;
|
||||
|
||||
if (! $status) {
|
||||
$status = Status::query()->where('slug', $defaultStatusSlug)->first();
|
||||
}
|
||||
|
||||
if (! $status) {
|
||||
$status = Status::query()->create([
|
||||
'name' => 'New',
|
||||
'slug' => $defaultStatusSlug,
|
||||
'color' => 'sky',
|
||||
'sort_order' => 1,
|
||||
'is_closed' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
$boardId = $data['board_id'] ?? null;
|
||||
if (blank($boardId) && Schema::hasTable('helpdesk_kanban_boards')) {
|
||||
$boardId = KanbanBoard::query()
|
||||
->where('is_default', true)
|
||||
->orderBy('sort_order')
|
||||
->value('id') ?: KanbanBoard::query()->orderBy('sort_order')->value('id');
|
||||
}
|
||||
$status = $this->resolveInitialStatus();
|
||||
$boardId = $this->resolveBoardId($data['board_id'] ?? null);
|
||||
|
||||
/** @var Ticket $ticket */
|
||||
$ticket = Ticket::query()->create([
|
||||
@@ -102,9 +79,7 @@ final class TicketService
|
||||
'last_activity_at' => now(),
|
||||
]);
|
||||
|
||||
if (! empty($data['tag_ids']) && is_array($data['tag_ids'])) {
|
||||
$ticket->tags()->sync(array_values(array_unique(array_map('intval', $data['tag_ids']))));
|
||||
}
|
||||
$this->syncTags($ticket, $data['tag_ids'] ?? null);
|
||||
|
||||
$ticket = $this->automation->apply($ticket, $data);
|
||||
$ticket = $this->sla->applyPolicy($ticket);
|
||||
@@ -117,6 +92,43 @@ final class TicketService
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
private function resolveInitialStatus(): Status
|
||||
{
|
||||
$defaultStatusSlug = (string) config('helpdesk.default_status_slug', 'new');
|
||||
$status = Schema::hasTable('helpdesk_statuses') && Schema::hasColumn('helpdesk_statuses', 'is_default')
|
||||
? Status::query()->where('is_default', true)->orderBy('sort_order')->first()
|
||||
: null;
|
||||
|
||||
$status ??= Status::query()->where('slug', $defaultStatusSlug)->first();
|
||||
|
||||
return $status ?? Status::query()->create([
|
||||
'name' => 'New',
|
||||
'slug' => $defaultStatusSlug,
|
||||
'color' => 'sky',
|
||||
'sort_order' => 1,
|
||||
'is_closed' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
private function resolveBoardId(mixed $boardId): mixed
|
||||
{
|
||||
if (! blank($boardId) || ! Schema::hasTable('helpdesk_kanban_boards')) {
|
||||
return $boardId;
|
||||
}
|
||||
|
||||
return KanbanBoard::query()->where('is_default', true)->orderBy('sort_order')->value('id')
|
||||
?: KanbanBoard::query()->orderBy('sort_order')->value('id');
|
||||
}
|
||||
|
||||
private function syncTags(Ticket $ticket, mixed $tagIds): void
|
||||
{
|
||||
if (! is_array($tagIds) || $tagIds === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ticket->tags()->sync(array_values(array_unique(array_map('intval', $tagIds))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a database error was caused by the ticket reference unique index.
|
||||
*/
|
||||
@@ -137,52 +149,69 @@ final class TicketService
|
||||
public function comment(Ticket $ticket, User $user, string $body, bool $internal = false, array $files = [], ?int $parentId = null, ?string $subject = null): Comment
|
||||
{
|
||||
return DB::transaction(function () use ($ticket, $user, $body, $internal, $files, $parentId, $subject): Comment {
|
||||
$parent = $parentId === null
|
||||
? null
|
||||
: Comment::query()
|
||||
->where('ticket_id', $ticket->id)
|
||||
->findOrFail($parentId);
|
||||
|
||||
$guid = (string) Str::uuid();
|
||||
$unid = strtoupper(hash('sha256', $guid . '|' . $ticket->id . '|' . microtime(true)));
|
||||
$parentUnid = $parent?->unid;
|
||||
$rootUnid = $parent?->root_unid ?: $parentUnid ?: $unid;
|
||||
$plainTextBody = trim(preg_replace('/\s+/', ' ', strip_tags($body)) ?: '');
|
||||
|
||||
$payload = [
|
||||
'parent_id' => $parentId,
|
||||
'user_id' => $user->id,
|
||||
'body' => $body,
|
||||
'body_text' => $plainTextBody,
|
||||
'is_internal' => $internal,
|
||||
'guid' => $guid,
|
||||
'unid' => $unid,
|
||||
'parent_unid' => $parentUnid,
|
||||
'root_unid' => $rootUnid,
|
||||
'originator_id' => $unid . ':1',
|
||||
'sync_sequence' => 1,
|
||||
'sync_hash' => hash('sha256', $body),
|
||||
'source_system' => 'wms-helpdesk',
|
||||
];
|
||||
|
||||
if (Schema::hasColumn('helpdesk_comments', 'subject')) {
|
||||
$payload['subject'] = trim((string) ($subject ?: ($parentId === null ? 'Response' : 'RE: Response')));
|
||||
}
|
||||
$parent = $this->resolveParentComment($ticket, $parentId);
|
||||
$payload = $this->commentPayload($ticket, $user, $body, $internal, $parent, $parentId, $subject);
|
||||
|
||||
/** @var Comment $comment */
|
||||
$comment = $ticket->comments()->create($payload);
|
||||
|
||||
$ticket->forceFill(['last_activity_at' => now()])->save();
|
||||
if (! $internal) {
|
||||
$this->sla->recordPublicResponse($ticket, $user);
|
||||
}
|
||||
$this->attachments->storeMany($ticket, $files, (int) $user->id, $comment);
|
||||
$this->workflow->dispatch($ticket->fresh(['status', 'priority', 'category', 'project', 'team']) ?? $ticket, $parentId === null ? 'ticket.commented' : 'ticket.reply_created', $user);
|
||||
$this->afterCommentCreated($ticket, $user, $comment, $files, $internal, $parentId);
|
||||
|
||||
return $comment->fresh(['user', 'attachments']) ?? $comment;
|
||||
});
|
||||
}
|
||||
|
||||
private function resolveParentComment(Ticket $ticket, ?int $parentId): ?Comment
|
||||
{
|
||||
return $parentId === null
|
||||
? null
|
||||
: Comment::query()->where('ticket_id', $ticket->id)->findOrFail($parentId);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function commentPayload(Ticket $ticket, User $user, string $body, bool $internal, ?Comment $parent, ?int $parentId, ?string $subject): array
|
||||
{
|
||||
$guid = (string) Str::uuid();
|
||||
$unid = strtoupper(hash('sha256', $guid . '|' . $ticket->id . '|' . microtime(true)));
|
||||
$parentUnid = $parent?->unid;
|
||||
$payload = [
|
||||
'parent_id' => $parentId,
|
||||
'user_id' => $user->id,
|
||||
'body' => $body,
|
||||
'body_text' => trim(preg_replace('/\s+/', ' ', strip_tags($body)) ?: ''),
|
||||
'is_internal' => $internal,
|
||||
'guid' => $guid,
|
||||
'unid' => $unid,
|
||||
'parent_unid' => $parentUnid,
|
||||
'root_unid' => $parent?->root_unid ?: $parentUnid ?: $unid,
|
||||
'originator_id' => $unid . ':1',
|
||||
'sync_sequence' => 1,
|
||||
'sync_hash' => hash('sha256', $body),
|
||||
'source_system' => 'wms-helpdesk',
|
||||
];
|
||||
|
||||
if (Schema::hasColumn('helpdesk_comments', 'subject')) {
|
||||
$defaultSubject = $parentId === null ? 'Response' : 'RE: Response';
|
||||
$payload['subject'] = trim((string) ($subject ?: $defaultSubject));
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/** @param array<int, UploadedFile> $files */
|
||||
private function afterCommentCreated(Ticket $ticket, User $user, Comment $comment, array $files, bool $internal, ?int $parentId): void
|
||||
{
|
||||
$ticket->forceFill(['last_activity_at' => now()])->save();
|
||||
|
||||
if (! $internal) {
|
||||
$this->sla->recordPublicResponse($ticket, $user);
|
||||
}
|
||||
|
||||
$this->attachments->storeMany($ticket, $files, (int) $user->id, $comment);
|
||||
$event = $parentId === null ? 'ticket.commented' : 'ticket.reply_created';
|
||||
$freshTicket = $ticket->fresh(['status', 'priority', 'category', 'project', 'team']) ?? $ticket;
|
||||
$this->workflow->dispatch($freshTicket, $event, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a ticket.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user