add ticket tool for AGI

This commit is contained in:
Hesabix 2025-07-26 11:49:32 -07:00
parent aaeb3cf31e
commit 29cc20207f
6 changed files with 438 additions and 120 deletions

View file

@ -0,0 +1,62 @@
<?php
namespace App\AiTool;
use App\Cog\TicketService as CogTicketService;
use Symfony\Component\Security\Core\User\UserInterface;
class TicketService
{
public function __construct(
private readonly CogTicketService $cogTicketService
) {
}
/**
* دریافت لیست تیکت‌های کاربر
*/
public function getUserTickets(UserInterface $user): array
{
try {
return $this->cogTicketService->getUserTickets($user);
} catch (\Exception $e) {
return [
'error' => 'خطا در دریافت لیست تیکت‌ها: ' . $e->getMessage()
];
}
}
/**
* ایجاد یا به‌روزرسانی تیکت
*/
public function createOrUpdateTicket(array $params, array $files, UserInterface $user, string $id = ''): array
{
try {
return $this->cogTicketService->createOrUpdateTicket($params, $files, $user, $id);
} catch (\Exception $e) {
return [
'error' => 'خطا در ایجاد/به‌روزرسانی تیکت: ' . $e->getMessage()
];
}
}
/**
* دریافت جزئیات تیکت و پاسخ‌های آن
*/
public function getTicketDetails(string $id, UserInterface $user): array
{
if (!$id) {
return [
'error' => 'شناسه تیکت الزامی است'
];
}
try {
return $this->cogTicketService->getTicketDetails($id, $user);
} catch (\Exception $e) {
return [
'error' => 'خطا در دریافت جزئیات تیکت: ' . $e->getMessage()
];
}
}
}

View file

