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; $em = $this->entityManager;
if (!isset($params['name']) || trim($params['name']) === '') if (!isset($params['name']) || trim($params['name']) === '')
return ['result' => -1, 'error' => 'نام کالا الزامی است']; return ['result' => -1, 'error' => 'نام کالا الزامی است'];
if ($code == 0) { if ($code == 0) {
// افزودن کالای جدید
$data = $em->getRepository(Commodity::class)->findOneBy([ $data = $em->getRepository(Commodity::class)->findOneBy([
'name' => $params['name'], 'name' => $params['name'],
'bid' => $acc['bid'] 'bid' => $acc['bid']
]); ]);
if (!$data) { if (!$data) {
$data = new Commodity(); $data = new 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->setCode((new \App\Service\Provider($em))->getAccountingCode($acc['bid'], 'Commodity'));
$data->setCustomCode(false);
}
} }
} else { } else {
// ویرایش کالای موجود
$data = $em->getRepository(Commodity::class)->findOneBy([ $data = $em->getRepository(Commodity::class)->findOneBy([
'bid' => $acc['bid'], 'bid' => $acc['bid'],
'code' => $code 'code' => $code
]); ]);
if (!$data) if (!$data)
return ['result' => -2, 'error' => 'کالا یافت نشد']; 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; $unit = null;
if (!isset($params['unit'])) if (!isset($params['unit']))
$unit = $em->getRepository(CommodityUnit::class)->findAll()[0]; $unit = $em->getRepository(CommodityUnit::class)->findAll()[0];

View file

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

View file

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

View file

@ -236,6 +236,7 @@ class Explore
'taxCode' => $item->getTaxCode(), 'taxCode' => $item->getTaxCode(),
'taxType' => $item->getTaxType(), 'taxType' => $item->getTaxType(),
'taxUnit' => $item->getTaxUnit(), 'taxUnit' => $item->getTaxUnit(),
'customCode' => $item->isCustomCode(),
'unitData' => [ 'unitData' => [
'name' => $item->getUnit()->getName(), 'name' => $item->getUnit()->getName(),
'floatNumber' => $item->getUnit()->getFloatNumber(), 'floatNumber' => $item->getUnit()->getFloatNumber(),
@ -245,8 +246,9 @@ class Explore
if ($des) { if ($des) {
$result['des'] = $des; $result['des'] = $des;
} }
if ($item->getCat()) { $cat = $item->getCat();
$result['cat'] = $item->getCat()->getName(); if ($cat !== null) {
$result['cat'] = $cat->getName();
} }
return $result; 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 v-model="tabs">
<!-- اطلاعات پایه --> <!-- اطلاعات پایه -->
<v-tabs-window-item value="0"> <v-tabs-window-item value="0">
<v-card flat> <v-card elevation="2">
<v-card-text class="pa-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-row dense>
<v-col cols="12"> <v-col cols="12">
<v-switch v-model="person.speedAccess" :label="$t('pages.person.speed_access')" <v-switch v-model="person.speedAccess" :label="$t('pages.person.speed_access')"
@ -68,8 +72,12 @@
<!-- اطلاعات اقتصادی --> <!-- اطلاعات اقتصادی -->
<v-tabs-window-item value="1"> <v-tabs-window-item value="1">
<v-card flat> <v-card elevation="2">
<v-card-text class="pa-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-row dense>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field v-model="person.shenasemeli" :label="$t('pages.person.national_id')" dense <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-tabs-window-item value="2">
<v-card flat> <v-card elevation="2">
<v-card-text class="pa-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-row dense>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field v-model="person.mobile" :label="$t('pages.person.mobile')" dense <v-text-field v-model="person.mobile" :label="$t('pages.person.mobile')" dense
@ -124,8 +136,12 @@
<!-- آدرس --> <!-- آدرس -->
<v-tabs-window-item value="3"> <v-tabs-window-item value="3">
<v-card flat> <v-card elevation="2">
<v-card-text class="pa-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-row dense>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field v-model="person.keshvar" :label="$t('pages.person.country')" dense <v-text-field v-model="person.keshvar" :label="$t('pages.person.country')" dense
@ -158,8 +174,12 @@
<!-- حسابهای بانکی --> <!-- حسابهای بانکی -->
<v-tabs-window-item value="4"> <v-tabs-window-item value="4">
<v-card flat> <v-card elevation="2">
<v-card-text class="pa-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-row justify="end" class="mb-2">
<v-col cols="auto"> <v-col cols="auto">
<v-btn color="primary" @click="addNewCard" prepend-icon="mdi-plus" size="small"> <v-btn color="primary" @click="addNewCard" prepend-icon="mdi-plus" size="small">

View file

@ -8,32 +8,51 @@
absolute absolute
style="top:0; left:0; right:0; z-index:2000;" 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-dialog v-model="showConversations" max-width="420" scrollable transition="dialog-bottom-transition">
<v-card> <v-card class="conversation-dialog-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> <div class="conversation-dialog-header">
<v-toolbar-title class="font-weight-bold">مدیریت گفتوگوها</v-toolbar-title> <div class="header-content">
<v-spacer></v-spacer> <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"> <v-tooltip text="حذف همه گفت‌وگوها" location="bottom">
<template #activator="{ props }"> <template #activator="{ props }">
<v-btn <v-btn
v-bind="props" v-bind="props"
color="error" color="white"
variant="text"
:loading="loadingDeleteAll" :loading="loadingDeleteAll"
icon icon
class="ml-1" size="small"
class="header-action-btn delete-all-btn"
@click="showDeleteAllDialog = true" @click="showDeleteAllDialog = true"
> >
<v-icon>mdi-delete-sweep</v-icon> <v-icon size="20">mdi-delete-sweep</v-icon>
</v-btn> </v-btn>
</template> </template>
</v-tooltip> </v-tooltip>
<!-- دکمه بستن دیالوگ --> <!-- دکمه بستن دیالوگ -->
<v-btn icon variant="text" @click="showConversations = false"><v-icon>mdi-close</v-icon></v-btn> <v-btn
</v-toolbar> icon
<v-divider></v-divider> 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;"> <v-list class="py-0" style="min-height: 320px; max-height: 60vh; overflow-y: auto;">
<template v-if="conversations.length"> <template v-if="conversations.length">
<template v-for="(conv, idx) in conversations" :key="conv.id"> <template v-for="(conv, idx) in conversations" :key="conv.id">
@ -66,14 +85,19 @@
</div> </div>
</template> </template>
</v-list> </v-list>
<v-divider></v-divider> <div class="conversation-dialog-footer">
<v-card-actions class="pa-4 d-flex flex-row-reverse align-center justify-space-between"> <v-btn
<v-btn color="primary" block large class="font-weight-bold" @click="createConversation"> color="primary"
<v-icon start>mdi-plus</v-icon> block
size="large"
class="new-conversation-btn"
@click="createConversation"
:loading="loadingConversation"
>
<v-icon start size="20">mdi-plus</v-icon>
گفتوگوی جدید گفتوگوی جدید
</v-btn> </v-btn>
<!-- دکمه حذف همه را از پایین (v-card-actions) حذف کن --> </div>
</v-card-actions>
<!-- دیالوگ تایید حذف --> <!-- دیالوگ تایید حذف -->
<v-dialog v-model="showDeleteDialog" max-width="320"> <v-dialog v-model="showDeleteDialog" max-width="320">
<v-card> <v-card>
@ -723,7 +747,7 @@ export default {
overflow-y: auto; overflow-y: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 12px;
min-height: 0; min-height: 0;
} }
@ -782,7 +806,7 @@ export default {
} }
.message-text { .message-text {
margin: 0 0 4px 0; margin: 0;
line-height: 1.5; line-height: 1.5;
font-size: 14px; font-size: 14px;
} }
@ -929,6 +953,10 @@ export default {
.message { .message {
max-width: 90%; max-width: 90%;
} }
.quick-actions {
display: none;
}
} }
.conversation-item { .conversation-item {
@ -1111,4 +1139,115 @@ export default {
border-radius: 6px; border-radius: 6px;
overflow: hidden; 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> </style>