diff --git a/hesabixCore/migrations/Version20250820232952.php b/hesabixCore/migrations/Version20250820232952.php
new file mode 100644
index 0000000..ceaee74
--- /dev/null
+++ b/hesabixCore/migrations/Version20250820232952.php
@@ -0,0 +1,53 @@
+addSql(<<<'SQL'
+ ALTER TABLE import_workflow_payment CHANGE amount amount NUMERIC(15, 2) NOT NULL
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE storeroom_ticket ADD completed TINYINT(1) DEFAULT NULL, ADD completed_at DATETIME DEFAULT NULL, ADD completed_by_id INT DEFAULT NULL
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE storeroom_ticket ADD CONSTRAINT FK_9B4CC0F785ECDE76 FOREIGN KEY (completed_by_id) REFERENCES user (id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_9B4CC0F785ECDE76 ON storeroom_ticket (completed_by_id)
+ SQL);
+ }
+
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->addSql(<<<'SQL'
+ ALTER TABLE import_workflow_payment CHANGE amount amount NUMERIC(15, 2) DEFAULT NULL
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE storeroom_ticket DROP FOREIGN KEY FK_9B4CC0F785ECDE76
+ SQL);
+ $this->addSql(<<<'SQL'
+ DROP INDEX IDX_9B4CC0F785ECDE76 ON storeroom_ticket
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE storeroom_ticket DROP completed, DROP completed_at, DROP completed_by_id
+ SQL);
+ }
+}
diff --git a/hesabixCore/migrations/Version20250820233206.php b/hesabixCore/migrations/Version20250820233206.php
new file mode 100644
index 0000000..821b86b
--- /dev/null
+++ b/hesabixCore/migrations/Version20250820233206.php
@@ -0,0 +1,47 @@
+addSql(<<<'SQL'
+ ALTER TABLE plug_warranty_serial ADD device_serial VARCHAR(255) DEFAULT NULL, ADD allocated_to_document_type VARCHAR(50) DEFAULT NULL, ADD allocated_by_id INT DEFAULT NULL
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE plug_warranty_serial ADD CONSTRAINT FK_1A5DC26F6802B588 FOREIGN KEY (allocated_by_id) REFERENCES user (id)
+ SQL);
+ $this->addSql(<<<'SQL'
+ CREATE INDEX IDX_1A5DC26F6802B588 ON plug_warranty_serial (allocated_by_id)
+ SQL);
+ }
+
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->addSql(<<<'SQL'
+ ALTER TABLE plug_warranty_serial DROP FOREIGN KEY FK_1A5DC26F6802B588
+ SQL);
+ $this->addSql(<<<'SQL'
+ DROP INDEX IDX_1A5DC26F6802B588 ON plug_warranty_serial
+ SQL);
+ $this->addSql(<<<'SQL'
+ ALTER TABLE plug_warranty_serial DROP device_serial, DROP allocated_to_document_type, DROP allocated_by_id
+ SQL);
+ }
+}
diff --git a/hesabixCore/migrations/Version20250820235141.php b/hesabixCore/migrations/Version20250820235141.php
new file mode 100644
index 0000000..a525e46
--- /dev/null
+++ b/hesabixCore/migrations/Version20250820235141.php
@@ -0,0 +1,35 @@
+addSql(<<<'SQL'
+ ALTER TABLE plug_warranty_serial DROP device_serial
+ SQL);
+ }
+
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->addSql(<<<'SQL'
+ ALTER TABLE plug_warranty_serial ADD device_serial VARCHAR(255) DEFAULT NULL
+ SQL);
+ }
+}
diff --git a/hesabixCore/src/Controller/ApprovalController.php b/hesabixCore/src/Controller/ApprovalController.php
index 6cf9686..865451b 100644
--- a/hesabixCore/src/Controller/ApprovalController.php
+++ b/hesabixCore/src/Controller/ApprovalController.php
@@ -8,6 +8,7 @@ use App\Entity\HesabdariRow;
use App\Entity\Log;
use App\Entity\Permission;
use App\Entity\User;
+use App\Entity\Year;
use App\Service\Access;
use App\Service\Log as LogService;
use Doctrine\ORM\EntityManagerInterface;
@@ -55,6 +56,10 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این حواله را ندارید']);
}
+ if (!$this->checkDocumentYear($ticket->getDoc(), $business, $entityManager)) {
+ return $this->json(['success' => false, 'message' => 'حواله مربوط به این سال مالی نیست']);
+ }
+
$ticket->setIsPreview(false);
$ticket->setIsApproved(true);
$ticket->setApprovedBy($user);
@@ -117,6 +122,10 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این حواله را ندارید']);
}
+ if (!$this->checkDocumentYear($ticket->getDoc(), $business, $entityManager)) {
+ return $this->json(['success' => false, 'message' => 'حواله مربوط به این سال مالی نیست']);
+ }
+
$ticket->setIsPreview(true);
$ticket->setIsApproved(false);
$ticket->setApprovedBy(null);
@@ -179,6 +188,10 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
}
+ if (!$this->checkDocumentYear($document, $business, $entityManager)) {
+ return $this->json(['success' => false, 'message' => 'فاکتور مربوط به این سال مالی نیست']);
+ }
+
$document->setIsPreview(false);
$document->setIsApproved(true);
$document->setApprovedBy($user);
@@ -262,6 +275,10 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'فاکتور فروش تایید شده است']);
}
+ if (!$this->checkDocumentYear($document, $business, $entityManager)) {
+ return $this->json(['success' => false, 'message' => 'فاکتور مربوط به این سال مالی نیست']);
+ }
+
$document->setIsPreview(false);
$document->setIsApproved(true);
$document->setApprovedBy($user);
@@ -338,6 +355,10 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
}
+ if (!$this->checkDocumentYear($document, $business, $entityManager)) {
+ return $this->json(['success' => false, 'message' => 'فاکتور مربوط به این سال مالی نیست']);
+ }
+
$document->setIsPreview(true);
$document->setIsApproved(false);
$document->setApprovedBy(null);
@@ -413,6 +434,10 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
}
+ if (!$this->checkDocumentYear($document, $business, $entityManager)) {
+ return $this->json(['success' => false, 'message' => 'فاکتور خرید مربوط به این سال مالی نیست']);
+ }
+
$document->setIsPreview(false);
$document->setIsApproved(true);
$document->setApprovedBy($user);
@@ -483,6 +508,10 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'فاکتور خرید تایید شده است']);
}
+ if (!$this->checkDocumentYear($document, $business, $entityManager)) {
+ return $this->json(['success' => false, 'message' => 'فاکتور خرید مربوط به این سال مالی نیست']);
+ }
+
$document->setIsPreview(false);
$document->setIsApproved(true);
$document->setApprovedBy($user);
@@ -547,6 +576,10 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']);
}
+ if (!$this->checkDocumentYear($document, $business, $entityManager)) {
+ return $this->json(['success' => false, 'message' => 'فاکتور خرید مربوط به این سال مالی نیست']);
+ }
+
$document->setIsPreview(true);
$document->setIsApproved(false);
$document->setApprovedBy(null);
@@ -617,6 +650,10 @@ class ApprovalController extends AbstractController
return $this->json(['success' => false, 'message' => 'فاکتور خرید تایید نشده است']);
}
+ if (!$this->checkDocumentYear($document, $business, $entityManager)) {
+ return $this->json(['success' => false, 'message' => 'فاکتور خرید مربوط به این سال مالی نیست']);
+ }
+
$document->setIsPreview(true);
$document->setIsApproved(false);
$document->setApprovedBy(null);
@@ -646,6 +683,15 @@ class ApprovalController extends AbstractController
}
}
+ private function checkDocumentYear(HesabdariDoc $document, Business $business, EntityManagerInterface $entityManager): bool
+ {
+ $year = $entityManager->getRepository(Year::class)->findOneBy([
+ 'bid' => $business,
+ 'head' => true
+ ]);
+ return $document->getYear()->getId() == $year->getId();
+ }
+
private function canUserApproveDocument(User $user, Business $business, string $documentType): bool
{
if ($user->getEmail() === $business->getOwner()->getEmail()) {
diff --git a/hesabixCore/src/Controller/BusinessController.php b/hesabixCore/src/Controller/BusinessController.php
index 18d7fbb..c58bcde 100644
--- a/hesabixCore/src/Controller/BusinessController.php
+++ b/hesabixCore/src/Controller/BusinessController.php
@@ -583,10 +583,10 @@ class BusinessController extends AbstractController
'plugGhestaManager' => true,
'plugTaxSettings' => true,
'plugWarranty' => true,
+ 'plugImportWorkflow' => true,
'inquiry' => true,
'ai' => true,
- 'warehouseManager' => true,
- 'importWorkflow' => true,
+ 'storehelper' => true,
];
} elseif ($perm) {
$result = [
@@ -632,18 +632,11 @@ class BusinessController extends AbstractController
'plugGhestaManager' => $perm->isPlugGhestaManager(),
'plugTaxSettings' => $perm->isPlugTaxSettings(),
'plugWarranty' => $perm->isPlugWarrantyManager(),
+ 'plugImportWorkflow' => $perm->isImportWorkflow(),
'inquiry' => $perm->isInquiry(),
'ai' => $perm->isAi(),
- 'warehouseManager' => $perm->isWarehouseManager(),
- 'importWorkflow' => $perm->isImportWorkflow(),
+ 'storehelper' => $perm->isStorehelper()
];
-
- if ($perm->isWarehouseManager()) {
- $result['commodity'] = true;
- $result['store'] = true;
- $result['plugWarranty'] = true;
- $result['permission'] = true;
- }
}
return $this->json($result);
}
@@ -714,10 +707,10 @@ class BusinessController extends AbstractController
$perm->setPlugGhestaManager($params['plugGhestaManager']);
$perm->setPlugWarrantyManager($params['plugWarranty'] ?? false);
$perm->setPlugTaxSettings($params['plugTaxSettings']);
+ $perm->setImportWorkflow($params['plugImportWorkflow'] ?? false);
$perm->setInquiry($params['inquiry']);
$perm->setAi($params['ai']);
- $perm->setWarehouseManager($params['warehouseManager'] ?? false);
- $perm->setImportWorkflow($params['importWorkflow'] ?? false);
+ $perm->setStorehelper($params['storehelper'] ?? false);
$entityManager->persist($perm);
$entityManager->flush();
$log->insert('تنظیمات پایه', 'ویرایش دسترسیهای کاربر با پست الکترونیکی ' . $user->getEmail(), $this->getUser(), $business);
diff --git a/hesabixCore/src/Controller/Plugins/PlugWarrantyController.php b/hesabixCore/src/Controller/Plugins/PlugWarrantyController.php
index ccb9735..8c6f9a2 100644
--- a/hesabixCore/src/Controller/Plugins/PlugWarrantyController.php
+++ b/hesabixCore/src/Controller/Plugins/PlugWarrantyController.php
@@ -71,9 +71,9 @@ class PlugWarrantyController extends AbstractController
$serials = $entityManager->getRepository(PlugWarrantySerial::class)->createQueryBuilder('s')
->andWhere('s.business = :bid')
- ->andWhere('s.allocatedToDocumentId = :docId')
+ ->andWhere('s.activationTicketCode = :code')
->setParameter('bid', $acc['bid'])
- ->setParameter('docId', $doc->getId())
+ ->setParameter('code', $code)
->getQuery()
->getResult();
@@ -81,6 +81,7 @@ class PlugWarrantyController extends AbstractController
$commodity = $s->getCommodity();
return [
'serialNumber' => $s->getSerialNumber(),
+ 'commoditySerial' => $s->getCommoditySerial(),
'commodity' => $commodity ? [
'id' => $commodity->getId(),
'name' => $commodity->getName(),
diff --git a/hesabixCore/src/Controller/StoreroomController.php b/hesabixCore/src/Controller/StoreroomController.php
index ebe968b..a1e2f2d 100644
--- a/hesabixCore/src/Controller/StoreroomController.php
+++ b/hesabixCore/src/Controller/StoreroomController.php
@@ -60,16 +60,18 @@ class StoreroomController extends AbstractController
public function uploadTicketAttachment(string $code, Request $request, Access $access, EntityManagerInterface $entityManager, \App\Service\FileStorage $storage): JsonResponse
{
$acc = $access->hasRole('store');
- if (!$acc) throw $this->createAccessDeniedException();
- $ticket = $entityManager->getRepository(StoreroomTicket::class)->findOneBy(['bid'=>$acc['bid'],'code'=>$code]);
- if (!$ticket) throw $this->createNotFoundException('حواله یافت نشد');
+ if (!$acc)
+ throw $this->createAccessDeniedException();
+ $ticket = $entityManager->getRepository(StoreroomTicket::class)->findOneBy(['bid' => $acc['bid'], 'code' => $code]);
+ if (!$ticket)
+ throw $this->createNotFoundException('حواله یافت نشد');
$file = $request->files->get('file');
if (!$file) {
- return $this->json(['result'=>-1,'message'=>'فایل ارسال نشده است'], 400);
+ return $this->json(['result' => -1, 'message' => 'فایل ارسال نشده است'], 400);
}
-
- $stored = $storage->store($file, (string)$acc['bid']->getId(), 'storeroom_attachments');
+
+ $stored = $storage->store($file, (string) $acc['bid']->getId(), 'storeroom_attachments');
$archive = new ArchiveFile();
$archive->setBid($acc['bid']);
@@ -82,33 +84,35 @@ class StoreroomController extends AbstractController
$archive->setDes($request->request->get('des'));
$archive->setRelatedDocType('storeroom_ticket');
$archive->setRelatedDocCode($ticket->getCode());
- $archive->setFileSize($stored['size'] !== null ? (string)$stored['size'] : null);
+ $archive->setFileSize($stored['size'] !== null ? (string) $stored['size'] : null);
$entityManager->persist($archive);
$entityManager->flush();
- return $this->json(['result'=>0]);
+ return $this->json(['result' => 0]);
}
#[Route('/api/storeroom/ticket/attachments/{code}', name: 'app_storeroom_ticket_list_attachments', methods: ['GET'])]
public function listTicketAttachments(string $code, Access $access, EntityManagerInterface $entityManager): JsonResponse
{
$acc = $access->hasRole('store');
- if (!$acc) throw $this->createAccessDeniedException();
- $ticket = $entityManager->getRepository(StoreroomTicket::class)->findOneBy(['bid'=>$acc['bid'],'code'=>$code]);
- if (!$ticket) throw $this->createNotFoundException('حواله یافت نشد');
+ if (!$acc)
+ throw $this->createAccessDeniedException();
+ $ticket = $entityManager->getRepository(StoreroomTicket::class)->findOneBy(['bid' => $acc['bid'], 'code' => $code]);
+ if (!$ticket)
+ throw $this->createNotFoundException('حواله یافت نشد');
$items = $entityManager->getRepository(ArchiveFile::class)->findBy([
- 'bid'=>$acc['bid'],
- 'relatedDocType'=>'storeroom_ticket',
- 'relatedDocCode'=>$ticket->getCode()
- ], ['id'=>'DESC']);
- return $this->json(array_map(function(ArchiveFile $a){
+ 'bid' => $acc['bid'],
+ 'relatedDocType' => 'storeroom_ticket',
+ 'relatedDocCode' => $ticket->getCode()
+ ], ['id' => 'DESC']);
+ return $this->json(array_map(function (ArchiveFile $a) {
return [
- 'id'=>$a->getId(),
- 'filename'=>$a->getFilename(),
- 'fileType'=>$a->getFileType(),
- 'fileSize'=>$a->getFileSize(),
- 'des'=>$a->getDes(),
- 'dateSubmit'=>$a->getDateSubmit(),
+ 'id' => $a->getId(),
+ 'filename' => $a->getFilename(),
+ 'fileType' => $a->getFileType(),
+ 'fileSize' => $a->getFileSize(),
+ 'des' => $a->getDes(),
+ 'dateSubmit' => $a->getDateSubmit(),
];
}, $items));
}
@@ -117,12 +121,13 @@ class StoreroomController extends AbstractController
public function downloadTicketAttachment(int $id, Access $access, EntityManagerInterface $entityManager, \App\Service\FileStorage $storage): Response
{
$acc = $access->hasRole('store');
- if (!$acc) throw $this->createAccessDeniedException();
+ if (!$acc)
+ throw $this->createAccessDeniedException();
$a = $entityManager->getRepository(ArchiveFile::class)->find($id);
if (!$a || $a->getBid()->getId() !== $acc['bid']->getId()) {
throw $this->createNotFoundException('فایل یافت نشد');
}
- $abs = $storage->absolutePath((string)$a->getFilename());
+ $abs = $storage->absolutePath((string) $a->getFilename());
if (!is_file($abs) || !is_readable($abs)) {
throw $this->createNotFoundException('فایل موجود نیست');
}
@@ -201,7 +206,7 @@ class StoreroomController extends AbstractController
* @throws ReflectionException
*/
#[Route('/api/storeroom/docs/get', name: 'app_storeroom_get_docs')]
- public function app_storeroom_get_docs(Provider $provider,Extractor $extractor, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
+ public function app_storeroom_get_docs(Provider $provider, Extractor $extractor, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager): JsonResponse
{
$acc = $access->hasRole('store');
if (!$acc)
@@ -466,10 +471,8 @@ class StoreroomController extends AbstractController
if ($content = $request->getContent()) {
$params = json_decode($content, true);
}
- //check parameters exist
if ((!array_key_exists('ticket', $params)) || (!array_key_exists('items', $params)) || (!array_key_exists('doc', $params)))
$this->createNotFoundException();
- //going to save
$doc = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([
'id' => $params['doc']['id'],
'bid' => $acc['bid'],
@@ -479,14 +482,11 @@ class StoreroomController extends AbstractController
throw $this->createNotFoundException('سند یافت نشد');
if ($doc->getBid()->getId() != $acc['bid']->getId())
throw $this->createAccessDeniedException('دسترسی به این سند را ندارید.');
- //find transfer type
if (!array_key_exists('transferType', $params['ticket']))
throw $this->createNotFoundException('نوع انتقال یافت نشد');
$transferType = $entityManager->getRepository(StoreroomTransferType::class)->find($params['ticket']['transferType']['id']);
if (!$transferType)
throw $this->createNotFoundException('نوع انتقال یافت نشد');
-
- //find storeroom
if (!array_key_exists('store', $params['ticket']))
throw $this->createNotFoundException('انبار یافت نشد');
$storeroom = $entityManager->getRepository(Storeroom::class)->find($params['ticket']['store']['id']);
@@ -494,7 +494,6 @@ class StoreroomController extends AbstractController
throw $this->createNotFoundException('انبار یافت نشد');
elseif ($storeroom->getBid()->getId() != $acc['bid']->getId())
throw $this->createAccessDeniedException('دسترسی به این انبار ممکن نیست!');
- //find person
if (!array_key_exists('person', $params['ticket']))
throw $this->createNotFoundException('طرف حساب یافت نشد');
$person = $entityManager->getRepository(Person::class)->find($params['ticket']['person']['id']);
@@ -502,7 +501,6 @@ class StoreroomController extends AbstractController
throw $this->createNotFoundException('طرف حساب یافت نشد');
elseif ($person->getBid()->getId() != $acc['bid']->getId())
throw $this->createAccessDeniedException('دسترسی به این طرف حساب ممکن نیست!');
-
$ticket = new StoreroomTicket();
$ticket->setSubmitter($this->getUser());
$ticket->setDate($params['ticket']['date']);
@@ -515,7 +513,9 @@ class StoreroomController extends AbstractController
$ticket->setCode($provider->getAccountingCode($acc['bid'], 'storeroom'));
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$rand = '';
- for ($i = 0; $i < 8; $i++) { $rand .= $alphabet[random_int(0, strlen($alphabet)-1)]; }
+ for ($i = 0; $i < 8; $i++) {
+ $rand .= $alphabet[random_int(0, strlen($alphabet) - 1)];
+ }
$ticket->setActivationCode($rand);
$ticket->setReceiver($params['ticket']['receiver']);
$ticket->setTransferType($transferType);
@@ -529,32 +529,25 @@ class StoreroomController extends AbstractController
$ticket->setImportWorkflowCode($params['ticket']['importWorkflowCode']);
}
$entityManager->persist($ticket);
- //$entityManager->flush();
-
- //going to save rows
$docRows = $entityManager->getRepository(HesabdariRow::class)->findBy([
'doc' => $doc
]);
-
- // Determine if warranty serials are required based on flag or provided lines
- $hasSerialLines = false;
- foreach (($params['items'] ?? []) as $it) {
- if (!empty($it['serialLines']) && is_array($it['serialLines'])) { $hasSerialLines = true; break; }
- }
- $requireWarrantySerial = (isset($params['ticket']['requireWarrantySerial']) && $params['ticket']['requireWarrantySerial'] === true) || $hasSerialLines;
+ $requireWarrantySerial = (
+ isset($params['ticket']['requireWarrantySerial'])
+ && $params['ticket']['requireWarrantySerial'] === true
+ && $pluginService->isActive('warranty', $acc['bid'])
+ );
if ($requireWarrantySerial) {
- if (!$pluginService->isActive('warranty', $acc['bid'])) {
- return $this->json(['result' => -5, 'message' => 'افزونه گارانتی فعال نیست'], 403);
- }
- // Validate counts up-front
foreach ($params['items'] as $item) {
$lines = isset($item['serialLines']) && is_array($item['serialLines']) ? $item['serialLines'] : [];
- if ((int)($item['ticketCount'] ?? 0) > 0 && count($lines) < (int)$item['ticketCount']) {
- return $this->json(['result' => -3, 'message' => 'تعداد سریال/گارانتی با تعداد حواله همخوانی ندارد'], 400);
+ if ((int) ($item['ticketCount'] ?? 0) > 0 && count($lines) < (int) $item['ticketCount']) {
+ return $this->json([
+ 'result' => -3,
+ 'message' => 'تعداد سریال/گارانتی با تعداد حواله همخوانی ندارد'
+ ], 400);
}
}
}
-
foreach ($params['items'] as $item) {
$row = $entityManager->getRepository(HesabdariRow::class)->findOneBy([
'bid' => $acc['bid'],
@@ -565,7 +558,6 @@ class StoreroomController extends AbstractController
throw $this->createNotFoundException('کالا یافت نشد!');
if (!$row->getCommodity())
throw $this->createNotFoundException('کالا یافت نشد!');
- //check row count not upper ticket count
if ($row->getCommdityCount() < $item['ticketCount'])
throw $this->createNotFoundException('تعداد کالای اضافه شده بیشتر از تعداد کالا در فاکتور است.');
$ticketItem = new StoreroomItem();
@@ -578,21 +570,17 @@ class StoreroomController extends AbstractController
$ticketItem->setCommodity($row->getCommodity());
$ticketItem->setType($item['type']);
$entityManager->persist($ticketItem);
-
- // Bind warranty serials per item if provided
+ $lines = isset($item['serialLines']) && is_array($item['serialLines']) ? $item['serialLines'] : [];
if ($requireWarrantySerial) {
- $lines = isset($item['serialLines']) && is_array($item['serialLines']) ? $item['serialLines'] : [];
- if ((int)$item['ticketCount'] > 0) {
- // Ensure we have an id to bind to
+ if ((int) $item['ticketCount'] > 0) {
$entityManager->flush();
- $lines = array_slice($lines, 0, (int)$item['ticketCount']);
+ $lines = array_slice($lines, 0, (int) $item['ticketCount']);
foreach ($lines as $ln) {
$warrantyCode = $ln['warranty'] ?? null;
$deviceSerial = $ln['serial'] ?? null;
if (!$warrantyCode) {
return $this->json(['result' => -4, 'message' => 'کد گارانتی ارسال نشده است'], 400);
}
- /** @var PlugWarrantySerial|null $serial */
$serial = $entityManager->getRepository(PlugWarrantySerial::class)->findOneBy([
'business' => $acc['bid'],
'serialNumber' => $warrantyCode,
@@ -613,27 +601,51 @@ class StoreroomController extends AbstractController
$entityManager->persist($serial);
}
}
+ } else {
+ if (!empty($lines)) {
+ $entityManager->flush();
+ foreach ($lines as $ln) {
+ $warrantyCode = $ln['warranty'] ?? null;
+ $deviceSerial = $ln['serial'] ?? null;
+ if (!$warrantyCode) {
+ continue;
+ }
+ $serial = $entityManager->getRepository(PlugWarrantySerial::class)->findOneBy([
+ 'business' => $acc['bid'],
+ 'serialNumber' => $warrantyCode,
+ 'commodity' => $row->getCommodity(),
+ ]);
+ if (!$serial || $serial->getStatus() !== PlugWarrantySerial::STATUS_AVAILABLE) {
+ continue;
+ }
+ $serial->setStatus(PlugWarrantySerial::STATUS_CONSUMED);
+ $serial->setCommoditySerial($deviceSerial);
+ $serial->setBuyer($person);
+ $serial->setAllocatedToDocumentId($doc->getId());
+ $serial->setAllocatedAt(new \DateTimeImmutable());
+ $serial->setBoundToItemId($ticketItem->getId());
+ $serial->setBoundAt(new \DateTimeImmutable());
+ $serial->setActivationTicketCode($ticket->getCode());
+ $serial->setActivationTicketSecret($ticket->getActivationCode());
+ $entityManager->persist($serial);
+ }
+ }
}
-
}
-
$entityManager->flush();
-
$business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']);
- $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false;
+ $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool) $business->isRequireTwoStepApproval() : false;
if ($businessRequire) {
$ticket->setIsPreview(true);
$ticket->setIsApproved(false);
- $ticket->setApprovedBy(null); // هنوز تأیید نشده
+ $ticket->setApprovedBy(null);
} else {
$ticket->setIsPreview(false);
$ticket->setIsApproved(true);
- $ticket->setApprovedBy($this->getUser()); // تأیید شده توسط کاربر فعلی
+ $ticket->setApprovedBy($this->getUser());
}
- //save logs
$log->insert('انبارداری', 'حواله انبار با شماره ' . $ticket->getCode() . ' اضافه / ویرایش شد.', $this->getUser(), $acc['bid']);
if ($pluginService->isActive('accpro', $acc['bid'])) {
- //notification to person
if ($params['ticket']['sms'] == true) {
$ticket->setCanShare(true);
$entityManager->persist($ticket);
@@ -676,7 +688,6 @@ class StoreroomController extends AbstractController
3
);
}
-
if ($smsres == 2) {
return $this->json([
'result' => 2
@@ -684,7 +695,6 @@ class StoreroomController extends AbstractController
}
}
}
-
return $this->json([
'result' => 0
]);
@@ -698,7 +708,7 @@ class StoreroomController extends AbstractController
throw $this->createAccessDeniedException();
$params = json_decode($request->getContent() ?: '{}', true);
$status = $params['status'] ?? null; // in_progress|done|rejected|approved|pending_approval
- if (!in_array($status, ['in_progress','done','rejected','approved','pending_approval'])) {
+ if (!in_array($status, ['in_progress', 'done', 'rejected', 'approved', 'pending_approval'])) {
return $this->json(['result' => -1, 'message' => 'وضعیت نامعتبر'], 400);
}
$ticket = $entityManager->getRepository(StoreroomTicket::class)->findOneBy([
@@ -729,7 +739,7 @@ class StoreroomController extends AbstractController
$criteria['status'] = $status;
}
$tickets = $entityManager->getRepository(StoreroomTicket::class)->findBy($criteria, ['date' => 'DESC']);
- return $this->json(array_map(function(StoreroomTicket $t){
+ return $this->json(array_map(function (StoreroomTicket $t) {
return [
'code' => $t->getCode(),
'date' => $t->getDate(),
@@ -764,7 +774,8 @@ class StoreroomController extends AbstractController
'getDoc',
'getTypeString',
'isPreview',
- 'isApproved'
+ 'isApproved',
+ 'isCompleted'
], 2);
foreach ($result as $key => &$ticket) {
@@ -779,6 +790,16 @@ class StoreroomController extends AbstractController
} else {
$ticket['approvedBy'] = null;
}
+ if ($ticketEntity->getCompletedBy()) {
+ $completedBy = $ticketEntity->getCompletedBy();
+ $ticket['completedBy'] = [
+ 'id' => $completedBy->getId(),
+ 'fullName' => $completedBy->getFullName(),
+ 'email' => $completedBy->getEmail()
+ ];
+ } else {
+ $ticket['completedBy'] = null;
+ }
}
return $this->json($result);
@@ -799,7 +820,7 @@ class StoreroomController extends AbstractController
//get items
$items = $entityManager->getRepository(StoreroomItem::class)->findBy(['ticket' => $ticket]);
$res = [];
- $res['ticket'] = $provider->Entity2ArrayJustIncludes($ticket, ['getStoreroom', 'getManager', 'getDate', 'getSubmitDate', 'getDes', 'getReceiver', 'getTransfer', 'getCode', 'getType', 'getReferral', 'getTypeString', 'isPreview', 'isApproved'], 2);
+ $res['ticket'] = $provider->Entity2ArrayJustIncludes($ticket, ['getStoreroom', 'getManager', 'getDate', 'getSubmitDate', 'getDes', 'getReceiver', 'getTransfer', 'getCode', 'getType', 'getReferral', 'getTypeString', 'isPreview', 'isApproved', 'isCompleted'], 2);
$res['transferType'] = $provider->Entity2ArrayJustIncludes($ticket->getTransferType(), ['getName'], 0);
$res['person'] = $provider->Entity2ArrayJustIncludes($ticket->getPerson(), ['getKeshvar', 'getOstan', 'getShahr', 'getAddress', 'getNikename', 'getCodeeghtesadi', 'getPostalcode', 'getName', 'getTel', 'getSabt'], 0);
//get rows
@@ -916,9 +937,9 @@ class StoreroomController extends AbstractController
} else {
$title = 'حواله خروج از انبار';
}
-
+
$business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']);
- $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false;
+ $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool) $business->isRequireTwoStepApproval() : false;
if ($businessRequire && $doc->isApproved() !== true && $doc->isPreview() == true) {
if ($doc->isPreview()) {
return $this->json(['result' => -10, 'message' => 'حواله هنوز تایید نشده است'], 403);
@@ -973,4 +994,103 @@ class StoreroomController extends AbstractController
);
return $this->json(['id' => $pdfPid]);
}
+
+ #[Route('/api/storeroom/ticket/complete/{id}', name: 'app_storeroom_ticket_complete', methods: ['POST'])]
+ public function app_storeroom_ticket_complete(string $id, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, PluginService $pluginService): JsonResponse
+ {
+ $acc = $access->hasRole('store');
+ if (!$acc)
+ throw $this->createAccessDeniedException();
+
+ $params = [];
+ if ($content = $request->getContent()) {
+ $params = json_decode($content, true);
+ }
+
+ $ticket = $entityManager->getRepository(StoreroomTicket::class)->findOneBy([
+ 'code' => $id,
+ 'bid' => $acc['bid']
+ ]);
+ if (!$ticket)
+ throw $this->createNotFoundException('حواله یافت نشد');
+
+ $requireWarrantySerial = (
+ isset($params['requireWarrantySerial'])
+ && $params['requireWarrantySerial'] === true
+ && $pluginService->isActive('warranty', $acc['bid'])
+ );
+
+ if ($pluginService->isActive('warranty', $acc['bid'])) {
+ $warrantyAllocations = $params['warrantyAllocations'] ?? [];
+ foreach ($warrantyAllocations as $allocation) {
+ $commodityId = $allocation['commodityId'] ?? null;
+ $warrantyLines = $allocation['warrantyLines'] ?? [];
+ if (!$commodityId || empty($warrantyLines)) {
+ if ($requireWarrantySerial) {
+ return $this->json(['result' => -3, 'message' => 'سریال گارانتی برای کالا ارسال نشده است'], 400);
+ }
+ continue;
+ }
+ $commodity = $entityManager->getRepository(Commodity::class)->find($commodityId);
+ if (!$commodity) {
+ if ($requireWarrantySerial) {
+ return $this->json(['result' => -3, 'message' => 'کالا معتبر نیست برای گارانتی'], 400);
+ }
+ continue;
+ }
+ foreach ($warrantyLines as $line) {
+ $warrantySerial = $line['warrantySerial'] ?? null;
+ $deviceSerial = $line['serialNumber'] ?? null;
+ $isBeforeAllocated = $line['isBeforeAllocated'] ?? false;
+ if (!$warrantySerial) {
+ if ($requireWarrantySerial) {
+ return $this->json(['result' => -4, 'message' => 'کد گارانتی ارسال نشده است'], 400);
+ }
+ continue;
+ }
+ if ($isBeforeAllocated) {
+ continue;
+ }
+ $warrantySerialEntity = $entityManager->getRepository(PlugWarrantySerial::class)->findOneBy([
+ 'business' => $acc['bid'],
+ 'serialNumber' => $warrantySerial,
+ 'commodity' => $commodity,
+ 'status' => PlugWarrantySerial::STATUS_AVAILABLE
+ ]);
+ if (!$warrantySerialEntity) {
+ if ($requireWarrantySerial) {
+ return $this->json(['result' => -2, 'message' => "گارانتی {$warrantySerial} یافت نشد یا در دسترس نیست"], 400);
+ }
+ continue;
+ }
+ $warrantySerialEntity->setStatus(PlugWarrantySerial::STATUS_ALLOCATED);
+ $warrantySerialEntity->setAllocatedToDocumentId($ticket->getId());
+ $warrantySerialEntity->setActivationTicketCode($ticket->getCode());
+ $warrantySerialEntity->setActivationTicketSecret($ticket->getActivationCode());
+ $warrantySerialEntity->setAllocatedToDocumentType('storeroom_ticket');
+ $warrantySerialEntity->setAllocatedAt(new \DateTimeImmutable());
+ $warrantySerialEntity->setAllocatedBy($this->getUser());
+ if ($deviceSerial) {
+ $warrantySerialEntity->setCommoditySerial($deviceSerial);
+ }
+ $entityManager->persist($warrantySerialEntity);
+ }
+ }
+ }
+
+ $ticket->setCompleted(true);
+ $ticket->setCompletedAt(new \DateTimeImmutable());
+ $ticket->setCompletedBy($this->getUser());
+
+ $entityManager->persist($ticket);
+ $entityManager->flush();
+
+ $log->insert('انبارداری', 'پروسه حواله انبار با شماره ' . $ticket->getCode() . ' تکمیل شد.', $this->getUser(), $acc['bid']);
+
+ return $this->json([
+ 'result' => 0,
+ 'message' => 'پروسه با موفقیت تکمیل شد'
+ ]);
+ }
+
}
diff --git a/hesabixCore/src/Entity/Permission.php b/hesabixCore/src/Entity/Permission.php
index 48ac1a0..1c364e6 100644
--- a/hesabixCore/src/Entity/Permission.php
+++ b/hesabixCore/src/Entity/Permission.php
@@ -142,7 +142,7 @@ class Permission
private ?bool $ai = null;
#[ORM\Column(nullable: true)]
- private ?bool $warehouseManager = null;
+ private ?bool $storehelper = null;
#[ORM\Column(nullable: true)]
private ?bool $importWorkflow = null;
@@ -656,14 +656,14 @@ class Permission
return $this;
}
- public function isWarehouseManager(): ?bool
+ public function isStorehelper(): ?bool
{
- return $this->warehouseManager;
+ return $this->storehelper;
}
- public function setWarehouseManager(?bool $warehouseManager): static
+ public function setStorehelper(?bool $storehelper): static
{
- $this->warehouseManager = $warehouseManager;
+ $this->storehelper = $storehelper;
return $this;
}
diff --git a/hesabixCore/src/Entity/PlugWarrantySerial.php b/hesabixCore/src/Entity/PlugWarrantySerial.php
index a557047..9bf1b14 100644
--- a/hesabixCore/src/Entity/PlugWarrantySerial.php
+++ b/hesabixCore/src/Entity/PlugWarrantySerial.php
@@ -82,6 +82,12 @@ class PlugWarrantySerial
#[ORM\ManyToOne]
private ?Person $buyer = null;
+ #[ORM\Column(type: 'string', length: 50, nullable: true)]
+ private ?string $allocatedToDocumentType = null;
+
+ #[ORM\ManyToOne]
+ private ?User $allocatedBy = null;
+
#[ORM\Column(type: 'string', length: 32, nullable: true)]
private ?string $activationTicketCode = null;
@@ -152,6 +158,12 @@ class PlugWarrantySerial
public function getBuyer(): ?Person { return $this->buyer; }
public function setBuyer(?Person $buyer): self { $this->buyer = $buyer; return $this; }
+ public function getAllocatedToDocumentType(): ?string { return $this->allocatedToDocumentType; }
+ public function setAllocatedToDocumentType(?string $type): self { $this->allocatedToDocumentType = $type; return $this; }
+
+ public function getAllocatedBy(): ?User { return $this->allocatedBy; }
+ public function setAllocatedBy(?User $user): self { $this->allocatedBy = $user; return $this; }
+
public function getActivationTicketCode(): ?string { return $this->activationTicketCode; }
public function setActivationTicketCode(?string $code): self { $this->activationTicketCode = $code; return $this; }
public function getActivationTicketSecret(): ?string { return $this->activationTicketSecret; }
diff --git a/hesabixCore/src/Entity/StoreroomTicket.php b/hesabixCore/src/Entity/StoreroomTicket.php
index 728e72e..70cd260 100644
--- a/hesabixCore/src/Entity/StoreroomTicket.php
+++ b/hesabixCore/src/Entity/StoreroomTicket.php
@@ -96,6 +96,17 @@ class StoreroomTicket
#[ORM\JoinColumn(nullable: true)]
private ?User $approvedBy = null;
+ // Completion fields
+ #[ORM\Column(nullable: true)]
+ private ?bool $completed = null;
+
+ #[ORM\Column(type: 'datetime_immutable', nullable: true)]
+ private ?\DateTimeImmutable $completedAt = null;
+
+ #[ORM\ManyToOne]
+ #[ORM\JoinColumn(nullable: true)]
+ private ?User $completedBy = null;
+
public function __construct()
{
$this->storeroomItems = new ArrayCollection();
@@ -409,4 +420,38 @@ class StoreroomTicket
$this->approvedBy = $approvedBy;
return $this;
}
+
+ // Completion methods
+ public function isCompleted(): ?bool
+ {
+ return $this->completed;
+ }
+
+ public function setCompleted(?bool $completed): static
+ {
+ $this->completed = $completed;
+ return $this;
+ }
+
+ public function getCompletedAt(): ?\DateTimeImmutable
+ {
+ return $this->completedAt;
+ }
+
+ public function setCompletedAt(?\DateTimeImmutable $completedAt): static
+ {
+ $this->completedAt = $completedAt;
+ return $this;
+ }
+
+ public function getCompletedBy(): ?User
+ {
+ return $this->completedBy;
+ }
+
+ public function setCompletedBy(?User $completedBy): static
+ {
+ $this->completedBy = $completedBy;
+ return $this;
+ }
}
diff --git a/hesabixCore/src/Service/Access.php b/hesabixCore/src/Service/Access.php
index c519049..88e4024 100644
--- a/hesabixCore/src/Service/Access.php
+++ b/hesabixCore/src/Service/Access.php
@@ -137,7 +137,7 @@ class Access
'user'=>$this->user
]);
- if($warehousePermission && $warehousePermission->isWarehouseManager()){
+ if($warehousePermission && $warehousePermission->isStorehelper()){
$warehouseRoles = ['commodity', 'store', 'plugWarrantyManager'];
if(in_array($roll, $warehouseRoles)){
return $accessArray;
diff --git a/webUI/src/components/plugins/warranty/SerialDialog.vue b/webUI/src/components/plugins/warranty/SerialDialog.vue
index 71bccef..5e164bf 100644
--- a/webUI/src/components/plugins/warranty/SerialDialog.vue
+++ b/webUI/src/components/plugins/warranty/SerialDialog.vue
@@ -12,35 +12,32 @@