@ -0,0 +1,193 @@
<?php
namespace App\Cog;
use App\Entity\Business;
use App\Entity\Support;
use App\Service\Explore;
use App\Service\Jdate;
use App\Service\registryMGR;
use App\Service\SMS;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
class TicketService
{
private const ERROR_TICKET_NOT_FOUND = ['error' => 1, 'message' => 'تیکت یافت نشد.'];
private const ERROR_INVALID_PARAMS = ['error' => 999, 'message' => 'تمام موارد لازم را وارد کنید.'];
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly Explore $explore,
private readonly Jdate $jdate,
private readonly registryMGR $registryMGR,
private readonly SMS $sms,
private readonly string $uploadDirectory
) {
}
/**
* Get list of support tickets for a user
*/
public function getUserTickets(UserInterface $user): array
{
$items = $this->entityManager->getRepository(Support::class)->findBy(
['submitter' => $user, 'main' => 0],
['id' => 'DESC']
);
return array_map(function ($item) use ($user) {
return $this->explore->ExploreSupportTicket($item, $user);
}, $items);
}
/**
* Create or update a support ticket
*/
public function createOrUpdateTicket(array $params, array $files, UserInterface $user, string $id = ''): array
{
if ($id === '') {
return $this->createNewTicket($params, $files, $user);
}
return $this->replyToTicket($params, $files, $user, $id);
}
private function createNewTicket(array $params, array $files, UserInterface $user): array
{
if (!isset($params['title'], $params['body'])) {
return self::ERROR_INVALID_PARAMS;
}
$item = new Support();
$item->setBody($params['body'])
->setTitle($params['title'])
->setDateSubmit(time())
->setSubmitter($user)
->setMain(0)
->setCode($this->generateRandomString(8))
->setState('در حال پیگیری');
// چک کردن مالکیت کسب‌وکار
$this->handleBusinessOwnership($item, $params['bid'] ?? null, $user);
$this->entityManager->persist($item);
$this->entityManager->flush();
$fileName = $this->handleFileUpload($files, $item->getId());
if ($fileName) {
$item->setFileName($fileName);
$this->entityManager->persist($item);
$this->entityManager->flush();
}
$this->sms->send([$item->getId()], $this->registryMGR->get('sms', 'ticketRec'), $this->registryMGR->get('ticket', 'managerMobile'));
return [
'error' => 0,
'message' => 'ok',
'url' => $item->getId(),
'files' => $fileName
];
}
private function replyToTicket(array $params, array $files, UserInterface $user, string $id): array
{
if (!isset($params['body'])) {
return self::ERROR_INVALID_PARAMS;
}
$upper = $this->getTicket($id);
if (!$upper) {
return self::ERROR_TICKET_NOT_FOUND;
}
$item = new Support();
$item->setMain($upper->getId())
->setBody($params['body'])
->setTitle($upper->getTitle())
->setDateSubmit(time())
->setSubmitter($user)
->setState('در حال پیگیری');
$this->entityManager->persist($item);
$this->entityManager->flush();
$fileName = $this->handleFileUpload($files, $item->getId());
if ($fileName) {
$item->setFileName($fileName);
}
$this->entityManager->persist($item);
$upper->setState('در حال پیگیری');
$this->entityManager->persist($upper);
$this->entityManager->flush();
$this->sms->send([$item->getId()], $this->registryMGR->get('sms', 'ticketRec'), $this->registryMGR->get('ticket', 'managerMobile'));
return [
'error' => 0,
'message' => 'ok',
'url' => $item->getId(),
'files' => $fileName
];
}
private function handleBusinessOwnership(Support $support, ?string $businessId, UserInterface $user): void
{
if ($businessId) {
$business = $this->entityManager->getRepository(Business::class)->find($businessId);
if ($business && $business->getOwner() === $user) {
$support->setBid($business);
return;
}
}
$support->setBid(null);
}
private function getTicket(string $id): ?Support
{
return $this->entityManager->getRepository(Support::class)->find($id);
}
private function generateRandomString(int $length = 32): string
{
return substr(str_shuffle(str_repeat('23456789ABCDEFGHJKLMNPQRSTUVWXYZ', ceil($length / 32))), 1, $length);
}
private function handleFileUpload(array $files, int $ticketId): ?string
{
if (!file_exists($this->uploadDirectory)) {
mkdir($this->uploadDirectory, 0777, true);
}
if (!empty($files)) {
$file = $files[0];
$extension = $file->getClientOriginalExtension();
$fileName = $ticketId . '.' . $extension;
$file->move($this->uploadDirectory, $fileName);
return $fileName;
}
return null;
}
/**
* Get ticket details with its replies
*/
public function getTicketDetails(string $id, UserInterface $user): array
{
$ticket = $this->entityManager->getRepository(Support::class)->find($id);
if (!$ticket || $ticket->getSubmitter() !== $user) {
throw new AccessDeniedException('شما اجازه دسترسی به این تیکت را ندارید.');
}
$replies = $this->entityManager->getRepository(Support::class)->findBy(['main' => $ticket->getId()]);
$repliesArray = array_map(fn($reply) => $this->explore->ExploreSupportTicket($reply, $user), $replies);
return [
'item' => $this->explore->ExploreSupportTicket($ticket, $user),
'replays' => $repliesArray
];
}
}

View file

