more progress in agi and support external tools
This commit is contained in:
parent
474f1274c0
commit
bad8dc0f73
|
@ -97,6 +97,32 @@ services:
|
|||
tags: ['twig.extension']
|
||||
|
||||
App\Cog\PersonService:
|
||||
arguments:
|
||||
$entityManager: '@doctrine.orm.entity_manager'
|
||||
|
||||
App\Service\AGI\Promps\AccountingDocPromptService:
|
||||
arguments:
|
||||
$entityManager: '@doctrine.orm.entity_manager'
|
||||
|
||||
App\Service\AGI\Promps\BasePromptService:
|
||||
arguments:
|
||||
$entityManager: '@doctrine.orm.entity_manager'
|
||||
$access: '@App\Service\Access'
|
||||
|
||||
App\Service\AGI\Promps\PromptService:
|
||||
arguments:
|
||||
$entityManager: '@doctrine.orm.entity_manager'
|
||||
$personPromptService: '@App\Service\AGI\Promps\PersonPromptService'
|
||||
$basePromptService: '@App\Service\AGI\Promps\BasePromptService'
|
||||
$inventoryPromptService: '@App\Service\AGI\Promps\InventoryPromptService'
|
||||
$bankPromptService: '@App\Service\AGI\Promps\BankPromptService'
|
||||
$accountingDocPromptService: '@App\Service\AGI\Promps\AccountingDocPromptService'
|
||||
|
||||
App\Cog\AccountingDocService:
|
||||
arguments:
|
||||
$entityManager: '@doctrine.orm.entity_manager'
|
||||
|
||||
App\AiTool\AccountingDocService:
|
||||
arguments:
|
||||
$em: '@doctrine.orm.entity_manager'
|
||||
$cogAccountingDocService: '@App\Cog\AccountingDocService'
|
||||
|
|
37
hesabixCore/src/AiTool/AccountingDocService.php
Normal file
37
hesabixCore/src/AiTool/AccountingDocService.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\AiTool;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Cog\AccountingDocService as CogAccountingDocService;
|
||||
|
||||
class AccountingDocService
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
private CogAccountingDocService $cogAccountingDocService;
|
||||
public function __construct(EntityManagerInterface $em, CogAccountingDocService $cogAccountingDocService)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->cogAccountingDocService = $cogAccountingDocService;
|
||||
}
|
||||
|
||||
/**
|
||||
* جستوجوی ردیفهای اسناد حسابداری برای ابزار هوش مصنوعی
|
||||
*/
|
||||
public function searchRowsAi(array $params, $acc = null): array
|
||||
{
|
||||
$acc = $acc ?? ($params['acc'] ?? null);
|
||||
if (!$acc) {
|
||||
return [
|
||||
'error' => 'اطلاعات دسترسی (acc) الزامی است'
|
||||
];
|
||||
}
|
||||
try {
|
||||
return $this->cogAccountingDocService->searchRows($params, $acc);
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'error' => 'خطا در جستوجوی ردیفهای اسناد: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
37
hesabixCore/src/AiTool/CommodityService.php
Normal file
37
hesabixCore/src/AiTool/CommodityService.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\AiTool;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Cog\CommodityService as CogCommodityService;
|
||||
|
||||
class CommodityService
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
private CogCommodityService $cogCommodityService;
|
||||
public function __construct(EntityManagerInterface $em, CogCommodityService $cogCommodityService)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->cogCommodityService = $cogCommodityService;
|
||||
}
|
||||
|
||||
/**
|
||||
* افزودن یا ویرایش کالا برای ابزار هوش مصنوعی
|
||||
*/
|
||||
public function addOrUpdateCommodityAi(array $params, $acc = null, $code = 0): array
|
||||
{
|
||||
$acc = $acc ?? ($params['acc'] ?? null);
|
||||
if (!$acc) {
|
||||
return [
|
||||
'error' => 'اطلاعات دسترسی (acc) الزامی است'
|
||||
];
|
||||
}
|
||||
try {
|
||||
return $this->cogCommodityService->addOrUpdateCommodity($params, $acc, $code ?? ($params['code'] ?? 0));
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'error' => 'خطا در افزودن/ویرایش کالا: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ class PersonService
|
|||
];
|
||||
}
|
||||
try {
|
||||
// فقط کد را به سرویس Cog پاس بده
|
||||
return $this->cogPersonService->getPersonInfo($code, $acc);
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
|
@ -40,5 +41,43 @@ class PersonService
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت لیست اشخاص با فیلتر و صفحهبندی برای ابزار هوش مصنوعی
|
||||
*/
|
||||
public function getPersonsListAi(array $params, $acc = null): array
|
||||
{
|
||||
$acc = $acc ?? ($params['acc'] ?? null);
|
||||
if (!$acc) {
|
||||
return [
|
||||
'error' => 'اطلاعات دسترسی (acc) الزامی است'
|
||||
];
|
||||
}
|
||||
try {
|
||||
return $this->cogPersonService->getPersonsList($params, $acc);
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'error' => 'خطا در دریافت لیست اشخاص: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* افزودن یا ویرایش شخص برای ابزار هوش مصنوعی
|
||||
*/
|
||||
public function addOrUpdatePersonAi(array $params, $acc = null, $code = 0): array
|
||||
{
|
||||
$acc = $acc ?? ($params['acc'] ?? null);
|
||||
if (!$acc) {
|
||||
return [
|
||||
'error' => 'اطلاعات دسترسی (acc) الزامی است'
|
||||
];
|
||||
}
|
||||
try {
|
||||
return $this->cogPersonService->addOrUpdatePerson($params, $acc, $code ?? ($params['code'] ?? 0));
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'error' => 'خطا در افزودن/ویرایش شخص: ' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
116
hesabixCore/src/Cog/AccountingDocService.php
Normal file
116
hesabixCore/src/Cog/AccountingDocService.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace App\Cog;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class AccountingDocService
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* جستوجوی ردیفهای اسناد حسابداری بر اساس نوع و شناسه
|
||||
* @param array $params
|
||||
* @param array $acc
|
||||
* @return array
|
||||
*/
|
||||
public function searchRows(array $params, array $acc): array
|
||||
{
|
||||
$em = $this->entityManager;
|
||||
$data = [];
|
||||
if (!isset($params['type'])) {
|
||||
return ['error' => 'نوع (type) الزامی است'];
|
||||
}
|
||||
$roll = '';
|
||||
if ($params['type'] == 'person')
|
||||
$roll = 'person';
|
||||
if ($params['type'] == 'person_receive' || $params['type'] == 'person_send')
|
||||
$roll = 'person';
|
||||
elseif ($params['type'] == 'sell_receive')
|
||||
$roll = 'sell';
|
||||
elseif ($params['type'] == 'bank')
|
||||
$roll = 'banks';
|
||||
elseif ($params['type'] == 'buy_send')
|
||||
$roll = 'buy';
|
||||
elseif ($params['type'] == 'transfer')
|
||||
$roll = 'bankTransfer';
|
||||
elseif ($params['type'] == 'all')
|
||||
$roll = 'accounting';
|
||||
else
|
||||
$roll = $params['type'];
|
||||
// اینجا فرض میکنیم acc معتبر است و قبلاً بررسی شده
|
||||
if ($params['type'] == 'person') {
|
||||
$person = $em->getRepository(\App\Entity\Person::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['id'],
|
||||
]);
|
||||
if (!$person)
|
||||
return ['error' => 'شخص یافت نشد'];
|
||||
$data = $em->getRepository(\App\Entity\HesabdariRow::class)->findBy([
|
||||
'person' => $person,
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
} elseif ($params['type'] == 'bank') {
|
||||
$bank = $em->getRepository(\App\Entity\BankAccount::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['id'],
|
||||
]);
|
||||
if (!$bank)
|
||||
return ['error' => 'بانک یافت نشد'];
|
||||
$data = $em->getRepository(\App\Entity\HesabdariRow::class)->findBy([
|
||||
'bank' => $bank,
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
} elseif ($params['type'] == 'cashdesk') {
|
||||
$cashdesk = $em->getRepository(\App\Entity\Cashdesk::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['id'],
|
||||
]);
|
||||
if (!$cashdesk)
|
||||
return ['error' => 'صندوق یافت نشد'];
|
||||
$data = $em->getRepository(\App\Entity\HesabdariRow::class)->findBy([
|
||||
'cashdesk' => $cashdesk,
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
} elseif ($params['type'] == 'salary') {
|
||||
$salary = $em->getRepository(\App\Entity\Salary::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['id'],
|
||||
]);
|
||||
if (!$salary)
|
||||
return ['error' => 'حقوق یافت نشد'];
|
||||
$data = $em->getRepository(\App\Entity\HesabdariRow::class)->findBy([
|
||||
'salary' => $salary,
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
} else {
|
||||
return ['error' => 'نوع پشتیبانی نمیشود'];
|
||||
}
|
||||
$dataTemp = [];
|
||||
foreach ($data as $item) {
|
||||
$temp = [
|
||||
'id' => $item->getId(),
|
||||
'dateSubmit' => $item->getDoc()->getDateSubmit(),
|
||||
'date' => $item->getDoc()->getDate(),
|
||||
'type' => $item->getDoc()->getType(),
|
||||
'ref' => $item->getRef()->getName(),
|
||||
'des' => $item->getDes(),
|
||||
'bs' => $item->getBs(),
|
||||
'bd' => $item->getBd(),
|
||||
'code' => $item->getDoc()->getCode(),
|
||||
'submitter' => $item->getDoc()->getSubmitter()->getFullName()
|
||||
];
|
||||
$dataTemp[] = $temp;
|
||||
}
|
||||
return $dataTemp;
|
||||
}
|
||||
}
|
107
hesabixCore/src/Cog/CommodityService.php
Normal file
107
hesabixCore/src/Cog/CommodityService.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace App\Cog;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Entity\Commodity;
|
||||
use App\Entity\CommodityUnit;
|
||||
use App\Entity\CommodityCat;
|
||||
use App\Entity\PriceList;
|
||||
use App\Entity\PriceListDetail;
|
||||
|
||||
class CommodityService
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* افزودن یا ویرایش کالا/خدمات
|
||||
* @param array $params
|
||||
* @param array $acc
|
||||
* @param int|string $code
|
||||
* @return array
|
||||
*/
|
||||
public function addOrUpdateCommodity(array $params, array $acc, $code = 0): array
|
||||
{
|
||||
$em = $this->entityManager;
|
||||
if (!isset($params['name']) || trim($params['name']) === '')
|
||||
return ['result' => -1, 'error' => 'نام کالا الزامی است'];
|
||||
if ($code == 0) {
|
||||
$data = $em->getRepository(Commodity::class)->findOneBy([
|
||||
'name' => $params['name'],
|
||||
'bid' => $acc['bid']
|
||||
]);
|
||||
if (!$data) {
|
||||
$data = new Commodity();
|
||||
$data->setCode((new \App\Service\Provider($em))->getAccountingCode($acc['bid'], 'Commodity'));
|
||||
}
|
||||
} else {
|
||||
$data = $em->getRepository(Commodity::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $code
|
||||
]);
|
||||
if (!$data)
|
||||
return ['result' => -2, 'error' => 'کالا یافت نشد'];
|
||||
}
|
||||
$unit = null;
|
||||
if (!isset($params['unit']))
|
||||
$unit = $em->getRepository(CommodityUnit::class)->findAll()[0];
|
||||
else
|
||||
$unit = $em->getRepository(CommodityUnit::class)->findOneBy(['name' => $params['unit']]);
|
||||
if (!$unit)
|
||||
return ['result' => -3, 'error' => 'واحد کالا یافت نشد'];
|
||||
$data->setUnit($unit);
|
||||
$data->setBid($acc['bid']);
|
||||
$data->setName($params['name']);
|
||||
$data->setKhadamat($params['khadamat'] ?? false);
|
||||
$data->setWithoutTax($params['withoutTax'] ?? false);
|
||||
if (isset($params['des'])) $data->setDes($params['des']);
|
||||
if (isset($params['priceSell'])) $data->setPriceSell($params['priceSell']);
|
||||
if (isset($params['priceBuy'])) $data->setPriceBuy($params['priceBuy']);
|
||||
if (isset($params['commodityCountCheck'])) $data->setCommodityCountCheck($params['commodityCountCheck']);
|
||||
if (isset($params['barcodes'])) $data->setBarcodes($params['barcodes']);
|
||||
if (isset($params['taxCode'])) $data->setTaxCode($params['taxCode']);
|
||||
if (isset($params['taxType'])) $data->setTaxType($params['taxType']);
|
||||
if (isset($params['taxUnit'])) $data->setTaxUnit($params['taxUnit']);
|
||||
if (isset($params['minOrderCount'])) $data->setMinOrderCount($params['minOrderCount']);
|
||||
if (isset($params['speedAccess'])) $data->setSpeedAccess($params['speedAccess']);
|
||||
if (isset($params['dayLoading'])) $data->setDayLoading($params['dayLoading']);
|
||||
if (isset($params['orderPoint'])) $data->setOrderPoint($params['orderPoint']);
|
||||
// دستهبندی
|
||||
if (isset($params['cat']) && $params['cat'] != '') {
|
||||
$cat = is_array($params['cat']) ? $em->getRepository(CommodityCat::class)->find($params['cat']['id']) : $em->getRepository(CommodityCat::class)->find($params['cat']);
|
||||
if ($cat && $cat->getBid() == $acc['bid']) {
|
||||
$data->setCat($cat);
|
||||
}
|
||||
}
|
||||
$em->persist($data);
|
||||
// قیمتها
|
||||
if (isset($params['prices'])) {
|
||||
foreach ($params['prices'] as $item) {
|
||||
$priceList = $em->getRepository(PriceList::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'id' => $item['list']['id']
|
||||
]);
|
||||
if ($priceList) {
|
||||
$detail = $em->getRepository(PriceListDetail::class)->findOneBy([
|
||||
'list' => $priceList,
|
||||
'commodity' => $data
|
||||
]);
|
||||
if (!$detail) $detail = new PriceListDetail();
|
||||
$detail->setList($priceList);
|
||||
$detail->setCommodity($data);
|
||||
$detail->setPriceSell($item['priceSell']);
|
||||
$detail->setPriceBuy(0);
|
||||
$detail->setMoney($acc['money']);
|
||||
$em->persist($detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
$em->flush();
|
||||
return ['Success' => true, 'result' => 1, 'code' => $data->getId()];
|
||||
}
|
||||
}
|
|
@ -16,15 +16,13 @@ use App\Service\Explore;
|
|||
class PersonService
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
private array $access;
|
||||
|
||||
/**
|
||||
* سازنده سرویس
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entityManager, array $access)
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->access = $access;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,4 +72,212 @@ class PersonService
|
|||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت لیست اشخاص با فیلتر، جستوجو و صفحهبندی
|
||||
*
|
||||
* @param array $params پارامترهای جستوجو و فیلتر
|
||||
* @param array $acc اطلاعات دسترسی
|
||||
* @return array
|
||||
*/
|
||||
public function getPersonsList(array $params, array $acc): array
|
||||
{
|
||||
$page = $params['page'] ?? 1;
|
||||
$itemsPerPage = $params['itemsPerPage'] ?? 10;
|
||||
$search = $params['search'] ?? '';
|
||||
$types = $params['types'] ?? null;
|
||||
$transactionFilters = $params['transactionFilters'] ?? null;
|
||||
|
||||
$queryBuilder = $this->entityManager->getRepository(Person::class)
|
||||
->createQueryBuilder('p')
|
||||
->where('p.bid = :bid')
|
||||
->setParameter('bid', $acc['bid']);
|
||||
|
||||
if (!empty($search) || $search === '0') {
|
||||
$search = trim($search);
|
||||
$queryBuilder->andWhere('p.nikename LIKE :search OR p.name LIKE :search OR p.code LIKE :search OR p.mobile LIKE :search')
|
||||
->setParameter('search', "%$search%");
|
||||
}
|
||||
|
||||
if ($types && !empty($types)) {
|
||||
$queryBuilder->leftJoin('p.type', 't')
|
||||
->andWhere('t.code IN (:types)')
|
||||
->setParameter('types', $types);
|
||||
}
|
||||
|
||||
$totalItems = (clone $queryBuilder)
|
||||
->select('COUNT(p.id)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
|
||||
$persons = $queryBuilder
|
||||
->select('p')
|
||||
->setFirstResult(($page - 1) * $itemsPerPage)
|
||||
->setMaxResults($itemsPerPage)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
$response = [];
|
||||
foreach ($persons as $person) {
|
||||
$rows = $this->entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||
'person' => $person,
|
||||
'bid' => $acc['bid']
|
||||
]);
|
||||
$bs = 0;
|
||||
$bd = 0;
|
||||
foreach ($rows as $row) {
|
||||
$doc = $row->getDoc();
|
||||
if ($doc && $doc->getMoney() && $doc->getYear() &&
|
||||
$doc->getMoney()->getId() == $acc['money']->getId() &&
|
||||
$doc->getYear()->getId() == $acc['year']->getId()) {
|
||||
$bs += (float) $row->getBs();
|
||||
$bd += (float) $row->getBd();
|
||||
}
|
||||
}
|
||||
$balance = $bs - $bd;
|
||||
|
||||
$include = true;
|
||||
if ($transactionFilters && !empty($transactionFilters)) {
|
||||
$include = false;
|
||||
if (in_array('debtors', $transactionFilters) && $balance < 0) {
|
||||
$include = true;
|
||||
}
|
||||
if (in_array('creditors', $transactionFilters) && $balance > 0) {
|
||||
$include = true;
|
||||
}
|
||||
if (in_array('zero', $transactionFilters) && $balance == 0) {
|
||||
$include = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($include) {
|
||||
$result = Explore::ExplorePerson($person, $this->entityManager->getRepository(PersonType::class)->findAll());
|
||||
$result['bs'] = $bs;
|
||||
$result['bd'] = $bd;
|
||||
$result['balance'] = $balance;
|
||||
$response[] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
$filteredTotal = count($response);
|
||||
|
||||
return [
|
||||
'items' => array_slice($response, 0, $itemsPerPage),
|
||||
'total' => $filteredTotal,
|
||||
'unfilteredTotal' => $totalItems,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* افزودن یا ویرایش شخص
|
||||
* @param array $params
|
||||
* @param array $acc
|
||||
* @param int|string $code
|
||||
* @return array
|
||||
*/
|
||||
public function addOrUpdatePerson(array $params, array $acc, $code = 0): array
|
||||
{
|
||||
$em = $this->entityManager;
|
||||
if (!isset($params['nikename']) || trim($params['nikename']) === '')
|
||||
return ['result' => -1, 'error' => 'نام مستعار الزامی است'];
|
||||
|
||||
if ($code == 0) {
|
||||
$person = $em->getRepository(\App\Entity\Person::class)->findOneBy([
|
||||
'nikename' => $params['nikename'],
|
||||
'bid' => $acc['bid']
|
||||
]);
|
||||
if (!$person) {
|
||||
$person = new \App\Entity\Person();
|
||||
$maxAttempts = 10;
|
||||
$newCode = null;
|
||||
for ($i = 0; $i < $maxAttempts; $i++) {
|
||||
$newCode = $params['code'] ?? $code;
|
||||
if (!$newCode || $newCode == 0) {
|
||||
$newCode = (new \App\Service\Provider($em))->getAccountingCode($acc['bid'], 'person');
|
||||
}
|
||||
$exist = $em->getRepository(\App\Entity\Person::class)->findOneBy(['code' => $newCode]);
|
||||
if (!$exist) break;
|
||||
}
|
||||
if ($newCode === null) return ['result' => -2, 'error' => 'کد جدید تولید نشد'];
|
||||
$person->setCode($newCode);
|
||||
}
|
||||
} else {
|
||||
$person = $em->getRepository(\App\Entity\Person::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $code
|
||||
]);
|
||||
if (!$person) return ['result' => -3, 'error' => 'شخص یافت نشد'];
|
||||
}
|
||||
$person->setBid($acc['bid']);
|
||||
$person->setNikename($params['nikename']);
|
||||
if (isset($params['name'])) $person->setName($params['name']);
|
||||
if (isset($params['birthday'])) $person->setBirthday($params['birthday']);
|
||||
if (isset($params['tel'])) $person->setTel($params['tel']);
|
||||
if (isset($params['speedAccess'])) $person->setSpeedAccess($params['speedAccess']);
|
||||
if (isset($params['address'])) $person->setAddress($params['address']);
|
||||
if (isset($params['des'])) $person->setDes($params['des']);
|
||||
if (isset($params['mobile'])) $person->setMobile($params['mobile']);
|
||||
if (isset($params['mobile2'])) $person->setMobile2($params['mobile2']);
|
||||
if (isset($params['fax'])) $person->setFax($params['fax']);
|
||||
if (isset($params['website'])) $person->setWebsite($params['website']);
|
||||
if (isset($params['email'])) $person->setEmail($params['email']);
|
||||
if (isset($params['postalcode'])) $person->setPostalcode($params['postalcode']);
|
||||
if (isset($params['shahr'])) $person->setShahr($params['shahr']);
|
||||
if (isset($params['ostan'])) $person->setOstan($params['ostan']);
|
||||
if (isset($params['keshvar'])) $person->setKeshvar($params['keshvar']);
|
||||
if (isset($params['sabt'])) $person->setSabt($params['sabt']);
|
||||
if (isset($params['codeeghtesadi'])) $person->setCodeeghtesadi($params['codeeghtesadi']);
|
||||
if (isset($params['shenasemeli'])) $person->setShenasemeli($params['shenasemeli']);
|
||||
if (isset($params['company'])) $person->setCompany($params['company']);
|
||||
if (array_key_exists('prelabel', $params)) {
|
||||
if ($params['prelabel'] != '') {
|
||||
$prelabel = $em->getRepository(\App\Entity\PersonPrelabel::class)->findOneBy(['label' => $params['prelabel']]);
|
||||
if ($prelabel) $person->setPrelabel($prelabel);
|
||||
} elseif ($params['prelabel'] == null) {
|
||||
$person->setPrelabel(null);
|
||||
}
|
||||
}
|
||||
// کارتها
|
||||
if (isset($params['accounts'])) {
|
||||
foreach ($params['accounts'] as $item) {
|
||||
$card = $em->getRepository(\App\Entity\PersonCard::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'person' => $person,
|
||||
'bank' => $item['bank']
|
||||
]);
|
||||
if (!$card) $card = new \App\Entity\PersonCard();
|
||||
$card->setPerson($person);
|
||||
$card->setBid($acc['bid']);
|
||||
$card->setShabaNum($item['shabaNum']);
|
||||
$card->setCardNum($item['cardNum']);
|
||||
$card->setAccountNum($item['accountNum']);
|
||||
$card->setBank($item['bank']);
|
||||
$em->persist($card);
|
||||
}
|
||||
// حذف کارتهای حذفشده
|
||||
$accounts = $em->getRepository(\App\Entity\PersonCard::class)->findBy([
|
||||
'bid' => $acc['bid'],
|
||||
'person' => $person,
|
||||
]);
|
||||
foreach ($accounts as $item) {
|
||||
$deleted = true;
|
||||
foreach ($params['accounts'] as $param) {
|
||||
if ($item->getBank() == $param['bank']) $deleted = false;
|
||||
}
|
||||
if ($deleted) $em->remove($item);
|
||||
}
|
||||
}
|
||||
// نوعها
|
||||
if (isset($params['types'])) {
|
||||
$types = $em->getRepository(\App\Entity\PersonType::class)->findAll();
|
||||
foreach ($params['types'] as $item) {
|
||||
$typeEntity = $em->getRepository(\App\Entity\PersonType::class)->findOneBy(['code' => $item['code']]);
|
||||
if ($item['checked'] == true) $person->addType($typeEntity);
|
||||
elseif ($item['checked'] == false) $person->removeType($typeEntity);
|
||||
}
|
||||
}
|
||||
$em->persist($person);
|
||||
$em->flush();
|
||||
return ['Success' => true, 'result' => 1];
|
||||
}
|
||||
}
|
|
@ -469,6 +469,7 @@ class AdminController extends AbstractController
|
|||
$resp['inputTokenPrice'] = $registryMGR->get('system', key: 'inputTokenPrice');
|
||||
$resp['outputTokenPrice'] = $registryMGR->get('system', key: 'outputTokenPrice');
|
||||
$resp['aiPrompt'] = $registryMGR->get('system', key: 'aiPrompt');
|
||||
$resp['aiDebugMode'] = $registryMGR->get('system', key: 'aiDebugMode');
|
||||
|
||||
return $this->json($resp);
|
||||
}
|
||||
|
@ -521,6 +522,8 @@ class AdminController extends AbstractController
|
|||
$registryMGR->update('system', 'outputTokenPrice', $params['outputTokenPrice'] ?? '');
|
||||
if (array_key_exists('aiPrompt', $params))
|
||||
$registryMGR->update('system', 'aiPrompt', $params['aiPrompt'] ?? '');
|
||||
if (array_key_exists('aiDebugMode', $params))
|
||||
$registryMGR->update('system', 'aiDebugMode', $params['aiDebugMode'] ?? '');
|
||||
|
||||
$entityManager->persist($item);
|
||||
$entityManager->flush();
|
||||
|
|
|
@ -823,138 +823,13 @@ class CommodityController extends AbstractController
|
|||
if ($content = $request->getContent()) {
|
||||
$params = json_decode($content, true);
|
||||
}
|
||||
if (!array_key_exists('name', $params))
|
||||
return $this->json(['result' => -1]);
|
||||
if (count_chars(trim($params['name'])) == 0)
|
||||
return $this->json(['result' => 3]);
|
||||
if ($code == 0) {
|
||||
$data = $entityManager->getRepository(Commodity::class)->findOneBy([
|
||||
'name' => $params['name'],
|
||||
'bid' => $acc['bid']
|
||||
]);
|
||||
//check exist before
|
||||
if (!$data) {
|
||||
$data = new Commodity();
|
||||
$data->setCode($provider->getAccountingCode($request->headers->get('activeBid'), 'Commodity'));
|
||||
$commodityService = new \App\Cog\CommodityService($entityManager);
|
||||
$result = $commodityService->addOrUpdateCommodity($params, $acc, $code);
|
||||
if (isset($result['error'])) {
|
||||
return $this->json($result, 400);
|
||||
}
|
||||
} else {
|
||||
$data = $entityManager->getRepository(Commodity::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $code
|
||||
]);
|
||||
if (!$data)
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
if (!array_key_exists('unit', $params))
|
||||
$unit = $entityManager->getRepository(CommodityUnit::class)->findAll()[0];
|
||||
else
|
||||
$unit = $entityManager->getRepository(CommodityUnit::class)->findOneBy(['name' => $params['unit']]);
|
||||
if (!$unit)
|
||||
throw $this->createNotFoundException('unit not fount!');
|
||||
$data->setUnit($unit);
|
||||
$data->setBid($acc['bid']);
|
||||
$data->setname($params['name']);
|
||||
if ($params['khadamat'] == 'true')
|
||||
$data->setKhadamat(true);
|
||||
else
|
||||
$data->setKhadamat(false);
|
||||
|
||||
if (!array_key_exists('withoutTax', $params))
|
||||
$data->setWithoutTax(false);
|
||||
else {
|
||||
if ($params['withoutTax'] == 'true')
|
||||
$data->setWithoutTax(true);
|
||||
else
|
||||
$data->setWithoutTax(false);
|
||||
}
|
||||
|
||||
if (array_key_exists('des', $params))
|
||||
$data->setDes($params['des']);
|
||||
|
||||
if (array_key_exists('priceSell', $params))
|
||||
$data->setPriceSell($params['priceSell']);
|
||||
|
||||
if (array_key_exists('priceBuy', $params))
|
||||
$data->setPriceBuy($params['priceBuy']);
|
||||
|
||||
if (array_key_exists('commodityCountCheck', $params)) {
|
||||
$data->setCommodityCountCheck($params['commodityCountCheck']);
|
||||
}
|
||||
if (array_key_exists('barcodes', $params)) {
|
||||
$data->setBarcodes($params['barcodes']);
|
||||
}
|
||||
|
||||
if (array_key_exists('taxCode', $params)) {
|
||||
$data->setTaxCode($params['taxCode']);
|
||||
}
|
||||
|
||||
if (array_key_exists('taxType', $params)) {
|
||||
$data->setTaxType($params['taxType']);
|
||||
}
|
||||
|
||||
if (array_key_exists('taxUnit', $params)) {
|
||||
$data->setTaxUnit($params['taxUnit']);
|
||||
}
|
||||
|
||||
if (array_key_exists('minOrderCount', $params)) {
|
||||
$data->setMinOrderCount($params['minOrderCount']);
|
||||
}
|
||||
if (array_key_exists('speedAccess', $params)) {
|
||||
$data->setSpeedAccess($params['speedAccess']);
|
||||
}
|
||||
if (array_key_exists('dayLoading', $params)) {
|
||||
$data->setDayLoading($params['dayLoading']);
|
||||
}
|
||||
if (array_key_exists('orderPoint', $params)) {
|
||||
$data->setOrderPoint($params['orderPoint']);
|
||||
}
|
||||
//set cat
|
||||
if (array_key_exists('cat', $params)) {
|
||||
if ($params['cat'] != '') {
|
||||
if (is_int($params['cat']))
|
||||
$cat = $entityManager->getRepository(CommodityCat::class)->find($params['cat']);
|
||||
else
|
||||
$cat = $entityManager->getRepository(CommodityCat::class)->find($params['cat']['id']);
|
||||
if ($cat) {
|
||||
if ($cat->getBid() == $acc['bid']) {
|
||||
$data->setCat($cat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$entityManager->persist($data);
|
||||
|
||||
//save prices list
|
||||
if (array_key_exists('prices', $params)) {
|
||||
foreach ($params['prices'] as $item) {
|
||||
$priceList = $entityManager->getRepository(PriceList::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'id' => $item['list']['id']
|
||||
]);
|
||||
if ($priceList) {
|
||||
$detail = $entityManager->getRepository(PriceListDetail::class)->findOneBy([
|
||||
'list' => $priceList,
|
||||
'commodity' => $data
|
||||
]);
|
||||
if (!$detail) {
|
||||
$detail = new PriceListDetail;
|
||||
}
|
||||
$detail->setList($priceList);
|
||||
$detail->setCommodity($data);
|
||||
$detail->setPriceSell($item['priceSell']);
|
||||
$detail->setPriceBuy(0);
|
||||
$detail->setMoney($acc['money']);
|
||||
$entityManager->persist($detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
$entityManager->flush();
|
||||
$log->insert('کالا و خدمات', 'کالا / خدمات با نام ' . $params['name'] . ' افزوده/ویرایش شد.', $this->getUser(), $request->headers->get('activeBid'));
|
||||
return $this->json([
|
||||
'Success' => true,
|
||||
'result' => 1,
|
||||
'code' => $data->getId()
|
||||
]);
|
||||
return $this->json($result);
|
||||
}
|
||||
|
||||
#[Route('/api/commodity/units', name: 'app_commodity_units')]
|
||||
|
|
|
@ -848,99 +848,15 @@ class HesabdariController extends AbstractController
|
|||
if ($content = $request->getContent()) {
|
||||
$params = json_decode($content, true);
|
||||
}
|
||||
if (!array_key_exists('type', $params))
|
||||
$this->createNotFoundException();
|
||||
$roll = '';
|
||||
if ($params['type'] == 'person')
|
||||
$roll = 'person';
|
||||
if ($params['type'] == 'person_receive' || $params['type'] == 'person_send')
|
||||
$roll = 'person';
|
||||
elseif ($params['type'] == 'sell_receive')
|
||||
$roll = 'sell';
|
||||
elseif ($params['type'] == 'bank')
|
||||
$roll = 'banks';
|
||||
elseif ($params['type'] == 'buy_send')
|
||||
$roll = 'buy';
|
||||
elseif ($params['type'] == 'transfer')
|
||||
$roll = 'bankTransfer';
|
||||
elseif ($params['type'] == 'all')
|
||||
$roll = 'accounting';
|
||||
else
|
||||
$roll = $params['type'];
|
||||
|
||||
$acc = $access->hasRole($roll);
|
||||
$acc = $access->hasRole($params['type'] ?? 'accounting');
|
||||
if (!$acc)
|
||||
throw $this->createAccessDeniedException();
|
||||
if ($params['type'] == 'person') {
|
||||
$person = $entityManager->getRepository(Person::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['id'],
|
||||
]);
|
||||
if (!$person)
|
||||
throw $this->createNotFoundException();
|
||||
|
||||
$data = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||
'person' => $person,
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
} elseif ($params['type'] == 'bank') {
|
||||
$bank = $entityManager->getRepository(BankAccount::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['id'],
|
||||
]);
|
||||
if (!$bank)
|
||||
throw $this->createNotFoundException();
|
||||
|
||||
$data = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||
'bank' => $bank,
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
} elseif ($params['type'] == 'cashdesk') {
|
||||
$cashdesk = $entityManager->getRepository(Cashdesk::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['id'],
|
||||
]);
|
||||
if (!$cashdesk)
|
||||
throw $this->createNotFoundException();
|
||||
|
||||
$data = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||
'cashdesk' => $cashdesk,
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
} elseif ($params['type'] == 'salary') {
|
||||
$salary = $entityManager->getRepository(Salary::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $params['id'],
|
||||
]);
|
||||
if (!$salary)
|
||||
throw $this->createNotFoundException();
|
||||
|
||||
$data = $entityManager->getRepository(HesabdariRow::class)->findBy([
|
||||
'salary' => $salary,
|
||||
], [
|
||||
'id' => 'DESC'
|
||||
]);
|
||||
$service = new \App\Cog\AccountingDocService($entityManager);
|
||||
$result = $service->searchRows($params, $acc);
|
||||
if (isset($result['error'])) {
|
||||
return $this->json($result, 400);
|
||||
}
|
||||
$dataTemp = [];
|
||||
foreach ($data as $item) {
|
||||
$temp = [
|
||||
'id' => $item->getId(),
|
||||
'dateSubmit' => $item->getDoc()->getDateSubmit(),
|
||||
'date' => $item->getDoc()->getDate(),
|
||||
'type' => $item->getDoc()->getType(),
|
||||
'ref' => $item->getRef()->getName(),
|
||||
'des' => $item->getDes(),
|
||||
'bs' => $item->getBs(),
|
||||
'bd' => $item->getBd(),
|
||||
'code' => $item->getDoc()->getCode(),
|
||||
'submitter' => $item->getDoc()->getSubmitter()->getFullName()
|
||||
];
|
||||
$dataTemp[] = $temp;
|
||||
}
|
||||
return $this->json($dataTemp);
|
||||
return $this->json($result);
|
||||
}
|
||||
|
||||
#[Route('/api/accounting/table/get', name: 'app_accounting_table_get')]
|
||||
|
|
|
@ -238,155 +238,36 @@ class PersonsController extends AbstractController
|
|||
if ($content = $request->getContent()) {
|
||||
$params = json_decode($content, true);
|
||||
}
|
||||
if (!array_key_exists('nikename', $params))
|
||||
return $this->json(['result' => -1]);
|
||||
if (count_chars(trim($params['nikename'])) == 0)
|
||||
return $this->json(['result' => 3]);
|
||||
|
||||
if ($code == 0) {
|
||||
$person = $entityManager->getRepository(Person::class)->findOneBy([
|
||||
'nikename' => $params['nikename'],
|
||||
'bid' => $acc['bid']
|
||||
]);
|
||||
//check exist before
|
||||
if (!$person) {
|
||||
$person = new Person();
|
||||
$maxAttempts = 10; // حداکثر تعداد تلاش برای تولید کد جدید
|
||||
$code = null;
|
||||
|
||||
for ($i = 0; $i < $maxAttempts; $i++) {
|
||||
$code = $provider->getAccountingCode($acc['bid'], 'person');
|
||||
$exist = $entityManager->getRepository(Person::class)->findOneBy([
|
||||
'code' => $code
|
||||
]);
|
||||
if (!$exist) {
|
||||
break;
|
||||
$personService = new \App\Cog\PersonService($entityManager);
|
||||
$result = $personService->addOrUpdatePerson($params, $acc, $code);
|
||||
if (isset($result['error'])) {
|
||||
return $this->json($result, 400);
|
||||
}
|
||||
}
|
||||
|
||||
if ($code === null) {
|
||||
throw new \Exception('نمیتوان کد جدیدی برای شخص تولید کرد');
|
||||
}
|
||||
|
||||
$person->setCode($code);
|
||||
}
|
||||
|
||||
} else {
|
||||
$person = $entityManager->getRepository(Person::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'code' => $code
|
||||
]);
|
||||
if (!$person)
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
$person->setBid($acc['bid']);
|
||||
$person->setNikename($params['nikename']);
|
||||
if (array_key_exists('name', $params))
|
||||
$person->setName($params['name']);
|
||||
if (array_key_exists('birthday', $params))
|
||||
$person->setBirthday($params['birthday']);
|
||||
if (array_key_exists('tel', $params))
|
||||
$person->setTel($params['tel']);
|
||||
if (array_key_exists('speedAccess', $params))
|
||||
$person->setSpeedAccess($params['speedAccess']);
|
||||
if (array_key_exists('address', $params))
|
||||
$person->setAddress($params['address']);
|
||||
if (array_key_exists('des', $params))
|
||||
$person->setDes($params['des']);
|
||||
if (array_key_exists('mobile', $params))
|
||||
$person->setMobile($params['mobile']);
|
||||
if (array_key_exists('mobile2', $params))
|
||||
$person->setMobile2($params['mobile2']);
|
||||
if (array_key_exists('fax', $params))
|
||||
$person->setFax($params['fax']);
|
||||
if (array_key_exists('website', $params))
|
||||
$person->setWebsite($params['website']);
|
||||
if (array_key_exists('email', $params))
|
||||
$person->setEmail($params['email']);
|
||||
if (array_key_exists('postalcode', $params))
|
||||
$person->setPostalcode($params['postalcode']);
|
||||
if (array_key_exists('shahr', $params))
|
||||
$person->setShahr($params['shahr']);
|
||||
if (array_key_exists('ostan', $params))
|
||||
$person->setOstan($params['ostan']);
|
||||
if (array_key_exists('keshvar', $params))
|
||||
$person->setKeshvar($params['keshvar']);
|
||||
if (array_key_exists('sabt', $params))
|
||||
$person->setSabt($params['sabt']);
|
||||
if (array_key_exists('codeeghtesadi', $params))
|
||||
$person->setCodeeghtesadi($params['codeeghtesadi']);
|
||||
if (array_key_exists('shenasemeli', $params))
|
||||
$person->setShenasemeli($params['shenasemeli']);
|
||||
if (array_key_exists('company', $params))
|
||||
$person->setCompany($params['company']);
|
||||
if (array_key_exists('prelabel', $params)) {
|
||||
if ($params['prelabel'] != '') {
|
||||
$prelabel = $entityManager->getRepository(PersonPrelabel::class)->findOneBy(['label' => $params['prelabel']]);
|
||||
if ($prelabel) {
|
||||
$person->setPrelabel($prelabel);
|
||||
}
|
||||
}
|
||||
elseif ($params['prelabel'] == null) {
|
||||
$person->setPrelabel(null);
|
||||
}
|
||||
}
|
||||
//inset cards
|
||||
if (array_key_exists('accounts', $params)) {
|
||||
foreach ($params['accounts'] as $item) {
|
||||
$card = $entityManager->getRepository(PersonCard::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'person' => $person,
|
||||
'bank' => $item['bank']
|
||||
]);
|
||||
if (!$card)
|
||||
$card = new PersonCard();
|
||||
|
||||
$card->setPerson($person);
|
||||
$card->setBid($acc['bid']);
|
||||
$card->setShabaNum($item['shabaNum']);
|
||||
$card->setCardNum($item['cardNum']);
|
||||
$card->setAccountNum($item['accountNum']);
|
||||
$card->setBank($item['bank']);
|
||||
$entityManager->persist($card);
|
||||
}
|
||||
}
|
||||
//remove not sended accounts
|
||||
$accounts = $entityManager->getRepository(PersonCard::class)->findBy([
|
||||
'bid' => $acc['bid'],
|
||||
'person' => $person,
|
||||
]);
|
||||
foreach ($accounts as $item) {
|
||||
$deleted = true;
|
||||
foreach ($params['accounts'] as $param) {
|
||||
if ($item->getBank() == $param['bank']) {
|
||||
$deleted = false;
|
||||
}
|
||||
}
|
||||
if ($deleted) {
|
||||
$entityManager->remove($item);
|
||||
}
|
||||
}
|
||||
$entityManager->persist($person);
|
||||
|
||||
//insert new types
|
||||
$types = $entityManager->getRepository(PersonType::class)->findAll();
|
||||
foreach ($params['types'] as $item) {
|
||||
if ($item['checked'] == true)
|
||||
$person->addType($entityManager->getRepository(PersonType::class)->findOneBy([
|
||||
'code' => $item['code']
|
||||
]));
|
||||
elseif ($item['checked'] == false) {
|
||||
$person->removeType($entityManager->getRepository(PersonType::class)->findOneBy([
|
||||
'code' => $item['code']
|
||||
]));
|
||||
}
|
||||
}
|
||||
$entityManager->flush();
|
||||
$log->insert('اشخاص', 'شخص با نام مستعار ' . $params['nikename'] . ' افزوده/ویرایش شد.', $this->getUser(), $acc['bid']);
|
||||
return $this->json([
|
||||
'Success' => true,
|
||||
'result' => 1,
|
||||
]);
|
||||
return $this->json($result);
|
||||
}
|
||||
|
||||
#[Route('/api/person/list', name: 'app_persons_list', methods: ['POST'])]
|
||||
public function app_persons_list(
|
||||
Provider $provider,
|
||||
Request $request,
|
||||
Access $access,
|
||||
Log $log,
|
||||
EntityManagerInterface $entityManager
|
||||
): JsonResponse {
|
||||
$acc = $access->hasRole('person');
|
||||
if (!$acc) {
|
||||
var_dump($acc);
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$params = json_decode($request->getContent(), true) ?? [];
|
||||
|
||||
// استفاده از سرویس جدید
|
||||
$personService = new \App\Cog\PersonService($entityManager);
|
||||
$result = $personService->getPersonsList($params, $acc);
|
||||
|
||||
return new JsonResponse($result);
|
||||
}
|
||||
|
||||
#[Route('/api/person/list/search', name: 'app_persons_list_search')]
|
||||
|
@ -478,115 +359,6 @@ class PersonsController extends AbstractController
|
|||
return $this->json($response);
|
||||
}
|
||||
|
||||
#[Route('/api/person/list', name: 'app_persons_list', methods: ['POST'])]
|
||||
public function app_persons_list(
|
||||
Provider $provider,
|
||||
Request $request,
|
||||
Access $access,
|
||||
Log $log,
|
||||
EntityManagerInterface $entityManager
|
||||
): JsonResponse {
|
||||
$acc = $access->hasRole('person');
|
||||
if (!$acc) {
|
||||
var_dump($acc);
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$params = json_decode($request->getContent(), true) ?? [];
|
||||
$page = $params['page'] ?? 1;
|
||||
$itemsPerPage = $params['itemsPerPage'] ?? 10;
|
||||
$search = $params['search'] ?? '';
|
||||
$types = $params['types'] ?? null;
|
||||
$transactionFilters = $params['transactionFilters'] ?? null;
|
||||
|
||||
// کوئری اصلی برای گرفتن همه اشخاص
|
||||
$queryBuilder = $entityManager->getRepository(\App\Entity\Person::class)
|
||||
->createQueryBuilder('p')
|
||||
->where('p.bid = :bid')
|
||||
->setParameter('bid', $acc['bid']);
|
||||
|
||||
// جستوجو (بهبود دادهشده)
|
||||
if (!empty($search) || $search === '0') { // برای اطمینان از کار با "0" یا خالی
|
||||
$search = trim($search); // حذف فضای خالی اضافی
|
||||
$queryBuilder->andWhere('p.nikename LIKE :search OR p.name LIKE :search OR p.code LIKE :search OR p.mobile LIKE :search')
|
||||
->setParameter('search', "%$search%");
|
||||
}
|
||||
|
||||
// فیلتر نوع اشخاص
|
||||
if ($types && !empty($types)) {
|
||||
$queryBuilder->leftJoin('p.type', 't')
|
||||
->andWhere('t.code IN (:types)')
|
||||
->setParameter('types', $types);
|
||||
}
|
||||
|
||||
// تعداد کل (قبل از فیلتر تراکنشها)
|
||||
$totalItems = (clone $queryBuilder)
|
||||
->select('COUNT(p.id)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
|
||||
// گرفتن اشخاص با صفحهبندی
|
||||
$persons = $queryBuilder
|
||||
->select('p')
|
||||
->setFirstResult(($page - 1) * $itemsPerPage)
|
||||
->setMaxResults($itemsPerPage)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// محاسبه تراکنشها و اعمال فیلتر تراکنشها
|
||||
$response = [];
|
||||
foreach ($persons as $person) {
|
||||
$rows = $entityManager->getRepository(\App\Entity\HesabdariRow::class)->findBy([
|
||||
'person' => $person,
|
||||
'bid' => $acc['bid']
|
||||
]);
|
||||
$bs = 0; // بستانکار
|
||||
$bd = 0; // بدهکار
|
||||
foreach ($rows as $row) {
|
||||
$doc = $row->getDoc();
|
||||
if ($doc && $doc->getMoney() && $doc->getYear() &&
|
||||
$doc->getMoney()->getId() == $acc['money']->getId() &&
|
||||
$doc->getYear()->getId() == $acc['year']->getId()) {
|
||||
$bs += (float) $row->getBs(); // بستانکار
|
||||
$bd += (float) $row->getBd(); // بدهکار
|
||||
}
|
||||
}
|
||||
$balance = $bs - $bd; // تراز = بستانکار - بدهکار
|
||||
|
||||
// اعمال فیلتر transactionFilters
|
||||
$include = true;
|
||||
if ($transactionFilters && !empty($transactionFilters)) {
|
||||
$include = false;
|
||||
if (in_array('debtors', $transactionFilters) && $balance < 0) { // بدهکارها (تراز منفی)
|
||||
$include = true;
|
||||
}
|
||||
if (in_array('creditors', $transactionFilters) && $balance > 0) { // بستانکارها (تراز مثبت)
|
||||
$include = true;
|
||||
}
|
||||
if (in_array('zero', $transactionFilters) && $balance == 0) { // تسویهشدهها
|
||||
$include = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($include) {
|
||||
$result = Explore::ExplorePerson($person, $entityManager->getRepository(PersonType::class)->findAll());
|
||||
$result['bs'] = $bs;
|
||||
$result['bd'] = $bd;
|
||||
$result['balance'] = $balance;
|
||||
$response[] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
// تعداد آیتمهای فیلترشده
|
||||
$filteredTotal = count($response);
|
||||
|
||||
return new JsonResponse([
|
||||
'items' => array_slice($response, 0, $itemsPerPage), // فقط تعداد درخواستی
|
||||
'total' => $filteredTotal, // تعداد کل فیلترشده
|
||||
'unfilteredTotal' => $totalItems, // تعداد کل بدون فیلتر (اختیاری)
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/api/person/list/debtors/{amount}', name: 'app_persons_list_debtors')]
|
||||
public function app_persons_list_debtors(string $amount, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
|
|
|
@ -29,7 +29,8 @@ class wizardController extends AbstractController
|
|||
Request $request,
|
||||
Access $access,
|
||||
EntityManagerInterface $entityManager,
|
||||
Log $log
|
||||
Log $log,
|
||||
\App\Service\registryMGR $registryMGR
|
||||
): JsonResponse
|
||||
{
|
||||
|
||||
|
@ -73,6 +74,53 @@ class wizardController extends AbstractController
|
|||
$options = $params['options'] ?? [];
|
||||
$conversationId = $params['conversationId'] ?? null;
|
||||
|
||||
// بررسی امنیتی conversationId
|
||||
if ($conversationId) {
|
||||
$conversation = $entityManager->getRepository(AIConversation::class)->find($conversationId);
|
||||
if ($conversation) {
|
||||
// بررسی دسترسی کاربر به این گفتگو
|
||||
if ($conversation->getUser()->getId() !== $acc['user']->getId() ||
|
||||
$conversation->getBusiness()->getId() !== $acc['bid']->getId()) {
|
||||
|
||||
$log->warning('تلاش غیرمجاز برای دسترسی به گفتگوی دیگران در wizard_talk', [
|
||||
'conversationId' => $conversationId,
|
||||
'requestedUser' => $acc['user']->getId(),
|
||||
'requestedBusiness' => $acc['bid']->getId(),
|
||||
'conversationUser' => $conversation->getUser()->getId(),
|
||||
'conversationBusiness' => $conversation->getBusiness()->getId()
|
||||
]);
|
||||
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'error' => 'دسترسی غیرمجاز به گفتگو',
|
||||
'debug_info' => [
|
||||
'conversationId' => $conversationId
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// بررسی حذف شدن گفتگو
|
||||
if ($conversation->isDeleted()) {
|
||||
$log->info('تلاش برای دسترسی به گفتگوی حذف شده در wizard_talk', [
|
||||
'conversationId' => $conversationId,
|
||||
'user' => $acc['user']->getId()
|
||||
]);
|
||||
|
||||
// گفتگوی جدید ایجاد میشود
|
||||
$conversationId = null;
|
||||
}
|
||||
} else {
|
||||
$log->warning('تلاش برای دسترسی به گفتگوی ناموجود در wizard_talk', [
|
||||
'conversationId' => $conversationId,
|
||||
'user' => $acc['user']->getId(),
|
||||
'business' => $acc['bid']->getId()
|
||||
]);
|
||||
|
||||
// گفتگوی جدید ایجاد میشود
|
||||
$conversationId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// بررسی فعال بودن هوش مصنوعی
|
||||
$aiStatus = $this->agiService->checkAIServiceStatus();
|
||||
if (!$aiStatus['isEnabled']) {
|
||||
|
@ -110,19 +158,31 @@ class wizardController extends AbstractController
|
|||
// استفاده از AGIService برای مدیریت گفتگو و ارسال درخواست
|
||||
$result = $this->agiService->sendRequest($message, $business, $acc['user'], $conversationId, $acc);
|
||||
|
||||
// دریافت وضعیت نمایش دیباگ از تنظیمات سیستم
|
||||
$aiDebugMode = false;
|
||||
if ($registryMGR) {
|
||||
$aiDebugMode = $registryMGR->get('system', 'aiDebugMode') === '1' || $registryMGR->get('system', 'aiDebugMode') === true;
|
||||
}
|
||||
|
||||
if ($result['success']) {
|
||||
$responseContent = $result['response'] ?? $result['message'] ?? 'عملیات با موفقیت انجام شد';
|
||||
|
||||
$response = [
|
||||
'success' => true,
|
||||
'response' => $responseContent,
|
||||
'conversationId' => $result['conversation_id'] ?? null,
|
||||
'conversationId' => $result['conversationId'] ?? $result['conversation_id'] ?? null,
|
||||
'model' => $result['model'] ?? null,
|
||||
'usage' => $result['usage'] ?? null,
|
||||
'cost' => $result['cost'] ?? null,
|
||||
'debug_info' => $result['debug_info'] ?? null
|
||||
];
|
||||
|
||||
// اگر دیباگ خاموش بود، debug_info و model را حذف کن
|
||||
if (!$aiDebugMode) {
|
||||
unset($response['debug_info']);
|
||||
unset($response['model']);
|
||||
}
|
||||
|
||||
// محاسبه هزینه در صورت وجود اطلاعات usage
|
||||
if (isset($result['cost'])) {
|
||||
$cost = $result['cost'];
|
||||
|
@ -217,5 +277,138 @@ class wizardController extends AbstractController
|
|||
}
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/conversations/list', name: 'wizard_conversations_list', methods: ['POST'])]
|
||||
public function wizard_conversations_list(Request $request, Access $access, EntityManagerInterface $entityManager, \App\Service\Jdate $jdate): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
return $this->json(['success' => false, 'error' => 'دسترسی غیرمجاز']);
|
||||
}
|
||||
$params = json_decode($request->getContent(), true) ?? [];
|
||||
$search = $params['search'] ?? '';
|
||||
$category = $params['category'] ?? '';
|
||||
$conversationRepo = $entityManager->getRepository(\App\Entity\AIConversation::class);
|
||||
if (!empty($search)) {
|
||||
$conversations = $conversationRepo->searchByTitle($acc['user'], $acc['bid'], $search);
|
||||
} elseif (!empty($category)) {
|
||||
$conversations = $conversationRepo->findByCategory($acc['user'], $acc['bid'], $category);
|
||||
} else {
|
||||
$conversations = $conversationRepo->findActiveConversations($acc['user'], $acc['bid']);
|
||||
}
|
||||
$result = [];
|
||||
foreach ($conversations as $conversation) {
|
||||
$messageRepo = $entityManager->getRepository(\App\Entity\AIMessage::class);
|
||||
$lastMessage = $messageRepo->findLastMessageByConversation($conversation);
|
||||
$result[] = [
|
||||
'id' => $conversation->getId(),
|
||||
'title' => $conversation->getTitle(),
|
||||
'category' => $conversation->getCategory(),
|
||||
'createdAt' => $jdate->jdate('Y/m/d H:i', $conversation->getCreatedAt()),
|
||||
'updatedAt' => $jdate->jdate('Y/m/d H:i', $conversation->getUpdatedAt()),
|
||||
'messageCount' => count($conversation->getMessages()),
|
||||
'lastMessage' => $lastMessage ? $lastMessage->getContent() : ''
|
||||
];
|
||||
}
|
||||
return $this->json(['success' => true, 'items' => $result]);
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/conversations/create', name: 'wizard_conversations_create', methods: ['POST'])]
|
||||
public function wizard_conversations_create(Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
return $this->json(['success' => false, 'error' => 'دسترسی غیرمجاز']);
|
||||
}
|
||||
$params = json_decode($request->getContent(), true) ?? [];
|
||||
$title = $params['title'] ?? 'گفتگوی جدید';
|
||||
$category = $params['category'] ?? 'عمومی';
|
||||
$conversation = new \App\Entity\AIConversation();
|
||||
$conversation->setUser($acc['user']);
|
||||
$conversation->setBusiness($acc['bid']);
|
||||
$conversation->setTitle($title);
|
||||
$conversation->setCategory($category);
|
||||
$entityManager->persist($conversation);
|
||||
$entityManager->flush();
|
||||
return $this->json(['success' => true, 'id' => $conversation->getId(), 'title' => $conversation->getTitle(), 'category' => $conversation->getCategory(), 'createdAt' => $conversation->getCreatedAt()]);
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/conversations/{id}/delete', name: 'wizard_conversations_delete', methods: ['POST'])]
|
||||
public function wizard_conversations_delete(int $id, Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
return $this->json(['success' => false, 'error' => 'دسترسی غیرمجاز']);
|
||||
}
|
||||
$conversation = $entityManager->getRepository(\App\Entity\AIConversation::class)->find($id);
|
||||
if (!$conversation) {
|
||||
return $this->json(['success' => false, 'error' => 'گفتگو یافت نشد']);
|
||||
}
|
||||
if ($conversation->getUser()->getId() !== $acc['user']->getId() || $conversation->getBusiness()->getId() !== $acc['bid']->getId()) {
|
||||
return $this->json(['success' => false, 'error' => 'دسترسی غیرمجاز']);
|
||||
}
|
||||
if ($conversation->isDeleted()) {
|
||||
return $this->json(['success' => false, 'error' => 'این گفتگو قبلاً حذف شده است']);
|
||||
}
|
||||
$conversation->setDeleted(true);
|
||||
$entityManager->persist($conversation);
|
||||
$entityManager->flush();
|
||||
return $this->json(['success' => true]);
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/conversations/{id}/messages', name: 'wizard_conversations_messages', methods: ['POST'])]
|
||||
public function wizard_conversations_messages(int $id, Access $access, EntityManagerInterface $entityManager, \App\Service\Jdate $jdate): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
return $this->json(['success' => false, 'error' => 'دسترسی غیرمجاز']);
|
||||
}
|
||||
$conversation = $entityManager->getRepository(\App\Entity\AIConversation::class)->find($id);
|
||||
if (!$conversation) {
|
||||
return $this->json(['success' => false, 'error' => 'گفتگو یافت نشد']);
|
||||
}
|
||||
if ($conversation->getUser()->getId() !== $acc['user']->getId() || $conversation->getBusiness()->getId() !== $acc['bid']->getId()) {
|
||||
return $this->json(['success' => false, 'error' => 'دسترسی غیرمجاز']);
|
||||
}
|
||||
$messageRepo = $entityManager->getRepository(\App\Entity\AIMessage::class);
|
||||
$messages = $messageRepo->findByConversation($conversation);
|
||||
$result = [];
|
||||
foreach ($messages as $message) {
|
||||
$result[] = [
|
||||
'id' => $message->getId(),
|
||||
'role' => $message->getRole(),
|
||||
'content' => $message->getContent(),
|
||||
'createdAt' => $jdate->jdate('Y/m/d H:i', $message->getCreatedAt())
|
||||
];
|
||||
}
|
||||
return $this->json(['success' => true, 'items' => $result]);
|
||||
}
|
||||
|
||||
#[Route('/api/wizard/conversations/delete-all', name: 'wizard_conversations_delete_all', methods: ['POST'])]
|
||||
public function wizard_conversations_delete_all(Access $access, EntityManagerInterface $entityManager): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('join');
|
||||
if (!$acc) {
|
||||
return $this->json(['success' => false, 'error' => 'دسترسی غیرمجاز']);
|
||||
}
|
||||
$userId = $acc['user']->getId();
|
||||
$businessId = $acc['bid']->getId();
|
||||
$repo = $entityManager->getRepository(AIConversation::class);
|
||||
$convs = $repo->createQueryBuilder('c')
|
||||
->where('c.user = :user')
|
||||
->andWhere('c.business = :business')
|
||||
->andWhere('c.deleted = false')
|
||||
->setParameter('user', $userId)
|
||||
->setParameter('business', $businessId)
|
||||
->getQuery()->getResult();
|
||||
$count = 0;
|
||||
foreach ($convs as $conv) {
|
||||
$conv->setDeleted(true);
|
||||
$entityManager->persist($conv);
|
||||
$count++;
|
||||
}
|
||||
$entityManager->flush();
|
||||
return $this->json(['success' => true, 'deleted' => $count]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -102,6 +102,11 @@ class AGIService
|
|||
$result = $this->sendToAIServiceWithFunctionCalling($userPrompt, $apiKey, $service, $conversationHistory, $acc);
|
||||
|
||||
if (!$result['success']) {
|
||||
// اگر system_prompt در debug_info وجود داشت، به خروجی خطا اضافه کن
|
||||
if (isset($result['debug_info']['system_prompt'])) {
|
||||
if (!isset($result['debug_info'])) $result['debug_info'] = [];
|
||||
$result['debug_info']['system_prompt'] = $result['debug_info']['system_prompt'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
@ -112,18 +117,24 @@ class AGIService
|
|||
// ذخیره پاسخ هوش مصنوعی
|
||||
$this->saveAIMessage($conversation, $aiResponse, $result['data'], $cost);
|
||||
|
||||
$debugInfo = [
|
||||
'context' => 'sendRequest',
|
||||
'function_calls' => $result['function_calls'] ?? [],
|
||||
'tool_results' => $result['tool_results'] ?? [],
|
||||
'conversation_history' => $conversationHistory
|
||||
];
|
||||
// اگر system_prompt در debug_info خروجی قبلی بود، به debug_info اضافه کن
|
||||
if (isset($result['debug_info']['system_prompt'])) {
|
||||
$debugInfo['system_prompt'] = $result['debug_info']['system_prompt'];
|
||||
}
|
||||
return [
|
||||
'success' => true,
|
||||
'response' => $aiResponse,
|
||||
'conversationId' => $conversation->getId(),
|
||||
'conversationId' => $conversation->getId(), // مقداردهی صحیح conversationId
|
||||
'model' => $this->getAIModel(),
|
||||
'usage' => $result['data']['usage'] ?? [],
|
||||
'cost' => $cost,
|
||||
'debug_info' => [
|
||||
'context' => 'sendRequest',
|
||||
'function_calls' => $result['function_calls'] ?? [],
|
||||
'tool_results' => $result['tool_results'] ?? []
|
||||
]
|
||||
'debug_info' => $debugInfo
|
||||
];
|
||||
try {
|
||||
} catch (\Exception $e) {
|
||||
|
@ -149,24 +160,15 @@ class AGIService
|
|||
{
|
||||
$urls = $this->getServiceUrls($service);
|
||||
$model = $this->getAIModel();
|
||||
|
||||
// پیام system شامل قوانین خروجی و مثال دقیق
|
||||
$systemPrompt = "شما دستیار هوشمند حسابیکس هستید. فقط پاسخ را به صورت JSON مطابق مثال خروجی بده. اگر نیاز به ابزار داشتی، از function calling استفاده کن."
|
||||
. $this->promptService->getOutputFormatPrompt();
|
||||
// ساخت پرامپت system با buildSmartPrompt
|
||||
$systemPrompt = $this->buildSmartPrompt($prompt, $acc['bid'] ?? null, $conversationHistory);
|
||||
$messages = [
|
||||
[
|
||||
'role' => 'system',
|
||||
'content' => $systemPrompt
|
||||
]
|
||||
];
|
||||
// تاریخچه گفتگو
|
||||
foreach ($conversationHistory as $historyItem) {
|
||||
$messages[] = [
|
||||
'role' => $historyItem['role'],
|
||||
'content' => $historyItem['content']
|
||||
];
|
||||
}
|
||||
// پیام user فقط سوال فعلی
|
||||
// تاریخچه گفتگو و پیام user دیگر اینجا اضافه نمیشود چون در buildSmartPrompt لحاظ شده است
|
||||
$messages[] = [
|
||||
'role' => 'user',
|
||||
'content' => $prompt
|
||||
|
@ -212,7 +214,8 @@ class AGIService
|
|||
'apiKey' => $apiKey,
|
||||
'service' => $service,
|
||||
'conversationHistory' => $conversationHistory,
|
||||
|
||||
'system_prompt' => $systemPrompt,
|
||||
'messages_to_ai' => $messages,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
@ -227,7 +230,9 @@ class AGIService
|
|||
'debug_info' => [
|
||||
'context' => 'sendToAIServiceWithFunctionCalling',
|
||||
'response_data' => $responseData,
|
||||
'iteration' => $iteration
|
||||
'iteration' => $iteration,
|
||||
'system_prompt' => $systemPrompt,
|
||||
'messages_to_ai' => $messages,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
@ -242,7 +247,11 @@ class AGIService
|
|||
'success' => true,
|
||||
'data' => $responseData,
|
||||
'function_calls' => $functionCalls,
|
||||
'tool_results' => $toolResults
|
||||
'tool_results' => $toolResults,
|
||||
'debug_info' => [
|
||||
'system_prompt' => $systemPrompt,
|
||||
'messages_to_ai' => $messages,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -292,7 +301,9 @@ class AGIService
|
|||
'context' => 'sendToAIServiceWithFunctionCalling',
|
||||
'max_iterations' => $maxIterations,
|
||||
'function_calls' => $functionCalls,
|
||||
'tool_results' => $toolResults
|
||||
'tool_results' => $toolResults,
|
||||
'system_prompt' => $systemPrompt,
|
||||
'messages_to_ai' => $messages,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
@ -305,10 +316,25 @@ class AGIService
|
|||
try {
|
||||
switch ($tool) {
|
||||
case 'getPersonInfo':
|
||||
// استفاده مستقیم از سرویس جدید
|
||||
$cogPersonService = new \App\Cog\PersonService($this->em, $params['acc'] ?? null);
|
||||
$cogPersonService = new \App\Cog\PersonService($this->em);
|
||||
$personService = new \App\AiTool\PersonService($this->em, $cogPersonService);
|
||||
return $personService->getPersonInfoByCode($params['code'] ?? null, $params['acc'] ?? null);
|
||||
case 'getPersonsList':
|
||||
$cogPersonService = new \App\Cog\PersonService($this->em);
|
||||
$personService = new \App\AiTool\PersonService($this->em, $cogPersonService);
|
||||
return $personService->getPersonsListAi($params, $params['acc'] ?? null);
|
||||
case 'addOrUpdatePerson':
|
||||
$cogPersonService = new \App\Cog\PersonService($this->em);
|
||||
$personService = new \App\AiTool\PersonService($this->em, $cogPersonService);
|
||||
return $personService->addOrUpdatePersonAi($params, $params['acc'] ?? null, $params['code'] ?? 0);
|
||||
case 'addOrUpdateCommodity':
|
||||
$cogCommodityService = new \App\Cog\CommodityService($this->em);
|
||||
$commodityService = new \App\AiTool\CommodityService($this->em, $cogCommodityService);
|
||||
return $commodityService->addOrUpdateCommodityAi($params, $params['acc'] ?? null, $params['code'] ?? 0);
|
||||
case 'searchAccountingRows':
|
||||
$cogAccountingDocService = new \App\Cog\AccountingDocService($this->em);
|
||||
$accountingDocService = new \App\AiTool\AccountingDocService($this->em, $cogAccountingDocService);
|
||||
return $accountingDocService->searchRowsAi($params, $params['acc'] ?? null);
|
||||
default:
|
||||
return [
|
||||
'error' => 'ابزار ناشناخته: ' . $tool
|
||||
|
@ -326,28 +352,41 @@ class AGIService
|
|||
*/
|
||||
private function buildSmartPrompt(string $message, ?Business $business, array $conversationHistory = []): string
|
||||
{
|
||||
// دریافت پرامپهای پایه از PromptService
|
||||
$basePrompts = $this->promptService->getAllBasePrompts();
|
||||
$prompt = $basePrompts;
|
||||
// دریافت aiPrompt مدیر سیستم و قوانین خروجی
|
||||
$aiPrompt = $this->registryMGR->get('system', 'aiPrompt');
|
||||
$outputFormatPrompt = $this->promptService->getOutputFormatPrompt();
|
||||
$basePrompt = "شما دستیار هوشمند حسابیکس هستید. فقط پاسخ را به صورت JSON مطابق مثال خروجی بده. اگر نیاز به ابزار داشتی، از function calling استفاده کن.";
|
||||
|
||||
// قوانین خروجی JSON و مثالها از سرویس مدیریت پرامپتها
|
||||
$prompt .= $this->promptService->getOutputFormatPrompt();
|
||||
$parts = [];
|
||||
// اضافه کردن aiPrompt اگر تکراری نبود
|
||||
if ($aiPrompt && strpos($basePrompt, trim($aiPrompt)) === false && strpos($outputFormatPrompt, trim($aiPrompt)) === false) {
|
||||
$parts[] = trim($aiPrompt);
|
||||
}
|
||||
// اضافه کردن basePrompt اگر تکراری نبود
|
||||
if (strpos($outputFormatPrompt, trim($basePrompt)) === false) {
|
||||
$parts[] = trim($basePrompt);
|
||||
}
|
||||
// قوانین خروجی اگر تکراری نبود
|
||||
$parts[] = trim($outputFormatPrompt);
|
||||
|
||||
// اضافه کردن اطلاعات کسب و کار
|
||||
if ($business) {
|
||||
$prompt .= "\n\nاطلاعات کسب و کار: نام: {$business->getName()}, کد اقتصادی: {$business->getCodeeghtesadi()}.";
|
||||
$parts[] = "اطلاعات کسب و کار: نام: {$business->getName()}, کد اقتصادی: {$business->getCodeeghtesadi()}.";
|
||||
}
|
||||
// اضافه کردن تاریخچه گفتگو
|
||||
if (!empty($conversationHistory)) {
|
||||
$prompt .= "\n\n📜 تاریخچه گفتگو:\n";
|
||||
$historyText = "\n📜 تاریخچه گفتگو:\n";
|
||||
foreach ($conversationHistory as $historyItem) {
|
||||
$role = $historyItem['role'] === 'user' ? 'کاربر' : 'دستیار';
|
||||
$prompt .= "{$role}: {$historyItem['content']}\n";
|
||||
$historyText .= "{$role}: {$historyItem['content']}\n";
|
||||
}
|
||||
$prompt .= "\n💡 نکته: لطفاً context گفتگو را حفظ کنید و به سوالات قبلی مراجعه کنید.";
|
||||
$historyText .= "\n💡 نکته: لطفاً context گفتگو را حفظ کنید و به سوالات قبلی مراجعه کنید.";
|
||||
$parts[] = $historyText;
|
||||
}
|
||||
$prompt .= "\n\nسوال کاربر: " . $message;
|
||||
return $prompt;
|
||||
// سوال کاربر
|
||||
$parts[] = "سوال کاربر: " . $message;
|
||||
// ادغام نهایی
|
||||
return implode("\n\n", array_filter($parts));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -604,8 +643,25 @@ class AGIService
|
|||
if ($conversationId) {
|
||||
// بازیابی گفتگوی موجود
|
||||
$conversation = $this->em->getRepository(AIConversation::class)->find($conversationId);
|
||||
if ($conversation && $conversation->getUser() === $user && $conversation->getBusiness() === $business) {
|
||||
// بهروزرسانی زمان آخرین تغییر
|
||||
|
||||
// بررسی وجود گفتگو
|
||||
if (!$conversation) {
|
||||
// ایجاد گفتگوی جدید به جای خطا
|
||||
}
|
||||
// بررسی دسترسی کاربر به گفتگو
|
||||
elseif ($conversation->getUser()->getId() !== $user->getId() ||
|
||||
$conversation->getBusiness()->getId() !== $business->getId()) {
|
||||
// ایجاد گفتگوی جدید به جای خطا
|
||||
$conversation = null;
|
||||
}
|
||||
// بررسی حذف شدن گفتگو
|
||||
elseif ($conversation->isDeleted()) {
|
||||
// ایجاد گفتگوی جدید به جای خطا
|
||||
$conversation = null;
|
||||
}
|
||||
|
||||
// اگر گفتگو معتبر است، بهروزرسانی زمان
|
||||
if ($conversation && !$conversation->isDeleted()) {
|
||||
$conversation->setUpdatedAt(time());
|
||||
$this->em->persist($conversation);
|
||||
return $conversation;
|
||||
|
@ -624,6 +680,8 @@ class AGIService
|
|||
$conversation->setDeleted(false);
|
||||
|
||||
$this->em->persist($conversation);
|
||||
$this->em->flush(); // اضافه شد تا id مقداردهی شود
|
||||
|
||||
return $conversation;
|
||||
}
|
||||
|
||||
|
@ -758,7 +816,36 @@ class AGIService
|
|||
{
|
||||
$conversation = $this->em->getRepository(AIConversation::class)->find($conversationId);
|
||||
|
||||
if (!$conversation || $conversation->getUser() !== $user || $conversation->getBusiness() !== $business) {
|
||||
// بررسی وجود گفتگو
|
||||
if (!$conversation) {
|
||||
$this->log->warning('تلاش برای دریافت پیامهای گفتگوی ناموجود', [
|
||||
'conversationId' => $conversationId,
|
||||
'user' => $user ? $user->getId() : 'unknown',
|
||||
'business' => $business ? $business->getId() : 'unknown'
|
||||
]);
|
||||
return [];
|
||||
}
|
||||
|
||||
// بررسی دسترسی کاربر به گفتگو
|
||||
if ($conversation->getUser()->getId() !== $user->getId() ||
|
||||
$conversation->getBusiness()->getId() !== $business->getId()) {
|
||||
|
||||
$this->log->warning('تلاش غیرمجاز برای دریافت پیامهای گفتگوی دیگران', [
|
||||
'conversationId' => $conversationId,
|
||||
'requestedUser' => $user ? $user->getId() : 'unknown',
|
||||
'requestedBusiness' => $business ? $business->getId() : 'unknown',
|
||||
'conversationUser' => $conversation->getUser()->getId(),
|
||||
'conversationBusiness' => $conversation->getBusiness()->getId()
|
||||
]);
|
||||
return [];
|
||||
}
|
||||
|
||||
// بررسی حذف شدن گفتگو
|
||||
if ($conversation->isDeleted()) {
|
||||
$this->log->info('تلاش برای دریافت پیامهای گفتگوی حذف شده', [
|
||||
'conversationId' => $conversationId,
|
||||
'user' => $user ? $user->getId() : 'unknown'
|
||||
]);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service\AGI\Promps;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class AccountingDocPromptService
|
||||
{
|
||||
private $em;
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
}
|
||||
/**
|
||||
* ابزارهای بخش اسناد حسابداری برای function calling
|
||||
*/
|
||||
public function getTools(): array
|
||||
{
|
||||
$tools = [];
|
||||
// ابزار جستوجوی ردیفهای اسناد
|
||||
$searchRowsPrompt = $this->getSearchAccountingRowsPrompt();
|
||||
$searchRowsData = json_decode($searchRowsPrompt, true);
|
||||
if ($searchRowsData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $searchRowsData['tool'],
|
||||
'description' => $searchRowsData['description'],
|
||||
'parameters' => $searchRowsData['parameters']
|
||||
]
|
||||
];
|
||||
}
|
||||
return $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپهای توضیحی برای مدل
|
||||
*/
|
||||
public function getAllAccountingDocPrompts(): string
|
||||
{
|
||||
return $this->getSearchAccountingRowsPrompt();
|
||||
}
|
||||
|
||||
public function getSearchAccountingRowsPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "searchAccountingRows",
|
||||
"description": "این ابزار برای جستوجوی تراکنشها و ردیفهای اسناد حسابداری اشخاص، حسابهای بانکی، صندوق، حقوق و ... استفاده میشود. برای استفاده، ابتدا باید با ابزارهای جستوجوی اشخاص یا حسابهای بانکی و ...، کد (code) یا شناسه مورد نظر را به دست آورید. سپس با ارسال نوع (type) مناسب (مانند person، bank، cashdesk، salary و ...) و کد (id)، میتوانید لیست تراکنشها یا ردیفهای مرتبط را دریافت کنید. خروجی شامل لیست ردیفها با اطلاعات کامل است.",
|
||||
"endpoint": "/api/accounting/rows/search",
|
||||
"method": "POST",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {"type": "string", "description": "نوع جستوجو (person, bank, cashdesk, salary و ...)"},
|
||||
"id": {"type": ["string", "integer"], "description": "کد یا شناسه مورد جستوجو"},
|
||||
"acc": {"type": "object", "description": "اطلاعات دسترسی (الزامی برای بکاند)"}
|
||||
},
|
||||
"required": ["type", "id"]
|
||||
},
|
||||
"output": [
|
||||
{
|
||||
"id": "integer",
|
||||
"dateSubmit": "string",
|
||||
"date": "string",
|
||||
"type": "string",
|
||||
"ref": "string",
|
||||
"des": "string",
|
||||
"bs": "string",
|
||||
"bd": "string",
|
||||
"code": "string",
|
||||
"submitter": "string"
|
||||
}
|
||||
],
|
||||
"examples": {
|
||||
"input": {"type":"person","id":"1001","acc":{"bid":2,"user":2,"year":2,"access":true,"money":1,"ai":true}},
|
||||
"output": [
|
||||
{
|
||||
"id": 9,
|
||||
"dateSubmit": "1753370911",
|
||||
"date": "1404/05/02",
|
||||
"type": "sell",
|
||||
"ref": "حسابهای دریافتی",
|
||||
"des": "فاکتور فروش",
|
||||
"bs": "0",
|
||||
"bd": "210000",
|
||||
"code": "1000",
|
||||
"submitter": "بابک"
|
||||
}
|
||||
]
|
||||
}
|
||||
}';
|
||||
}
|
||||
}
|
|
@ -5,130 +5,35 @@ namespace App\Service\AGI\Promps;
|
|||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Service\Access;
|
||||
use App\Entity\APIToken;
|
||||
use App\Service\registryMGR;
|
||||
|
||||
class BasePromptService
|
||||
{
|
||||
private $em;
|
||||
private $access;
|
||||
private $registryMGR;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, Access $access)
|
||||
{
|
||||
public function __construct(
|
||||
EntityManagerInterface $entityManager,
|
||||
Access $access,
|
||||
registryMGR $registryMGR
|
||||
) {
|
||||
$this->em = $entityManager;
|
||||
$this->access = $access;
|
||||
$this->registryMGR = $registryMGR;
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ پایه برای معرفی سیستم
|
||||
* @return string
|
||||
*/
|
||||
public function getSystemIntroductionPrompt(): string
|
||||
{
|
||||
// دسترسی فعلی
|
||||
$acc = $this->access->hasRole('join');
|
||||
$apiToken = null;
|
||||
if ($acc && isset($acc['bid']) && isset($acc['user'])) {
|
||||
// جستجوی توکن AI معتبر برای این کاربر و کسبوکار
|
||||
$now = time();
|
||||
$apiToken = $this->em->getRepository(APIToken::class)->findOneBy([
|
||||
'bid' => $acc['bid'],
|
||||
'submitter' => $acc['user'],
|
||||
'isForAi' => true
|
||||
]);
|
||||
if ($apiToken) {
|
||||
$expire = $apiToken->getDateExpire();
|
||||
if ($expire && $expire != '0' && $now > (int)$expire) {
|
||||
$apiToken = null; // منقضی شده
|
||||
}
|
||||
}
|
||||
if (!$apiToken) {
|
||||
// ساخت توکن جدید با اعتبار ۳۰ دقیقه
|
||||
$apiToken = $this->access->createAiToken($acc['bid'], $acc['user'], 1800);
|
||||
}
|
||||
}
|
||||
$apiKey = $apiToken ? $apiToken->getToken() : '';
|
||||
return '{
|
||||
"tool": "system_introduction",
|
||||
"description": "System introduction and authentication requirements",
|
||||
"content": "You are an AI assistant for Hesabix accounting system. This system manages businesses, persons, accounting entries, inventory, and financial reports. You can help users with various tasks using the available tools and APIs.",
|
||||
"capabilities": [
|
||||
"Person management (customers, suppliers, employees)",
|
||||
],
|
||||
"authentication": {
|
||||
"method": "API Key or Session Token",
|
||||
"required_headers": {
|
||||
"api-key": "' . $apiKey . ' (این کد را در هدر api-key قرار بده)",
|
||||
},
|
||||
},
|
||||
"language": "Persian (فارسی)",
|
||||
"currency": "Iranian Rial (ریال)"
|
||||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ پایه برای خطاها
|
||||
* پرامپ پایه تحلیلی و استدلالی برای هوش مصنوعی
|
||||
* @return string
|
||||
*/
|
||||
public function getErrorHandlingPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "error_handling",
|
||||
"description": "Error handling and authentication guidance",
|
||||
"instructions": "When encountering errors, provide clear explanations in Persian and suggest solutions. Focus on authentication and access control issues.",
|
||||
"error_types": {
|
||||
"access_denied": "دسترسی غیرمجاز - کاربر فاقد مجوز لازم است",
|
||||
"ai_permission_denied": "دسترسی هوش مصنوعی غیرمجاز - کاربر فاقد مجوز AI است",
|
||||
"invalid_api_key": "کلید API نامعتبر - لطفاً کلید صحیح را وارد کنید",
|
||||
"expired_token": "توکن منقضی شده - لطفاً توکن جدید دریافت کنید",
|
||||
"invalid_business": "کسب و کار نامعتبر - شناسه کسب و کار صحیح نیست",
|
||||
"invalid_year": "سال مالی نامعتبر - سال مالی انتخاب شده صحیح نیست",
|
||||
"invalid_currency": "واحد پول نامعتبر - واحد پول انتخاب شده صحیح نیست",
|
||||
"not_found": "مورد یافت نشد - کد یا شناسه وارد شده صحیح نیست",
|
||||
"validation_error": "خطای اعتبارسنجی - اطلاعات وارد شده صحیح نیست",
|
||||
"network_error": "خطای شبکه - اتصال اینترنت را بررسی کنید"
|
||||
},
|
||||
"authentication_solutions": {
|
||||
"missing_api_key": "کلید API را در هدر api-key قرار دهید",
|
||||
"expired_token": "توکن جدید از مدیر سیستم دریافت کنید",
|
||||
"no_ai_permission": "مجوز هوش مصنوعی از مدیر کسب و کار دریافت کنید",
|
||||
"wrong_business": "شناسه کسب و کار صحیح را در هدر activeBid قرار دهید",
|
||||
"wrong_year": "سال مالی صحیح را در هدر activeYear قرار دهید"
|
||||
},
|
||||
"response_format": "Explain error in Persian, provide specific solution, and suggest next steps"
|
||||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ پایه برای راهنمایی کاربر
|
||||
* @return string
|
||||
*/
|
||||
public function getHelpPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "help",
|
||||
"description": "User help and guidance",
|
||||
"instructions": "Provide helpful guidance to users about available features and how to use them. Be concise and clear in Persian.",
|
||||
"common_queries": {
|
||||
"person_info": "برای دریافت اطلاعات شخص، کد شخص را وارد کنید",
|
||||
},
|
||||
"response_format": "Provide step-by-step guidance in Persian with examples"
|
||||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ برای نمایش دامنه اصلی API
|
||||
* @return string
|
||||
*/
|
||||
public function getApiBaseUrlPrompt(): string
|
||||
{
|
||||
// دریافت اولین رکورد تنظیمات
|
||||
$settings = $this->em->getRepository(\App\Entity\Settings::class)->findAll();
|
||||
$appSite = isset($settings[0]) ? $settings[0]->getAppSite() : '';
|
||||
$domain = $appSite ? $appSite : '---';
|
||||
return '{
|
||||
"tool": "api_base_url",
|
||||
"description": "آدرس پایه API",
|
||||
"content": "تمام اندپوینتهای سیستم از طریق دامنه زیر قابل دسترسی هستند:",
|
||||
"base_url": "' . $domain . '"
|
||||
"tool": "reasoning_base",
|
||||
"description": "شما یک دستیار هوشمند حسابداری هستید که باید مانند یک فیلسوف و تحلیلگر رفتار کنید. هدف شما فقط اجرای مستقیم دستورات نیست، بلکه باید هدف نهایی کاربر را از دل مکالمه و تاریخچه بفهمید و برای رسیدن به آن، ابزار مناسب را انتخاب کنید. ممکن است برای رسیدن به هدف، نیاز باشد ابتدا با یک ابزار (مثلاً جستوجوی اشخاص یا کالا) دادهای (مثل code یا id) را به دست آورید و سپس آن را به عنوان ورودی ابزار دیگر (مثلاً ویرایش یا جستوجوی تراکنش) استفاده کنید. همیشه تحلیل کن که چه دادهای نیاز است و چه ابزاری باید فراخوانی شود. در حسابداری، code (کد) و id (شناسه دیتابیس) دو مفهوم کاملاً متفاوت هستند و نباید به جای هم استفاده شوند. اگر کاربر در چند پیام متوالی صحبت کرد (مثلاً ابتدا گفت بابک را پیدا کن و بعد گفت تلفنش را ویرایش کن)، باید از تاریخچه مکالمه بفهمی منظورش تغییر تلفن همان شخص بابک است و ابزار مناسب را با داده صحیح فراخوانی کنی. همیشه سعی کن با تحلیل و استدلال چندمرحلهای، بهترین مسیر را برای حل مسئله انتخاب کنی و اگر نیاز به پرسش از کاربر بود، سؤال شفاف و هدفمند بپرس. اگر دادهای ناقص بود، با تحلیل تاریخچه یا پرسش از کاربر آن را کامل کن. در تحلیل دادههای حسابداری، دقت و صحت اطلاعات بسیار مهم است و هرگونه اشتباه در تشخیص ابزار یا داده میتواند منجر به خطای مالی شود. خروجی هر ابزار را به دقت بررسی کن و اگر نیاز بود، آن را به عنوان ورودی ابزار بعدی استفاده کن. همیشه به دنبال درک عمیقتر هدف کاربر و ارائه راهحل بهینه باش.",
|
||||
"instructions": "استدلال کن، تحلیل کن، ابزار مناسب را انتخاب کن، از تاریخچه استفاده کن، تفاوت code و id را رعایت کن، خروجی ابزارها را به هم متصل کن و مانند یک متفکر حرفهای عمل کن.",
|
||||
"response_format": "Explain your reasoning in Persian, select the best tool, and if needed, ask clarifying questions."
|
||||
}';
|
||||
}
|
||||
|
||||
|
@ -139,12 +44,21 @@ class BasePromptService
|
|||
public function getAllBasePrompts(): string
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
$prompts[] = $this->getSystemIntroductionPrompt();
|
||||
$aiPrompt = $this->registryMGR->get('system', 'aiPrompt');
|
||||
if ($aiPrompt) {
|
||||
$prompts[] = $aiPrompt;
|
||||
}
|
||||
$prompts[] = $this->getErrorHandlingPrompt();
|
||||
$prompts[] = $this->getHelpPrompt();
|
||||
$prompts[] = $this->getApiBaseUrlPrompt();
|
||||
|
||||
return implode("\n\n", $prompts);
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام ابزارهای پایه
|
||||
* @return array
|
||||
*/
|
||||
public function getAllTools(): array
|
||||
{
|
||||
$tools = [];
|
||||
return $tools;
|
||||
}
|
||||
}
|
|
@ -13,111 +13,71 @@ class InventoryPromptService
|
|||
$this->em = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* دریافت تمام ابزارهای بخش کالاها برای function calling
|
||||
* @return array
|
||||
*/
|
||||
public function getTools(): array
|
||||
{
|
||||
$tools = [];
|
||||
|
||||
// ابزار getItemInfo
|
||||
$itemInfoPrompt = $this->getItemInfoPrompt();
|
||||
$itemInfoData = json_decode($itemInfoPrompt, true);
|
||||
|
||||
if ($itemInfoData) {
|
||||
// اصلاح ساختار properties
|
||||
$properties = [
|
||||
'code' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Item code (e.g., 1001, 1002)'
|
||||
]
|
||||
];
|
||||
$commodityPrompt = $this->getAddOrUpdateCommodityPrompt();
|
||||
$commodityData = json_decode($commodityPrompt, true);
|
||||
if ($commodityData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $itemInfoData['tool'],
|
||||
'description' => $itemInfoData['description'],
|
||||
'parameters' => [
|
||||
'type' => 'object',
|
||||
'properties' => $properties,
|
||||
'required' => ['code']
|
||||
]
|
||||
'name' => $commodityData['tool'],
|
||||
'description' => $commodityData['description'],
|
||||
'parameters' => $commodityData['parameters']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید تمام پرامپهای بخش کالاها
|
||||
* @return string
|
||||
*/
|
||||
public function getAllInventoryPrompts(): string
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
// اضافه کردن تمام پرامپهای موجود
|
||||
$prompts[] = $this->getItemInfoPrompt();
|
||||
|
||||
// در آینده پرامپهای دیگر اضافه خواهند شد
|
||||
// $prompts[] = $this->getCreateItemPrompt();
|
||||
// $prompts[] = $this->getUpdateItemPrompt();
|
||||
// $prompts[] = $this->getSearchItemPrompt();
|
||||
// $prompts[] = $this->getItemStockPrompt();
|
||||
|
||||
// ترکیب تمام پرامپها
|
||||
return implode("\n\n", $prompts);
|
||||
return $this->getAddOrUpdateCommodityPrompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* پرامپ برای دریافت اطلاعات کامل کالا
|
||||
* @return string
|
||||
*/
|
||||
public function getItemInfoPrompt(): string
|
||||
public function getAddOrUpdateCommodityPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "getItemInfo",
|
||||
"description": "Get complete item information by code",
|
||||
"endpoint": "/api/item/info/{code}",
|
||||
"method": "GET",
|
||||
"input": {
|
||||
"code": "string - Item code (e.g., 1001, 1002)"
|
||||
"tool": "addOrUpdateCommodity",
|
||||
"description": "برای ویرایش یک کالا ابتدا باید با ابزار جستوجوی کالا (در آینده) کالا را پیدا کنید. اگر چند نتیجه یافت شد، باید از کاربر بپرسید کدام را میخواهد ویرایش کند و کد (code) آن را دریافت کنید. سپس با ارسال کد و اطلاعات جدید به این ابزار، ویرایش انجام میشود. اگر code برابر 0 یا ارسال نشود، کالا/خدمت جدید ایجاد خواهد شد. Add a new commodity or update an existing one. If code is 0 or not set, a new commodity will be created. Otherwise, the commodity with the given code will be updated.",
|
||||
"endpoint": "/api/commodity/mod/{code}",
|
||||
"method": "POST",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "Commodity name (required)"},
|
||||
"priceSell": {"type": "string", "description": "Sell price"},
|
||||
"priceBuy": {"type": "string", "description": "Buy price"},
|
||||
"des": {"type": "string", "description": "Description"},
|
||||
"unit": {"type": "string", "description": "Unit name (required)"},
|
||||
"code": {"type": ["integer", "string"], "description": "Commodity code (0 for new, otherwise for update)"},
|
||||
"khadamat": {"type": "boolean", "description": "Is service?"},
|
||||
"cat": {"type": "object", "description": "Category object (id, code, name, ...)"},
|
||||
"orderPoint": {"type": "integer", "description": "Order point"},
|
||||
"commodityCountCheck": {"type": "boolean", "description": "Count check flag"},
|
||||
"minOrderCount": {"type": "integer", "description": "Minimum order count"},
|
||||
"dayLoading": {"type": "integer", "description": "Day loading"},
|
||||
"speedAccess": {"type": "boolean", "description": "Quick access flag"},
|
||||
"withoutTax": {"type": "boolean", "description": "Without tax flag"},
|
||||
"barcodes": {"type": "string", "description": "Barcodes"},
|
||||
"prices": {"type": "array", "items": {"type": "object"}, "description": "Prices list"},
|
||||
"taxCode": {"type": "string", "description": "Tax code"},
|
||||
"taxType": {"type": "string", "description": "Tax type"},
|
||||
"taxUnit": {"type": "string", "description": "Tax unit"},
|
||||
"acc": {"type": "object", "description": "Access info (required for backend)"}
|
||||
},
|
||||
"required": ["name", "unit"]
|
||||
},
|
||||
"output": {
|
||||
"id": "integer - Item ID",
|
||||
"code": "string - Item code",
|
||||
"name": "string - Item name",
|
||||
"description": "string - Item description",
|
||||
"category": "string - Item category",
|
||||
"unit": "string - Unit of measurement",
|
||||
"price": "float - Item price",
|
||||
"stock": "float - Current stock quantity",
|
||||
"minStock": "float - Minimum stock level",
|
||||
"maxStock": "float - Maximum stock level",
|
||||
"supplier": "string - Supplier name",
|
||||
"barcode": "string - Barcode",
|
||||
"isActive": "boolean - Item active status"
|
||||
"Success": "boolean",
|
||||
"result": "integer",
|
||||
"code": "integer"
|
||||
},
|
||||
"examples": {
|
||||
"input": {"code": "1001"},
|
||||
"output": {
|
||||
"id": 45,
|
||||
"code": "1001",
|
||||
"name": "لپتاپ اپل",
|
||||
"description": "لپتاپ اپل مکبوک پرو 13 اینچ",
|
||||
"category": "الکترونیک",
|
||||
"unit": "عدد",
|
||||
"price": 45000000,
|
||||
"stock": 15,
|
||||
"minStock": 5,
|
||||
"maxStock": 50,
|
||||
"supplier": "شرکت اپل",
|
||||
"barcode": "1234567890123",
|
||||
"isActive": true
|
||||
"input": {"name":"میخ","priceSell":"6500","priceBuy":"5500","des":"","unit":"عدد","code":0,"khadamat":false,"cat":{"id":4,"code":4,"name":"بدون دستهبندی","checked":false,"root":null,"upper":"3"},"orderPoint":0,"commodityCountCheck":false,"minOrderCount":1,"dayLoading":0,"speedAccess":false,"withoutTax":false,"barcodes":"","prices":[],"taxCode":"","taxType":"","taxUnit":"","acc":{"bid":2,"user":2,"year":2,"access":true,"money":1,"ai":true}},
|
||||
"output": {"Success":true,"result":1,"code":2}
|
||||
}
|
||||
}
|
||||
}';
|
||||
}';
|
||||
}
|
||||
}
|
|
@ -47,6 +47,33 @@ class PersonPromptService
|
|||
];
|
||||
}
|
||||
|
||||
// ابزار getPersonsList
|
||||
$personListPrompt = $this->getPersonsListPrompt();
|
||||
$personListData = json_decode($personListPrompt, true);
|
||||
if ($personListData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $personListData['tool'],
|
||||
'description' => $personListData['description'],
|
||||
'parameters' => $personListData['parameters']
|
||||
]
|
||||
];
|
||||
}
|
||||
// ابزار addOrUpdatePerson
|
||||
$addOrUpdatePrompt = $this->getAddOrUpdatePersonPrompt();
|
||||
$addOrUpdateData = json_decode($addOrUpdatePrompt, true);
|
||||
if ($addOrUpdateData) {
|
||||
$tools[] = [
|
||||
'type' => 'function',
|
||||
'function' => [
|
||||
'name' => $addOrUpdateData['tool'],
|
||||
'description' => $addOrUpdateData['description'],
|
||||
'parameters' => $addOrUpdateData['parameters']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
|
@ -57,17 +84,9 @@ class PersonPromptService
|
|||
public function getAllPersonPrompts(): string
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
// اضافه کردن تمام پرامپهای موجود
|
||||
$prompts[] = $this->getPersonInfoPrompt();
|
||||
|
||||
// در آینده پرامپهای دیگر اضافه خواهند شد
|
||||
// $prompts[] = $this->getCreatePersonPrompt();
|
||||
// $prompts[] = $this->getUpdatePersonPrompt();
|
||||
// $prompts[] = $this->getSearchPersonPrompt();
|
||||
// $prompts[] = $this->getDeletePersonPrompt();
|
||||
|
||||
// ترکیب تمام پرامپها
|
||||
$prompts[] = $this->getPersonsListPrompt();
|
||||
$prompts[] = $this->getAddOrUpdatePersonPrompt();
|
||||
return implode("\n\n", $prompts);
|
||||
}
|
||||
|
||||
|
@ -189,4 +208,164 @@ class PersonPromptService
|
|||
}';
|
||||
}
|
||||
|
||||
public function getPersonsListPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "getPersonsList",
|
||||
"description": "Search and list persons with filters, pagination, and types. The parameters types (person type: e.g., customer, marketer, etc.) and transactionFilters (debtors, creditors, zero) are optional and help to narrow down the search. Normally, you do not need to send these parameters unless you want a more precise search.",
|
||||
"endpoint": "/api/person/list",
|
||||
"method": "POST",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {"type": "integer", "description": "Page number"},
|
||||
"itemsPerPage": {"type": "integer", "description": "Number of items per page"},
|
||||
"search": {"type": "string", "description": "Search text (name, code, mobile, etc.)"},
|
||||
"types": {"type": "array", "items": {"type": "string"}, "description": "Person types (e.g., customer, marketer, etc.) - optional"},
|
||||
"transactionFilters": {"type": "array", "items": {"type": "string"}, "description": "Transaction filters (debtors, creditors, zero) - optional"},
|
||||
"sortBy": {"type": ["string", "null"], "description": "Sort field (optional)"},
|
||||
"acc": {"type": "object", "description": "Access info (required for backend)"}
|
||||
},
|
||||
"required": ["page", "itemsPerPage", "search"]
|
||||
},
|
||||
"output": {
|
||||
"items": [
|
||||
{
|
||||
"id": "integer",
|
||||
"code": "string",
|
||||
"nikename": "string",
|
||||
"name": "string",
|
||||
"tel": "string",
|
||||
"mobile": "string",
|
||||
"mobile2": "string",
|
||||
"des": "string",
|
||||
"company": "string",
|
||||
"shenasemeli": "string",
|
||||
"sabt": "string",
|
||||
"shahr": "string",
|
||||
"keshvar": "string",
|
||||
"ostan": "string",
|
||||
"postalcode": "string",
|
||||
"codeeghtesadi": "string",
|
||||
"email": "string",
|
||||
"website": "string",
|
||||
"fax": "string",
|
||||
"birthday": "string|null",
|
||||
"speedAccess": "boolean",
|
||||
"address": "string",
|
||||
"prelabel": "string|null",
|
||||
"accounts": "array",
|
||||
"types": "array",
|
||||
"bs": "float",
|
||||
"bd": "float",
|
||||
"balance": "float"
|
||||
}
|
||||
],
|
||||
"total": "integer",
|
||||
"unfilteredTotal": "integer"
|
||||
},
|
||||
"examples": {
|
||||
"input": {"page":1,"itemsPerPage":10,"search":"بابک","types":["customer","marketer","emplyee","supplier","colleague","salesman"],"transactionFilters":["debtors","creditors"],"sortBy":null,"acc":{"bid":2,"user":2,"year":2,"access":true,"money":1,"ai":true}},
|
||||
"output": {
|
||||
"items": [
|
||||
{
|
||||
"id": 2,
|
||||
"code": "1000",
|
||||
"nikename": "بابک علی زاده",
|
||||
"name": "",
|
||||
"tel": "",
|
||||
"mobile": "",
|
||||
"mobile2": "",
|
||||
"des": "",
|
||||
"company": "",
|
||||
"shenasemeli": "",
|
||||
"sabt": "",
|
||||
"shahr": "",
|
||||
"keshvar": "",
|
||||
"ostan": "",
|
||||
"postalcode": "6761656589",
|
||||
"codeeghtesadi": "",
|
||||
"email": "",
|
||||
"website": "",
|
||||
"fax": "",
|
||||
"birthday": null,
|
||||
"speedAccess": false,
|
||||
"address": "",
|
||||
"prelabel": "آقای",
|
||||
"accounts": [
|
||||
{
|
||||
"bank": "صادرات",
|
||||
"shabaNum": "125210000000032563214444",
|
||||
"cardNum": "6037998121456321",
|
||||
"accountNum": "123456"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{"label": "مشتری", "code": "customer", "checked": false},
|
||||
{"label": "بازاریاب", "code": "marketer", "checked": false},
|
||||
{"label": "کارمند", "code": "emplyee", "checked": true},
|
||||
{"label": "تامینکننده", "code": "supplier", "checked": true},
|
||||
{"label": "همکار", "code": "colleague", "checked": true},
|
||||
{"label": "فروشنده", "code": "salesman", "checked": true}
|
||||
],
|
||||
"bs": 0,
|
||||
"bd": 0,
|
||||
"balance": 0
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"unfilteredTotal": 4
|
||||
}
|
||||
}
|
||||
}';
|
||||
}
|
||||
|
||||
public function getAddOrUpdatePersonPrompt(): string
|
||||
{
|
||||
return '{
|
||||
"tool": "addOrUpdatePerson",
|
||||
"description": "برای ویرایش یک شخص ابتدا باید با ابزار جستوجوی شخص (getPersonsList) شخص مورد نظر را پیدا کنید. اگر چند نتیجه یافت شد، باید از کاربر بپرسید کدام را میخواهد ویرایش کند و کد (code) آن را دریافت کنید. سپس با ارسال کد و اطلاعات جدید به این ابزار، ویرایش انجام میشود. اگر code برابر 0 یا ارسال نشود، شخص جدید ایجاد خواهد شد. Add a new person or update an existing person. If code is 0 or not set, a new person will be created. Otherwise, the person with the given code will be updated.",
|
||||
"endpoint": "/api/person/mod/{code}",
|
||||
"method": "POST",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nikename": {"type": "string", "description": "Person nickname (required)"},
|
||||
"name": {"type": "string", "description": "Person name"},
|
||||
"des": {"type": "string", "description": "Description"},
|
||||
"tel": {"type": "string", "description": "Telephone number"},
|
||||
"mobile": {"type": "string", "description": "Mobile number"},
|
||||
"mobile2": {"type": "string", "description": "Secondary mobile"},
|
||||
"address": {"type": "string", "description": "Address"},
|
||||
"company": {"type": "string", "description": "Company name"},
|
||||
"shenasemeli": {"type": "string", "description": "National ID"},
|
||||
"codeeghtesadi": {"type": "string", "description": "Economic code"},
|
||||
"sabt": {"type": "string", "description": "Registration number"},
|
||||
"keshvar": {"type": "string", "description": "Country"},
|
||||
"ostan": {"type": "string", "description": "Province"},
|
||||
"shahr": {"type": "string", "description": "City"},
|
||||
"postalcode": {"type": "string", "description": "Postal code"},
|
||||
"email": {"type": "string", "description": "Email address"},
|
||||
"website": {"type": "string", "description": "Website"},
|
||||
"fax": {"type": "string", "description": "Fax number"},
|
||||
"code": {"type": ["integer", "string"], "description": "Person code (0 for new, otherwise for update)"},
|
||||
"types": {"type": "array", "items": {"type": "object"}, "description": "Person types (array of {label, code, checked})"},
|
||||
"accounts": {"type": "array", "items": {"type": "object"}, "description": "Bank accounts (array of {bank, accountNum, cardNum, shabaNum})"},
|
||||
"prelabel": {"type": "string", "description": "Pre label (e.g., آقای, دکتر, etc.)"},
|
||||
"speedAccess": {"type": "boolean", "description": "Quick access flag"},
|
||||
"birthday": {"type": "string", "description": "Birthday"},
|
||||
"acc": {"type": "object", "description": "Access info (required for backend)"}
|
||||
},
|
||||
"required": ["nikename"]
|
||||
},
|
||||
"output": {
|
||||
"Success": "boolean",
|
||||
"result": "integer"
|
||||
},
|
||||
"examples": {
|
||||
"input": {"nikename":"بهتاش","name":"بهتاش عابدینی","des":"توضیحات","tel":"","mobile":"09183282405","mobile2":"","address":"","company":"آذرخش","shenasemeli":"123848","codeeghtesadi":"4864864","sabt":"468468","keshvar":"ایران","ostan":"کرمانشاه","shahr":"اسلام آباد غرب","postalcode":"6761656589","email":"","website":"","fax":"","code":0,"types":[{"label":"مشتری","code":"customer","checked":false},{"label":"بازاریاب","code":"marketer","checked":false},{"label":"کارمند","code":"emplyee","checked":false},{"label":"تامینکننده","code":"supplier","checked":false},{"label":"همکار","code":"colleague","checked":false},{"label":"فروشنده","code":"salesman","checked":false}],"accounts":[{"bank":"ملی","accountNum":"123456","cardNum":"12888787","shabaNum":"8484220000051515150"}],"prelabel":"دکتر","speedAccess":false,"acc":{"bid":2,"user":2,"year":2,"access":true,"money":1,"ai":true}},
|
||||
"output": {"Success":true,"result":1}
|
||||
}
|
||||
}';
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace App\Service\AGI\Promps;
|
|||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Service\AGI\Promps\InventoryPromptService;
|
||||
use App\Service\AGI\Promps\BankPromptService;
|
||||
use App\Service\AGI\Promps\AccountingDocPromptService;
|
||||
|
||||
class PromptService
|
||||
{
|
||||
|
@ -13,19 +14,22 @@ class PromptService
|
|||
private $basePromptService;
|
||||
private $inventoryPromptService;
|
||||
private $bankPromptService;
|
||||
private $accountingDocPromptService;
|
||||
|
||||
public function __construct(
|
||||
EntityManagerInterface $entityManager,
|
||||
PersonPromptService $personPromptService,
|
||||
BasePromptService $basePromptService,
|
||||
InventoryPromptService $inventoryPromptService,
|
||||
BankPromptService $bankPromptService
|
||||
BankPromptService $bankPromptService,
|
||||
AccountingDocPromptService $accountingDocPromptService
|
||||
) {
|
||||
$this->em = $entityManager;
|
||||
$this->personPromptService = $personPromptService;
|
||||
$this->basePromptService = $basePromptService;
|
||||
$this->inventoryPromptService = $inventoryPromptService;
|
||||
$this->bankPromptService = $bankPromptService;
|
||||
$this->accountingDocPromptService = $accountingDocPromptService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,9 +52,9 @@ class PromptService
|
|||
$bankTools = $this->bankPromptService->getTools();
|
||||
$tools = array_merge($tools, $bankTools);
|
||||
|
||||
// در آینده ابزارهای بخشهای دیگر اضافه خواهند شد
|
||||
// $accountingTools = $this->accountingPromptService->getTools();
|
||||
// $tools = array_merge($tools, $accountingTools);
|
||||
// ابزارهای بخش اسناد حسابداری
|
||||
$accountingTools = $this->accountingDocPromptService->getTools();
|
||||
$tools = array_merge($tools, $accountingTools);
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
@ -108,6 +112,9 @@ class PromptService
|
|||
// پرامپهای بخش بانکها
|
||||
$prompts['bank'] = $this->bankPromptService->getAllBankPrompts();
|
||||
|
||||
// پرامپهای بخش اسناد حسابداری
|
||||
$prompts['accounting'] = $this->accountingDocPromptService->getAllAccountingDocPrompts();
|
||||
|
||||
// در آینده بخشهای دیگر اضافه خواهند شد
|
||||
// $prompts['accounting'] = $this->accountingPromptService->getAllAccountingPrompts();
|
||||
// $prompts['reports'] = $this->reportsPromptService->getAllReportsPrompts();
|
||||
|
|
|
@ -73,6 +73,7 @@ export default defineComponent({
|
|||
inputTokenPrice: 0,
|
||||
outputTokenPrice: 0,
|
||||
aiPrompt: '',
|
||||
aiDebugMode: false,
|
||||
aiAgentSources: [
|
||||
{ title: 'GapGPT', value: 'gapgpt', subtitle: 'gapgpt.app' },
|
||||
{ title: 'AvalAI', value: 'avalai', subtitle: 'avalai.ir' },
|
||||
|
@ -164,6 +165,7 @@ export default defineComponent({
|
|||
this.inputTokenPrice = parseFloat(data.inputTokenPrice) || 0;
|
||||
this.outputTokenPrice = parseFloat(data.outputTokenPrice) || 0;
|
||||
this.aiPrompt = data.aiPrompt || '';
|
||||
this.aiDebugMode = data.aiDebugMode === '1' || data.aiDebugMode === true;
|
||||
this.loading = false;
|
||||
})
|
||||
},
|
||||
|
@ -224,7 +226,8 @@ export default defineComponent({
|
|||
localModelAddress: this.localModelAddress,
|
||||
inputTokenPrice: this.inputTokenPrice,
|
||||
outputTokenPrice: this.outputTokenPrice,
|
||||
aiPrompt: this.aiPrompt
|
||||
aiPrompt: this.aiPrompt,
|
||||
aiDebugMode: this.aiDebugMode
|
||||
};
|
||||
|
||||
axios.post('/api/admin/settings/system/info/save', submitData).then((resp) => {
|
||||
|
@ -876,6 +879,19 @@ export default defineComponent({
|
|||
<v-icon size="16" class="mr-1">mdi-information</v-icon>
|
||||
این متن قبل از هر سوال به هوش مصنوعی ارسال میشود تا رفتار و پاسخدهی آن را کنترل کند
|
||||
</div>
|
||||
<v-switch
|
||||
v-model="aiDebugMode"
|
||||
label="نمایش اطلاعات دیباگ در خروجی هوش مصنوعی"
|
||||
color="info"
|
||||
hide-details="auto"
|
||||
inset
|
||||
density="comfortable"
|
||||
class="mt-6"
|
||||
></v-switch>
|
||||
<div class="text-caption text-medium-emphasis mt-1 d-flex align-center">
|
||||
<v-icon size="16" class="mr-1">mdi-information</v-icon>
|
||||
اگر این گزینه فعال باشد، اطلاعات دیباگ (debug_info) در خروجی پاسخهای هوش مصنوعی نمایش داده میشود.
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
|
|
@ -1,6 +1,105 @@
|
|||
<template>
|
||||
<div class="chat-container">
|
||||
|
||||
<!-- لودینگ بالای صفحه برای عملیات گفتگو -->
|
||||
<v-progress-linear
|
||||
v-if="loadingConversation"
|
||||
color="primary"
|
||||
indeterminate
|
||||
absolute
|
||||
style="top:0; left:0; right:0; z-index:2000;"
|
||||
/>
|
||||
<!-- دیالوگ مدیریت گفتوگوها - ساختار استاندارد Vuetify -->
|
||||
<v-dialog v-model="showConversations" max-width="420" scrollable transition="dialog-bottom-transition">
|
||||
<v-card>
|
||||
<v-toolbar color="primary" dark flat rounded>
|
||||
<v-avatar color="white" size="36" class="mr-3"><v-icon color="primary">mdi-forum</v-icon></v-avatar>
|
||||
<v-toolbar-title class="font-weight-bold">مدیریت گفتوگوها</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<!-- دکمه حذف همه گفتوگوها با تولتیپ -->
|
||||
<v-tooltip text="حذف همه گفتوگوها" location="bottom">
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
color="error"
|
||||
:loading="loadingDeleteAll"
|
||||
icon
|
||||
class="ml-1"
|
||||
@click="showDeleteAllDialog = true"
|
||||
>
|
||||
<v-icon>mdi-delete-sweep</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<!-- دکمه بستن دیالوگ -->
|
||||
<v-btn icon variant="text" @click="showConversations = false"><v-icon>mdi-close</v-icon></v-btn>
|
||||
</v-toolbar>
|
||||
<v-divider></v-divider>
|
||||
<v-list class="py-0" style="min-height: 320px; max-height: 60vh; overflow-y: auto;">
|
||||
<template v-if="conversations.length">
|
||||
<template v-for="(conv, idx) in conversations" :key="conv.id">
|
||||
<v-list-item :active="conv.id === conversationId" @click="switchConversation(conv.id)" class="conv-list-item-v">
|
||||
<template #prepend>
|
||||
<v-avatar :color="getAvatarColor(conv.title)" size="36">
|
||||
<span class="white--text text-h6">{{ conv.title.charAt(0) }}</span>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<v-list-item-title class="font-weight-bold">{{ conv.title }}</v-list-item-title>
|
||||
<v-list-item-subtitle class="d-flex align-center">
|
||||
<v-icon size="16" color="grey" class="ml-1">mdi-clock-outline</v-icon>
|
||||
<span class="mr-1">{{ conv.updatedAt }}</span>
|
||||
<v-divider vertical class="mx-2" style="height: 18px;"></v-divider>
|
||||
<v-icon size="16" color="primary" class="ml-1">mdi-message-reply-text</v-icon>
|
||||
<span>{{ conv.messageCount }}</span>
|
||||
</v-list-item-subtitle>
|
||||
<template #append>
|
||||
<v-btn icon color="error" variant="text" @click.stop="confirmDelete(conv.id)"><v-icon>mdi-delete</v-icon></v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-divider v-if="idx !== conversations.length - 1" class="my-1"></v-divider>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="d-flex flex-column align-center justify-center py-10">
|
||||
<v-icon size="64" color="grey-lighten-1">mdi-emoticon-happy-outline</v-icon>
|
||||
<div class="mt-3 text-h6">هنوز گفتوگویی ایجاد نکردهاید!</div>
|
||||
<div class="text-caption mt-1">برای شروع، روی دکمه زیر کلیک کنید.</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-list>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions class="pa-4 d-flex flex-row-reverse align-center justify-space-between">
|
||||
<v-btn color="primary" block large class="font-weight-bold" @click="createConversation">
|
||||
<v-icon start>mdi-plus</v-icon>
|
||||
گفتوگوی جدید
|
||||
</v-btn>
|
||||
<!-- دکمه حذف همه را از پایین (v-card-actions) حذف کن -->
|
||||
</v-card-actions>
|
||||
<!-- دیالوگ تایید حذف -->
|
||||
<v-dialog v-model="showDeleteDialog" max-width="320">
|
||||
<v-card>
|
||||
<v-card-title class="text-h6">حذف گفتگو</v-card-title>
|
||||
<v-card-text>آیا از حذف این گفتگو مطمئن هستید؟ این عمل قابل بازگشت نیست.</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" variant="text" @click="showDeleteDialog = false">انصراف</v-btn>
|
||||
<v-btn color="error" variant="flat" :loading="loadingDelete" :disabled="loadingDelete" @click="doDeleteConversation">حذف</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<!-- دیالوگ تایید حذف همه -->
|
||||
<v-dialog v-model="showDeleteAllDialog" max-width="340">
|
||||
<v-card>
|
||||
<v-card-title class="text-h6">حذف همه گفتوگوها</v-card-title>
|
||||
<v-card-text>آیا از حذف همه گفتوگوها مطمئن هستید؟ این عمل قابل بازگشت نیست.</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" variant="text" @click="showDeleteAllDialog = false">انصراف</v-btn>
|
||||
<v-btn color="error" variant="flat" :loading="loadingDeleteAll" :disabled="loadingDeleteAll" @click="deleteAllConversations">حذف همه</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- ناحیه پیامها -->
|
||||
<div class="messages-container" ref="messagesContainer">
|
||||
|
@ -83,6 +182,21 @@
|
|||
>
|
||||
<v-icon>mdi-send</v-icon>
|
||||
</v-btn>
|
||||
<!-- دکمه مدیریت گفتوگوها فقط آیکون با تولتیپ -->
|
||||
<v-tooltip text="مدیریت گفتوگوها" location="top">
|
||||
<template #activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
color="info"
|
||||
size="small"
|
||||
class="send-button"
|
||||
@click="showConversations = true"
|
||||
icon
|
||||
>
|
||||
<v-icon>mdi-forum</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
|
||||
<!-- دکمههای سریع -->
|
||||
|
@ -111,6 +225,9 @@
|
|||
</v-alert>
|
||||
</div>
|
||||
</div>
|
||||
<v-snackbar v-model="showSnackbar" :color="snackbarColor" location="bottom" timeout="3500">
|
||||
{{ snackbarText }}
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -118,6 +235,7 @@
|
|||
import { useNavigationStore } from '@/stores/navigationStore';
|
||||
import axios from 'axios';
|
||||
import AIChart from '@/components/widgets/AIChart.vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
export default {
|
||||
name: "WizardHome",
|
||||
|
@ -137,12 +255,23 @@ export default {
|
|||
aiEnabled: false,
|
||||
aiStatus: 'checking',
|
||||
conversationId: null,
|
||||
showConversations: false,
|
||||
conversations: [],
|
||||
quickSuggestions: [
|
||||
'چطور میتونم کمکتون کنم؟',
|
||||
'سوالی دارید؟',
|
||||
'نیاز به راهنمایی دارید؟',
|
||||
'مشکلی پیش اومده؟'
|
||||
]
|
||||
'چگونه میتوانم یک گزارش مالی تهیه کنم؟',
|
||||
'برای ثبت یک فاکتور جدید چه مراحلی را باید طی کنم؟',
|
||||
'چطور میتوانم کاربران جدید به سیستم اضافه کنم؟',
|
||||
'چگونه میتوانم تنظیمات کسبوکارم را تغییر دهم؟'
|
||||
],
|
||||
showDeleteDialog: false,
|
||||
deleteTargetId: null,
|
||||
loadingConversation: false,
|
||||
loadingDelete: false,
|
||||
showSnackbar: false,
|
||||
snackbarText: '',
|
||||
snackbarColor: 'success',
|
||||
showDeleteAllDialog: false,
|
||||
loadingDeleteAll: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -350,8 +479,206 @@ export default {
|
|||
}
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
|
||||
async fetchConversations() {
|
||||
const res = await axios.post('/api/wizard/conversations/list');
|
||||
if (res.data.success) {
|
||||
this.conversations = res.data.items;
|
||||
}
|
||||
},
|
||||
async createConversation() {
|
||||
this.loadingConversation = true;
|
||||
const res = await axios.post('/api/wizard/conversations/create', { title: 'گفتگوی جدید' });
|
||||
if (res.data.success) {
|
||||
this.showConversations = false;
|
||||
this.conversationId = res.data.id;
|
||||
this.messages = [
|
||||
{
|
||||
type: 'ai',
|
||||
data: { type: ['text'], data: [{ type: 'text', content: 'سلام! گفتوگوی جدید ایجاد شد. پیام خود را بنویسید.' }] },
|
||||
timestamp: new Date()
|
||||
}
|
||||
];
|
||||
await this.fetchConversations();
|
||||
}
|
||||
this.loadingConversation = false;
|
||||
},
|
||||
async switchConversation(id) {
|
||||
this.loadingConversation = true;
|
||||
this.conversationId = id;
|
||||
this.showConversations = false;
|
||||
// دریافت پیامهای گفتگو
|
||||
const res = await axios.post(`/api/wizard/conversations/${id}/messages`);
|
||||
if (res.data.success) {
|
||||
this.messages = res.data.items.map(msg => {
|
||||
const role = (msg.role || '').toLowerCase();
|
||||
if (role.includes('user')) {
|
||||
return {
|
||||
type: 'user',
|
||||
text: msg.content,
|
||||
timestamp: new Date(msg.createdAt)
|
||||
};
|
||||
} else if (role.includes('ai') || role.includes('assistant') || role.includes('system')) {
|
||||
let parsed = null;
|
||||
try {
|
||||
parsed = msg.content;
|
||||
let safety = 0;
|
||||
while (typeof parsed === 'string' && safety < 5) {
|
||||
parsed = JSON.parse(parsed);
|
||||
safety++;
|
||||
}
|
||||
if (
|
||||
parsed &&
|
||||
parsed.data &&
|
||||
Array.isArray(parsed.data) &&
|
||||
typeof parsed.data[0] === 'string'
|
||||
) {
|
||||
let safety2 = 0;
|
||||
while (typeof parsed.data[0] === 'string' && safety2 < 5) {
|
||||
parsed.data[0] = JSON.parse(parsed.data[0]);
|
||||
safety2++;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
parsed = { type: ['text'], data: [{ type: 'text', content: msg.content }] };
|
||||
}
|
||||
return {
|
||||
type: 'ai',
|
||||
data: parsed,
|
||||
timestamp: new Date(msg.createdAt)
|
||||
};
|
||||
} else {
|
||||
// پیشفرض: پیام کاربر
|
||||
return {
|
||||
type: 'user',
|
||||
text: msg.content,
|
||||
timestamp: new Date(msg.createdAt)
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
this.loadingConversation = false;
|
||||
},
|
||||
async deleteConversation(id) {
|
||||
this.loadingConversation = true;
|
||||
await axios.post(`/api/wizard/conversations/${id}/delete`);
|
||||
await this.fetchConversations();
|
||||
if (this.conversationId === id) {
|
||||
this.conversationId = null;
|
||||
this.messages = [
|
||||
{
|
||||
type: 'ai',
|
||||
data: { type: ['text'], data: [{ type: 'text', content: 'گفتوگو حذف شد. یک گفتوگوی جدید شروع کنید.' }] },
|
||||
timestamp: new Date()
|
||||
}
|
||||
];
|
||||
}
|
||||
this.loadingConversation = false;
|
||||
},
|
||||
confirmDelete(id) {
|
||||
this.deleteTargetId = id;
|
||||
this.showDeleteDialog = true;
|
||||
},
|
||||
async doDeleteConversation() {
|
||||
if (this.deleteTargetId) {
|
||||
this.loadingDelete = true;
|
||||
try {
|
||||
await this.deleteConversation(this.deleteTargetId);
|
||||
this.showDeleteDialog = false;
|
||||
this.deleteTargetId = null;
|
||||
this.snackbarText = 'گفتوگو با موفقیت حذف شد.';
|
||||
this.snackbarColor = 'success';
|
||||
this.showSnackbar = true;
|
||||
} catch (e) {
|
||||
this.snackbarText = 'خطا در حذف گفتوگو!';
|
||||
this.snackbarColor = 'error';
|
||||
this.showSnackbar = true;
|
||||
}
|
||||
this.loadingDelete = false;
|
||||
}
|
||||
},
|
||||
async deleteAllConversations() {
|
||||
this.loadingDeleteAll = true;
|
||||
try {
|
||||
const res = await axios.post('/api/wizard/conversations/delete-all');
|
||||
if (res.data.success) {
|
||||
this.showDeleteAllDialog = false;
|
||||
this.snackbarText = 'همه گفتوگوها با موفقیت حذف شدند.';
|
||||
this.snackbarColor = 'success';
|
||||
this.showSnackbar = true;
|
||||
await this.fetchConversations();
|
||||
this.conversationId = null;
|
||||
this.messages = [
|
||||
{
|
||||
type: 'ai',
|
||||
data: { type: ['text'], data: [{ type: 'text', content: 'همه گفتوگوها حذف شدند. یک گفتوگوی جدید شروع کنید.' }] },
|
||||
timestamp: new Date()
|
||||
}
|
||||
];
|
||||
} else {
|
||||
this.snackbarText = 'خطا در حذف همه گفتوگوها!';
|
||||
this.snackbarColor = 'error';
|
||||
this.showSnackbar = true;
|
||||
}
|
||||
} catch (e) {
|
||||
this.snackbarText = 'خطا در حذف همه گفتوگوها!';
|
||||
this.snackbarColor = 'error';
|
||||
this.showSnackbar = true;
|
||||
}
|
||||
this.loadingDeleteAll = false;
|
||||
},
|
||||
getAvatarColor(title) {
|
||||
// تولید رنگ ثابت بر اساس عنوان گفتگو
|
||||
const colors = ['primary', 'deep-purple', 'indigo', 'teal', 'cyan', 'pink', 'orange', 'green', 'blue', 'red'];
|
||||
let hash = 0;
|
||||
for (let i = 0; i < title.length; i++) hash = title.charCodeAt(i) + ((hash << 5) - hash);
|
||||
return colors[Math.abs(hash) % colors.length];
|
||||
},
|
||||
renderLastMessagePreview(msg) {
|
||||
if (!msg || typeof msg !== 'string') return {
|
||||
render() { return h('span', msg || 'بدون پیام'); }
|
||||
};
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(msg);
|
||||
} catch (e) {
|
||||
return {
|
||||
render() { return h('span', msg); }
|
||||
};
|
||||
}
|
||||
if (parsed && parsed.type && parsed.data && Array.isArray(parsed.data)) {
|
||||
if (parsed.type.includes('text')) {
|
||||
const textItem = parsed.data.find(d => d.type === 'text');
|
||||
if (textItem && textItem.content) {
|
||||
return {
|
||||
render() { return h('span', textItem.content); }
|
||||
};
|
||||
}
|
||||
}
|
||||
if (parsed.type.includes('table')) {
|
||||
const tableItem = parsed.data.find(d => d.type === 'table');
|
||||
if (tableItem && tableItem.headers && tableItem.rows) {
|
||||
return {
|
||||
render() {
|
||||
return h('v-simple-table', { class: 'conv-preview-table' }, [
|
||||
h('thead', [
|
||||
h('tr', tableItem.headers.map(hd => h('th', hd)))
|
||||
]),
|
||||
h('tbody', tableItem.rows.map(row =>
|
||||
h('tr', row.map(cell => h('td', cell)))
|
||||
))
|
||||
]);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
render() { return h('span', msg); }
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
// بستن منو در دسکتاپ
|
||||
|
@ -362,6 +689,7 @@ export default {
|
|||
// بررسی وضعیت هوش مصنوعی
|
||||
await this.checkAIStatus();
|
||||
|
||||
await this.fetchConversations();
|
||||
this.scrollToBottom();
|
||||
},
|
||||
|
||||
|
@ -602,4 +930,185 @@ export default {
|
|||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-item {
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.conversation-item:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.conv-dialog-card {
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.10);
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #e3eafc 100%);
|
||||
}
|
||||
.conv-dialog-title {
|
||||
background: linear-gradient(90deg, #1976d2 0%, #42a5f5 100%);
|
||||
color: white;
|
||||
border-top-left-radius: 20px;
|
||||
border-top-right-radius: 20px;
|
||||
min-height: 56px;
|
||||
}
|
||||
.conv-new-btn {
|
||||
border-radius: 12px;
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
box-shadow: 0 2px 8px rgba(25, 118, 210, 0.08);
|
||||
}
|
||||
.conv-list {
|
||||
background: transparent;
|
||||
}
|
||||
.conv-list-item {
|
||||
margin-bottom: 6px;
|
||||
border-radius: 14px;
|
||||
transition: background 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 1px 4px rgba(25, 118, 210, 0.04);
|
||||
border: 1px solid #e3eafc;
|
||||
}
|
||||
.conv-list-item:hover, .active-conv {
|
||||
background: linear-gradient(90deg, #e3f2fd 0%, #f5faff 100%);
|
||||
box-shadow: 0 4px 16px rgba(25, 118, 210, 0.10);
|
||||
border-color: #90caf9;
|
||||
}
|
||||
.empty-state {
|
||||
opacity: 0.7;
|
||||
font-size: 15px;
|
||||
}
|
||||
.conv-dialog-card-new {
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 12px 40px rgba(25, 118, 210, 0.13);
|
||||
background: linear-gradient(135deg, #fafdff 0%, #e3eafc 100%);
|
||||
overflow: hidden;
|
||||
}
|
||||
.conv-dialog-header-new {
|
||||
background: linear-gradient(90deg, #1976d2 0%, #42a5f5 100%);
|
||||
color: white;
|
||||
border-top-left-radius: 24px;
|
||||
border-top-right-radius: 24px;
|
||||
min-height: 64px;
|
||||
font-size: 20px;
|
||||
letter-spacing: 0.5px;
|
||||
box-shadow: 0 2px 8px rgba(25, 118, 210, 0.08);
|
||||
}
|
||||
.conv-dialog-body-new {
|
||||
padding: 18px 0 0 0;
|
||||
min-height: 320px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.conv-card-item-new {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: linear-gradient(90deg, #f5faff 0%, #e3f2fd 100%);
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 2px 12px rgba(25, 118, 210, 0.07);
|
||||
margin-bottom: 14px;
|
||||
padding: 12px 18px 12px 8px;
|
||||
transition: box-shadow 0.2s, background 0.2s;
|
||||
border: 1.5px solid #e3eafc;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.conv-card-item-new.active {
|
||||
background: linear-gradient(90deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border-color: #90caf9;
|
||||
box-shadow: 0 6px 24px rgba(25, 118, 210, 0.13);
|
||||
}
|
||||
.conv-card-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.conv-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.conv-title {
|
||||
color: #1976d2;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.conv-count {
|
||||
color: #42a5f5;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
.conv-last-message {
|
||||
color: #607d8b;
|
||||
font-size: 13px;
|
||||
margin-top: 2px;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.conv-time {
|
||||
color: #90a4ae;
|
||||
font-size: 12px;
|
||||
margin-right: 12px;
|
||||
min-width: 70px;
|
||||
text-align: left;
|
||||
}
|
||||
.conv-delete-btn {
|
||||
margin-right: 8px;
|
||||
margin-left: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.conv-dialog-actions-new {
|
||||
padding: 18px 24px 18px 24px;
|
||||
background: transparent;
|
||||
border-bottom-left-radius: 24px;
|
||||
border-bottom-right-radius: 24px;
|
||||
box-shadow: 0 -2px 8px rgba(25, 118, 210, 0.04);
|
||||
}
|
||||
.conv-new-btn-new {
|
||||
border-radius: 14px;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
box-shadow: 0 2px 8px rgba(25, 118, 210, 0.10);
|
||||
padding: 14px 0;
|
||||
}
|
||||
.conv-empty-state-new {
|
||||
opacity: 0.8;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-top: 48px;
|
||||
}
|
||||
.conv-list-fade-enter-active, .conv-list-fade-leave-active {
|
||||
transition: all 0.3s cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
.conv-list-fade-enter-from, .conv-list-fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
.conv-list-item-v {
|
||||
border-radius: 14px;
|
||||
margin-bottom: 4px;
|
||||
transition: background 0.2s, box-shadow 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.conv-list-item-v:hover, .conv-list-item-v.v-list-item--active {
|
||||
background: linear-gradient(90deg, #e3f2fd 0%, #f5faff 100%);
|
||||
box-shadow: 0 4px 16px rgba(25, 118, 210, 0.10);
|
||||
}
|
||||
.conv-preview-table {
|
||||
font-size: 12px;
|
||||
margin-top: 2px;
|
||||
background: #f8fafc;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue