progress and some bug fix in commodity,ai

This commit is contained in:
Hesabix 2025-07-25 17:12:20 +00:00
parent 91cf5d4eb6
commit aaeb3cf31e
8 changed files with 940 additions and 362 deletions

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241201000001 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add customCode column to commodity table';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE commodity ADD customCode TINYINT(1) DEFAULT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE commodity DROP customCode');
}
}

View file

@ -30,23 +30,62 @@ class CommodityService
$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'));
// بررسی کد سفارشی
if (isset($params['customCode']) && $params['customCode'] === true && isset($params['code'])) {
// بررسی تکراری نبودن کد سفارشی
$existingCommodity = $em->getRepository(Commodity::class)->findOneBy([
'code' => $params['code'],
'bid' => $acc['bid']
]);
if ($existingCommodity) {
return ['result' => 2, 'error' => 'کد کالا تکراری است'];
}
$data->setCode($params['code']);
$data->setCustomCode(true);
} else {
// کد اتوماتیک
$data->setCode((new \App\Service\Provider($em))->getAccountingCode($acc['bid'], 'Commodity'));
$data->setCustomCode(false);
}
}
} else {
// ویرایش کالای موجود
$data = $em->getRepository(Commodity::class)->findOneBy([
'bid' => $acc['bid'],
'code' => $code
]);
if (!$data)
return ['result' => -2, 'error' => 'کالا یافت نشد'];
// بررسی کد سفارشی در زمان ویرایش
if (isset($params['customCode']) && $params['customCode'] === true && isset($params['code'])) {
// بررسی تکراری نبودن کد سفارشی (به جز خود کالا)
$existingCommodity = $em->getRepository(Commodity::class)->findOneBy([
'code' => $params['code'],
'bid' => $acc['bid']
]);
if ($existingCommodity && $existingCommodity->getId() !== $data->getId()) {
return ['result' => 2, 'error' => 'کد کالا تکراری است'];
}
$data->setCode($params['code']);
$data->setCustomCode(true);
} elseif (isset($params['customCode']) && $params['customCode'] === false) {
// تغییر به کد اتوماتیک
$data->setCode((new \App\Service\Provider($em))->getAccountingCode($acc['bid'], 'Commodity'));
$data->setCustomCode(false);
}
}
$unit = null;
if (!isset($params['unit']))
$unit = $em->getRepository(CommodityUnit::class)->findAll()[0];

View file