@ -248,139 +248,28 @@ class SupportController extends AbstractController
} }
#[Route('/api/support/list', name: 'app_support_list')] #[Route('/api/support/list', name: 'app_support_list')]
public function app_support_list(Jdate $jdate, EntityManagerInterface $entityManager, Explore $explore): JsonResponse public function app_support_list(TicketService $ticketService): JsonResponse
{ {
$items = $entityManager->getRepository(Support::class)->findBy( return $this->json($ticketService->getUserTickets($this->getUser()));
['submitter' => $this->getUser(), 'main' => 0],
['id' => 'DESC']
);
// استفاده از Explore برای تبدیل اشیاء به آرایه
$serializedItems = array_map(function ($item) use ($explore, $jdate) {
return $explore->ExploreSupportTicket($item, $this->getUser());
}, $items);
return $this->json($serializedItems);
} }
#[Route('/api/support/mod/{id}', name: 'app_support_mod')] #[Route('/api/support/mod/{id}', name: 'app_support_mod')]
public function app_support_mod( public function app_support_mod(
registryMGR $registryMGR, TicketService $ticketService,
SMS $SMS,
Request $request, Request $request,
EntityManagerInterface $entityManager,
string $id = '' string $id = ''
): JsonResponse { ): JsonResponse {
$params = $request->getPayload()->all(); $params = $request->getPayload()->all();
$uploadDirectory = $this->getParameter('SupportFilesDir'); $files = $request->files->get('files') ?? [];
if (!file_exists($uploadDirectory)) {
mkdir($uploadDirectory, 0777, true); return $this->json($ticketService->createOrUpdateTicket($params, $files, $this->getUser(), $id));
}
if ($id === '') {
if (!isset($params['title'], $params['body'])) {
return $this->json(self::ERROR_INVALID_PARAMS);
}
$item = new Support();
$item->setBody($params['body'])
->setTitle($params['title'])
->setDateSubmit(time())
->setSubmitter($this->getUser())
->setMain(0)
->setCode($this->randomString(8))
->setState('در حال پیگیری');
// چک کردن مالکیت کسب‌وکار
$bid = $params['bid'] ?? null;
if ($bid) {
$business = $entityManager->getRepository(Business::class)->find($bid);
if ($business && $business->getOwner() === $this->getUser()) {
$item->setBid($business); // فقط در صورتی که کاربر مالک باشد
} else {
$item->setBid(null); // اگر مالک نباشد، bid خالی می‌ماند
}
} else {
$item->setBid(null); // اگر bid ارسال نشده باشد
}
$entityManager->persist($item);
$entityManager->flush();
$fileName = $this->handleFileUpload($request, $uploadDirectory, $item->getId());
if ($fileName) {
$item->setFileName($fileName);
}
$entityManager->persist($item);
$entityManager->flush();
$SMS->send([$item->getId()], $registryMGR->get('sms', 'ticketRec'), $registryMGR->get('ticket', 'managerMobile'));
return $this->json([
'error' => 0,
'message' => 'ok',
'url' => $item->getId(),
'files' => $fileName
]);
}
if (!isset($params['body'])) {
return $this->json(self::ERROR_INVALID_PARAMS);
}
$upper = $this->getTicket($entityManager, $id);
if (!$upper) {
return $this->json(self::ERROR_TICKET_NOT_FOUND);
}
$item = new Support();
$item->setMain($upper->getId())
->setBody($params['body'])
->setTitle($upper->getTitle())
->setDateSubmit(time())
->setSubmitter($this->getUser())
->setState('در حال پیگیری');
$entityManager->persist($item);
$entityManager->flush();
$fileName = $this->handleFileUpload($request, $uploadDirectory, $item->getId());
if ($fileName) {
$item->setFileName($fileName);
}
$entityManager->persist($item);
$upper->setState('در حال پیگیری');
$entityManager->persist($upper);
$entityManager->flush();
$SMS->send([$item->getId()], $registryMGR->get('sms', 'ticketRec'), $registryMGR->get('ticket', 'managerMobile'));
return $this->json([
'error' => 0,
'message' => 'ok',
'url' => $item->getId(),
'files' => $fileName
]);
} }
#[Route('/api/support/view/{id}', name: 'app_support_view')] #[Route('/api/support/view/{id}', name: 'app_support_view')]
public function app_support_view(EntityManagerInterface $entityManager, string $id): JsonResponse public function app_support_view(TicketService $ticketService, string $id): JsonResponse
{ {
$item = $this->getTicket($entityManager, $id, true); return $this->json($ticketService->getTicketDetails($id, $this->getUser()));
if (!$item) {
throw $this->createAccessDeniedException();
}
$replays = $entityManager->getRepository(Support::class)->findBy(['main' => $item->getId()]);
$replaysArray = array_map(fn($replay) => Explore::ExploreSupportTicket($replay, $this->getUser()), $replays);
return $this->json([
'item' => Explore::ExploreSupportTicket($item, $this->getUser()),
'replays' => $replaysArray
]);
} }
#[Route('/api/support/download/file/{id}', name: 'app_support_download_file')] #[Route('/api/support/download/file/{id}', name: 'app_support_download_file')]

View file

@ -335,6 +335,23 @@ class AGIService
$cogAccountingDocService = new \App\Cog\AccountingDocService($this->em); $cogAccountingDocService = new \App\Cog\AccountingDocService($this->em);
$accountingDocService = new \App\AiTool\AccountingDocService($this->em, $cogAccountingDocService); $accountingDocService = new \App\AiTool\AccountingDocService($this->em, $cogAccountingDocService);
return $accountingDocService->searchRowsAi($params, $params['acc'] ?? null); return $accountingDocService->searchRowsAi($params, $params['acc'] ?? null);
// ابزارهای مربوط به تیکت
case 'getTicketsList':
$cogTicketService = new \App\Cog\TicketService($this->em);
$ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService);
return $ticketService->getTicketsListAi($params, $params['acc'] ?? null);
case 'getTicketInfo':
$cogTicketService = new \App\Cog\TicketService($this->em);
$ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService);
return $ticketService->getTicketInfoByCode($params['code'] ?? null, $params['acc'] ?? null);
case 'addOrUpdateTicket':
$cogTicketService = new \App\Cog\TicketService($this->em);
$ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService);
return $ticketService->addOrUpdateTicketAi($params, $params['acc'] ?? null, $params['code'] ?? 0);
case 'replyToTicket':
$cogTicketService = new \App\Cog\TicketService($this->em);
$ticketService = new \App\AiTool\TicketService($this->em, $cogTicketService);
return $ticketService->replyToTicketAi($params, $params['acc'] ?? null);
default: default:
return [ return [
'error' => 'ابزار ناشناخته: ' . $tool 'error' => 'ابزار ناشناخته: ' . $tool

View file

@ -6,6 +6,7 @@ use Doctrine\ORM\EntityManagerInterface;
use App\Service\AGI\Promps\InventoryPromptService; use App\Service\AGI\Promps\InventoryPromptService;
use App\Service\AGI\Promps\BankPromptService; use App\Service\AGI\Promps\BankPromptService;
use App\Service\AGI\Promps\AccountingDocPromptService; use App\Service\AGI\Promps\AccountingDocPromptService;
use App\Service\AGI\Promps\TicketPromptService;
class PromptService class PromptService
{ {
@ -15,6 +16,7 @@ class PromptService
private $inventoryPromptService; private $inventoryPromptService;
private $bankPromptService; private $bankPromptService;
private $accountingDocPromptService; private $accountingDocPromptService;
private $ticketPromptService;
public function __construct( public function __construct(
EntityManagerInterface $entityManager, EntityManagerInterface $entityManager,
@ -22,7 +24,8 @@ class PromptService
BasePromptService $basePromptService, BasePromptService $basePromptService,
InventoryPromptService $inventoryPromptService, InventoryPromptService $inventoryPromptService,
BankPromptService $bankPromptService, BankPromptService $bankPromptService,
AccountingDocPromptService $accountingDocPromptService AccountingDocPromptService $accountingDocPromptService,
TicketPromptService $ticketPromptService
) { ) {
$this->em = $entityManager; $this->em = $entityManager;
$this->personPromptService = $personPromptService; $this->personPromptService = $personPromptService;
@ -30,6 +33,7 @@ class PromptService
$this->inventoryPromptService = $inventoryPromptService; $this->inventoryPromptService = $inventoryPromptService;
$this->bankPromptService = $bankPromptService; $this->bankPromptService = $bankPromptService;
$this->accountingDocPromptService = $accountingDocPromptService; $this->accountingDocPromptService = $accountingDocPromptService;
$this->ticketPromptService = $ticketPromptService;
} }
/** /**
@ -56,6 +60,10 @@ class PromptService
$accountingTools = $this->accountingDocPromptService->getTools(); $accountingTools = $this->accountingDocPromptService->getTools();
$tools = array_merge($tools, $accountingTools); $tools = array_merge($tools, $accountingTools);
// ابزارهای بخش تیکت‌ها
$ticketTools = $this->ticketPromptService->getTools();
$tools = array_merge($tools, $ticketTools);
return $tools; return $tools;
} }
@ -115,6 +123,9 @@ class PromptService
// پرامپ‌های بخش اسناد حسابداری // پرامپ‌های بخش اسناد حسابداری
$prompts['accounting'] = $this->accountingDocPromptService->getAllAccountingDocPrompts(); $prompts['accounting'] = $this->accountingDocPromptService->getAllAccountingDocPrompts();
// پرامپ‌های بخش تیکت‌ها
$prompts['ticket'] = $this->ticketPromptService->getAllTicketPrompts();
// در آینده بخش‌های دیگر اضافه خواهند شد // در آینده بخش‌های دیگر اضافه خواهند شد
// $prompts['accounting'] = $this->accountingPromptService->getAllAccountingPrompts(); // $prompts['accounting'] = $this->accountingPromptService->getAllAccountingPrompts();
// $prompts['reports'] = $this->reportsPromptService->getAllReportsPrompts(); // $prompts['reports'] = $this->reportsPromptService->getAllReportsPrompts();

View file

@ -0,0 +1,146 @@
<?php
namespace App\Service\AGI\Promps;
class TicketService
{
/**
* پرامپت برای بررسی متن تیکت و دسته‌بندی آن
*/
public function getTicketAnalysisPrompt(string $ticketBody): string
{
return <<<PROMPT
لطفاً این تیکت پشتیبانی را بررسی و دسته‌بندی کنید:
متن تیکت:
{$ticketBody}
لطفاً موارد زیر را مشخص کنید:
1. موضوع اصلی تیکت
2. اولویت (کم، متوسط، زیاد)
3. بخش مربوطه (مالی، فنی، عمومی)
4. پیشنهاد برای پاسخ
PROMPT;
}
/**
* پرامپت برای تولید پیش‌نویس پاسخ به تیکت
*/
public function getDraftResponsePrompt(string $ticketBody, string $ticketTitle, array $history = []): string
{
$historyText = '';
if (!empty($history)) {
$historyText = "تاریخچه مکالمات قبلی:\n";
foreach ($history as $message) {
$historyText .= sprintf(
"- %s: %s\n",
$message['sender'],
$message['message']
);
}
}
return <<<PROMPT
لطفاً یک پیش‌نویس پاسخ مناسب برای این تیکت پشتیبانی آماده کنید:
عنوان تیکت: {$ticketTitle}
متن تیکت:
{$ticketBody}
{$historyText}
لطفاً یک پاسخ حرفه‌ای و دقیق با در نظر گرفتن نکات زیر آماده کنید:
1. لحن مؤدبانه و حرفه‌ای
2. پاسخگویی به تمام نکات مطرح شده در تیکت
3. ارائه راهکارهای عملی
4. درخواست اطلاعات تکمیلی در صورت نیاز
PROMPT;
}
/**
* پرامپت برای پیشنهاد اقدامات بعدی برای تیکت
*/
public function getNextActionPrompt(string $ticketBody, string $currentStatus, array $previousActions = []): string
{
$previousActionsText = '';
if (!empty($previousActions)) {
$previousActionsText = "اقدامات قبلی:\n";
foreach ($previousActions as $action) {
$previousActionsText .= "- {$action}\n";
}
}
return <<<PROMPT
لطفاً اقدامات بعدی مناسب برای این تیکت را پیشنهاد دهید:
متن تیکت:
{$ticketBody}
وضعیت فعلی: {$currentStatus}
{$previousActionsText}
لطفاً موارد زیر را مشخص کنید:
1. آیا نیاز به ارجاع به بخش دیگری هست؟
2. آیا نیاز به اطلاعات تکمیلی از کاربر هست؟
3. اولویت رسیدگی به این تیکت
4. پیشنهاد برای اقدام بعدی
PROMPT;
}
/**
* پرامپت برای خلاصه‌سازی تیکت و تاریخچه آن
*/
public function getTicketSummaryPrompt(array $ticketHistory): string
{
$historyText = '';
foreach ($ticketHistory as $entry) {
$historyText .= sprintf(
"- %s (%s): %s\n",
$entry['date'],
$entry['user'],
$entry['message']
);
}
return <<<PROMPT
لطفاً خلاصه‌ای از این تیکت و تاریخچه آن تهیه کنید:
تاریخچه تیکت:
{$historyText}
لطفاً موارد زیر را در خلاصه مشخص کنید:
1. موضوع اصلی و مشکل گزارش شده
2. اقدامات انجام شده
3. وضعیت فعلی
4. نکات مهم برای پیگیری
PROMPT;
}
/**
* پرامپت برای دسته‌بندی خودکار تیکت‌ها
*/
public function getTicketCategorizationPrompt(array $tickets): string
{
$ticketsText = '';
foreach ($tickets as $ticket) {
$ticketsText .= sprintf(
"عنوان: %s\nمتن: %s\n\n",
$ticket['title'],
$ticket['body']
);
}
return <<<PROMPT
لطفاً این تیکت‌ها را بر اساس موضوع و محتوا دسته‌بندی کنید:
تیکت‌ها:
{$ticketsText}
لطفاً برای هر تیکت موارد زیر را مشخص کنید:
1. دسته اصلی (مالی، فنی، پشتیبانی عمومی، آموزش)
2. زیر دسته
3. برچسب‌های پیشنهادی
4. اولویت پیشنهادی
PROMPT;
}
}