@ -267,6 +267,7 @@ class CommodityController extends AbstractController
$temp['taxCode'] = $item->getTaxCode();
$temp['taxType'] = $item->getTaxType();
$temp['taxUnit'] = $item->getTaxUnit();
$temp['customCode'] = $item->isCustomCode();
//calculate count
if ($item->isKhadamat()) {
$temp['count'] = 0;
@ -334,6 +335,7 @@ class CommodityController extends AbstractController
$temp['taxCode'] = $item->getTaxCode();
$temp['taxType'] = $item->getTaxType();
$temp['taxUnit'] = $item->getTaxUnit();
$temp['customCode'] = $item->isCustomCode();
//calculate count
if ($item->isKhadamat()) {
$temp['count'] = 0;
@ -429,6 +431,7 @@ class CommodityController extends AbstractController
$temp['taxCode'] = $item->getTaxCode();
$temp['taxType'] = $item->getTaxType();
$temp['taxUnit'] = $item->getTaxUnit();
$temp['customCode'] = $item->isCustomCode();
//calculate count
if ($item->isKhadamat()) {
$temp['count'] = 0;
@ -528,7 +531,8 @@ class CommodityController extends AbstractController
$temp[] = $item->getMinOrderCount();
$temp[] = $item->getDes();
$temp[] = $item->getUnit()->getName();
$temp[] = $item->getCat()->getName();
$cat = $item->getCat();
$temp[] = $cat ? $cat->getName() : '';
$array[] = $temp;
}
$filePath = $provider->createExcellFromArray($array, [
@ -604,20 +608,28 @@ class CommodityController extends AbstractController
return $this->json(['id' => $pid]);
}
#[Route('/api/commodity/info/{code}', name: 'app_commodity_info')]
public function app_commodity_info($code, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
#[Route('/api/commodity/info/{id}', name: 'app_commodity_info')]
public function app_commodity_info($id, Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
{
$acc = $access->hasRole('commodity');
if (!$acc)
throw $this->createAccessDeniedException();
$data = $entityManager->getRepository(Commodity::class)->findOneBy([
'bid' => $acc['bid'],
'code' => $code
'code' => $id
]);
if (!$data) {
return $this->json(['error' => 'کالا یافت نشد'], 404);
}
$res = Explore::ExploreCommodity($data);
$res['cat'] = '';
if ($data->getCat())
$res['cat'] = $data->getCat()->getId();
$cat = $data->getCat();
if ($cat !== null) {
$res['cat'] = $cat->getId();
}
$res['customCode'] = $data->isCustomCode();
$count = 0;
//calculate count
if ($data->isKhadamat()) {
@ -813,8 +825,8 @@ class CommodityController extends AbstractController
'result' => 1,
]);
}
#[Route('/api/commodity/mod/{code}', name: 'app_commodity_mod')]
public function app_commodity_mod(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, $code = 0): JsonResponse
#[Route('/api/commodity/mod/{id}', name: 'app_commodity_mod')]
public function app_commodity_mod(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, $id = 0): JsonResponse
{
$acc = $access->hasRole('commodity');
if (!$acc)
@ -824,7 +836,7 @@ class CommodityController extends AbstractController
$params = json_decode($content, true);
}
$commodityService = new \App\Cog\CommodityService($entityManager);
$result = $commodityService->addOrUpdateCommodity($params, $acc, $code);
$result = $commodityService->addOrUpdateCommodity($params, $acc, $id);
if (isset($result['error'])) {
return $this->json($result, 400);
}

View file

@ -36,6 +36,9 @@ class Commodity
#[ORM\Column(type: 'bigint')]
private $code;
#[ORM\Column(nullable: true)]
private ?bool $customCode = null;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $priceBuy;
@ -178,6 +181,18 @@ class Commodity
return $this;
}
public function isCustomCode(): ?bool
{
return $this->customCode;
}
public function setCustomCode(?bool $customCode): static
{
$this->customCode = $customCode;
return $this;
}
public function getPriceBuy(): ?int
{
return $this->priceBuy;

View file

@ -236,6 +236,7 @@ class Explore
'taxCode' => $item->getTaxCode(),
'taxType' => $item->getTaxType(),
'taxUnit' => $item->getTaxUnit(),
'customCode' => $item->isCustomCode(),
'unitData' => [
'name' => $item->getUnit()->getName(),
'floatNumber' => $item->getUnit()->getFloatNumber(),
@ -245,8 +246,9 @@ class Explore
if ($des) {
$result['des'] = $des;
}
if ($item->getCat()) {
$result['cat'] = $item->getCat()->getName();
$cat = $item->getCat();
if ($cat !== null) {
$result['cat'] = $cat->getName();
}
return $result;
}

File diff suppressed because it is too large Load diff

View file

@ -29,8 +29,12 @@
<v-tabs-window v-model="tabs">
<!-- اطلاعات پایه -->
<v-tabs-window-item value="0">
<v-card flat>
<v-card-text class="pa-2">
<v-card elevation="2">
<v-card-title class="text-white bg-primary">
<v-icon start icon="mdi-account" class="text-white"></v-icon>
{{ $t('pages.person.basic_info') }}
</v-card-title>
<v-card-text class="pa-4">
<v-row dense>
<v-col cols="12">
<v-switch v-model="person.speedAccess" :label="$t('pages.person.speed_access')"
@ -68,8 +72,12 @@
<!-- اطلاعات اقتصادی -->
<v-tabs-window-item value="1">
<v-card flat>
<v-card-text class="pa-2">
<v-card elevation="2">
<v-card-title class="text-white bg-primary">
<v-icon start icon="mdi-chart-line" class="text-white"></v-icon>
{{ $t('pages.person.eco_info') }}
</v-card-title>
<v-card-text class="pa-4">
<v-row dense>
<v-col cols="12" md="6">
<v-text-field v-model="person.shenasemeli" :label="$t('pages.person.national_id')" dense
@ -90,8 +98,12 @@
<!-- اطلاعات تماس -->
<v-tabs-window-item value="2">
<v-card flat>
<v-card-text class="pa-2">
<v-card elevation="2">
<v-card-title class="text-white bg-primary">
<v-icon start icon="mdi-phone" class="text-white"></v-icon>
{{ $t('pages.person.contact_info') }}
</v-card-title>
<v-card-text class="pa-4">
<v-row dense>
<v-col cols="12" md="6">
<v-text-field v-model="person.mobile" :label="$t('pages.person.mobile')" dense
@ -124,8 +136,12 @@
<!-- آدرس -->
<v-tabs-window-item value="3">
<v-card flat>
<v-card-text class="pa-2">
<v-card elevation="2">
<v-card-title class="text-white bg-primary">
<v-icon start icon="mdi-map-marker" class="text-white"></v-icon>
{{ $t('pages.person.address') }}
</v-card-title>
<v-card-text class="pa-4">
<v-row dense>
<v-col cols="12" md="6">
<v-text-field v-model="person.keshvar" :label="$t('pages.person.country')" dense
@ -158,8 +174,12 @@
<!-- حسابهای بانکی -->
<v-tabs-window-item value="4">
<v-card flat>
<v-card-text class="pa-2">
<v-card elevation="2">
<v-card-title class="text-white bg-primary">
<v-icon start icon="mdi-bank" class="text-white"></v-icon>
{{ $t('pages.person.banks_accounts') }}
</v-card-title>
<v-card-text class="pa-4">
<v-row justify="end" class="mb-2">
<v-col cols="auto">
<v-btn color="primary" @click="addNewCard" prepend-icon="mdi-plus" size="small">

View file

@ -8,32 +8,51 @@
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-card class="conversation-dialog-card">
<!-- هدر بهبود یافته -->
<div class="conversation-dialog-header">
<div class="header-content">
<div class="header-icon-wrapper">
<v-icon size="28" color="white">mdi-forum</v-icon>
</div>
<div class="header-text">
<h3 class="header-title">مدیریت گفتوگوها</h3>
<p class="header-subtitle">{{ conversations.length }} گفتوگو موجود</p>
</div>
</div>
<div class="header-actions">
<!-- دکمه حذف همه گفتوگوها با تولتیپ -->
<v-tooltip text="حذف همه گفت‌وگوها" location="bottom">
<template #activator="{ props }">
<v-btn
v-bind="props"
color="white"
variant="text"
:loading="loadingDeleteAll"
icon
size="small"
class="header-action-btn delete-all-btn"
@click="showDeleteAllDialog = true"
>
<v-icon size="20">mdi-delete-sweep</v-icon>
</v-btn>
</template>
</v-tooltip>
<!-- دکمه بستن دیالوگ -->
<v-btn
icon
variant="text"
color="white"
size="small"
class="header-action-btn close-btn"
@click="showConversations = false"
>
<v-icon size="20">mdi-close</v-icon>
</v-btn>
</div>
</div>
<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">
@ -66,14 +85,19 @@
</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>
<div class="conversation-dialog-footer">
<v-btn
color="primary"
block
size="large"
class="new-conversation-btn"
@click="createConversation"
:loading="loadingConversation"
>
<v-icon start size="20">mdi-plus</v-icon>
گفتوگوی جدید
</v-btn>
<!-- دکمه حذف همه را از پایین (v-card-actions) حذف کن -->
</v-card-actions>
</div>
<!-- دیالوگ تایید حذف -->
<v-dialog v-model="showDeleteDialog" max-width="320">
<v-card>
@ -723,7 +747,7 @@ export default {
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
gap: 12px;
min-height: 0;
}
@ -782,7 +806,7 @@ export default {
}
.message-text {
margin: 0 0 4px 0;
margin: 0;
line-height: 1.5;
font-size: 14px;
}
@ -929,6 +953,10 @@ export default {
.message {
max-width: 90%;
}
.quick-actions {
display: none;
}
}
.conversation-item {
@ -1111,4 +1139,115 @@ export default {
border-radius: 6px;
overflow: hidden;
}
/* استایل‌های جدید دیالوگ گفت‌وگو */
.conversation-dialog-card {
border-radius: 20px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
}
.conversation-dialog-header {
background: linear-gradient(135deg, #1976d2 0%, #42a5f5 100%);
padding: 24px;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.conversation-dialog-header::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100%);
}
.header-content {
display: flex;
align-items: center;
gap: 16px;
}
.header-icon-wrapper {
width: 48px;
height: 48px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(10px);
}
.header-text {
display: flex;
flex-direction: column;
gap: 4px;
}
.header-title {
color: white;
font-size: 20px;
font-weight: 700;
margin: 0;
line-height: 1.2;
}
.header-subtitle {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
margin: 0;
font-weight: 400;
}
.header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.header-action-btn {
width: 36px;
height: 36px;
border-radius: 8px;
transition: all 0.3s ease;
}
.header-action-btn:hover {
background: rgba(255, 255, 255, 0.15);
transform: translateY(-1px);
}
.delete-all-btn:hover {
background: rgba(244, 67, 54, 0.2);
}
.close-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.conversation-dialog-footer {
padding: 20px 24px;
background: #fafafa;
border-top: 1px solid #e0e0e0;
}
.new-conversation-btn {
border-radius: 12px;
font-weight: 600;
font-size: 16px;
text-transform: none;
letter-spacing: 0.5px;
box-shadow: 0 4px 16px rgba(25, 118, 210, 0.2);
transition: all 0.3s ease;
}
.new-conversation-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(25, 118, 210, 0.3);
}
</style>