src/Controller/ComponentInventoryController.php line 136

  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\ComponentInventoryItem;
  4. use App\Form\Type\ComponentInventory\BatchSetQuantityType;
  5. use App\Form\Type\ComponentInventory\BatchDecreaseQuantityType;
  6. use App\Form\Type\ComponentInventory\BatchIncreaseQuantityType;
  7. use App\Form\Type\ComponentInventory\DecreaseQuantityType;
  8. use App\Form\Type\ComponentInventory\IncreaseQuantityType;
  9. use App\Form\Type\ComponentInventory\ItemCreateType;
  10. use App\Form\Type\ComponentInventory\ItemUpdateType;
  11. use App\Form\Type\ComponentInventory\LogFilterType;
  12. use App\Model\ComponentInventory\BatchSetQuantity;
  13. use App\Model\ComponentInventory\BatchDecreaseQuantity;
  14. use App\Model\ComponentInventory\BatchIncreaseQuantity;
  15. use App\Model\ComponentInventory\DecreaseQuantity;
  16. use App\Model\ComponentInventory\IncreaseQuantity;
  17. use App\Model\ComponentInventory\ItemCreate;
  18. use App\Model\ComponentInventory\ItemUpdate;
  19. use App\Model\DateRange;
  20. use App\Model\ModalResponse;
  21. use App\Repository\ComponentInventoryItemRepository;
  22. use App\Repository\ComponentInventoryLogEntryRepository;
  23. use App\Service\ComponentInventoryService;
  24. use Exception;
  25. use Symfony\Component\HttpFoundation\File\UploadedFile;
  26. use Symfony\Component\HttpFoundation\Request;
  27. use Symfony\Component\HttpFoundation\Response;
  28. use Symfony\Component\Routing\Annotation\Route;
  29. #[Route('/component-inventory')]
  30. class ComponentInventoryController extends AbstractController
  31. {
  32. const BATCH_DECREASE_DATA_KEY = 'batchDecreaseData';
  33. const BATCH_INCREASE_DATA_KEY = 'batchIncreaseData';
  34. const BATCH_SET_DATA_KEY = 'batchSetData';
  35. const HIDE_ZERO_QUANTITY_KEY = 'hideZeroQuantity';
  36. protected ComponentInventoryItemRepository $componentInventoryItemRepository;
  37. protected ComponentInventoryLogEntryRepository $componentInventoryLogEntryRepository;
  38. protected ComponentInventoryService $componentInventoryService;
  39. public function __construct(
  40. ComponentInventoryItemRepository $componentInventoryItemRepository,
  41. ComponentInventoryLogEntryRepository $componentInventoryLogEntryRepository,
  42. ComponentInventoryService $componentInventoryService
  43. ) {
  44. $this->componentInventoryItemRepository = $componentInventoryItemRepository;
  45. $this->componentInventoryLogEntryRepository = $componentInventoryLogEntryRepository;
  46. $this->componentInventoryService = $componentInventoryService;
  47. }
  48. #[Route(
  49. '/{item}/delete',
  50. name: 'app.component_inventory.delete',
  51. requirements: ['item' => '\d+'],
  52. methods: ['GET']
  53. )]
  54. public function deleteAction(ComponentInventoryItem $item): Response
  55. {
  56. $this->assertAdmin();
  57. try {
  58. $this->componentInventoryItemRepository->delete($item);
  59. $this->flashSuccess('ui.success.delete', [], 'ComponentInventoryItem');
  60. } catch (Exception $e) {
  61. $this->flashError('ui.error.delete', [], 'ComponentInventoryItem');
  62. if ($this->getCurrentUser()->isSuperAdmin()) {
  63. $this->flashError($e->getMessage());
  64. }
  65. }
  66. return $this->redirectResponse('app.component_inventory.index');
  67. }
  68. #[Route(
  69. '/excel',
  70. name: 'app.component_inventory.excel'
  71. )]
  72. public function excelAction(): Response
  73. {
  74. try {
  75. return $this->componentInventoryService->getInventoryExcelFile()->getResponse();
  76. } catch (Exception $e) {
  77. $this->flashError($e->getMessage());
  78. $this->flashError($e->getPrevious()->getMessage());
  79. return $this->redirectResponse('app.component_inventory.index');
  80. }
  81. }
  82. #[Route(
  83. '/',
  84. name: 'app.component_inventory.index'
  85. )]
  86. public function indexAction(Request $request): Response
  87. {
  88. return $this->render(
  89. 'ComponentInventory/index.html.twig',
  90. [
  91. 'items' => $this->componentInventoryItemRepository->findAll(),
  92. 'hideZeroQuantity' => $request->getSession()->get(self::HIDE_ZERO_QUANTITY_KEY, true),
  93. ]
  94. );
  95. }
  96. #[Route(
  97. '/toggle-hide-zero-quantity',
  98. name: 'app.component_inventory.index.toggle_hide_zero_quantity'
  99. )]
  100. public function indexToggleHideZeroQuantityAction(Request $request): Response
  101. {
  102. $request->getSession()->set(
  103. self::HIDE_ZERO_QUANTITY_KEY,
  104. !$request->getSession()->get(self::HIDE_ZERO_QUANTITY_KEY, true)
  105. );
  106. return $this->redirectResponse('app.component_inventory.index');
  107. }
  108. #[Route(
  109. '/{item}/log',
  110. name: 'app.component_inventory.item_log',
  111. requirements: ['item' => '\d+']
  112. )]
  113. public function itemLogAction(ComponentInventoryItem $item): Response
  114. {
  115. return $this->render(
  116. 'ComponentInventory/itemLog.html.twig',
  117. [
  118. 'item' => $item,
  119. ]
  120. );
  121. }
  122. #[Route(
  123. '/log',
  124. name: 'app.component_inventory.log'
  125. )]
  126. public function logAction(Request $request): Response
  127. {
  128. $form = $this->createForm(
  129. LogFilterType::class,
  130. null,
  131. [
  132. 'action' => $this->generateUrl('app.component_inventory.log'),
  133. ]
  134. );
  135. $period = null;
  136. $showZeroQuantity = false;
  137. $form->handleRequest($request);
  138. if ($form->isSubmitted()) {
  139. if ($form->isValid()) {
  140. /* @var DateRange $period */
  141. $period = $form->get('period')->getData();
  142. $showZeroQuantity = $form->get('showZeroQuantity')->getData();
  143. try {
  144. if ($form->get('excel')->isClicked()) {
  145. return $this->componentInventoryService->getEventLogExcelFile(
  146. $period, $showZeroQuantity
  147. )->getResponse();
  148. }
  149. } catch (Exception $e) {
  150. $this->flashError($e->getMessage());
  151. if (null !== $previous = $e->getPrevious()) {
  152. $this->flashError($previous->getMessage());
  153. }
  154. }
  155. }
  156. }
  157. return $this->render(
  158. 'ComponentInventory/log.html.twig',
  159. [
  160. 'logEntries' => $this->componentInventoryLogEntryRepository->findByCreated($period, $showZeroQuantity),
  161. 'form' => $form->createView(),
  162. ]
  163. );
  164. }
  165. #[Route(
  166. '/log/excel',
  167. name: 'app.component_inventory.log.excel'
  168. )]
  169. public function logExcelAction(): Response
  170. {
  171. try {
  172. return $this->componentInventoryService->getEventLogExcelFile()->getResponse();
  173. } catch (Exception $e) {
  174. $this->flashError($e->getMessage());
  175. $this->flashError($e->getPrevious()->getMessage());
  176. return $this->redirectResponse('app.component_inventory.index');
  177. }
  178. }
  179. #[Route(
  180. '/batch-decrease-quantity',
  181. name: 'app.component_inventory.batch_decrease_quantity.modal'
  182. )]
  183. public function modalBatchDecreaseQuantityAction(Request $request): ModalResponse
  184. {
  185. $response = new ModalResponse();
  186. $response->setSubmit(true);
  187. $response->setTitle(
  188. $this->trans('ui.batchDecreaseQuantity', [], 'ComponentInventoryItem')
  189. );
  190. $form = $this->createForm(
  191. BatchDecreaseQuantityType::class,
  192. null,
  193. [
  194. 'action' => $this->generateUrl(
  195. 'app.component_inventory.batch_decrease_quantity.modal'
  196. )
  197. ]
  198. );
  199. $form->handleRequest($request);
  200. if ($form->isSubmitted()) {
  201. if ($form->isValid()) {
  202. try {
  203. /* @var BatchDecreaseQuantity $data */
  204. $data = $form->getData();
  205. $import = $request->getSession()->get(self::BATCH_DECREASE_DATA_KEY);
  206. foreach ($import['items'] as $item) {
  207. if (null !== $obj = $this->componentInventoryItemRepository->findByCode($item['code'])) {
  208. $data->updateItem($obj, $item['quantity'], $import['file']);
  209. $this->componentInventoryItemRepository->save($obj);
  210. }
  211. }
  212. $response->setRedirect(
  213. $this->generateUrl('app.component_inventory.index')
  214. );
  215. } catch (Exception $e) {
  216. $response->setError($e->getMessage());
  217. }
  218. }
  219. }
  220. $response->setBody(
  221. $this->renderView(
  222. 'ComponentInventory/batchDecreaseQuantityForm.html.twig',
  223. [
  224. 'form' => $form->createView(),
  225. ]
  226. )
  227. );
  228. return $response;
  229. }
  230. #[Route(
  231. '/batch-decrease-quantity/preview',
  232. name: 'app.component_inventory.batch_decrease_quantity.preview'
  233. )]
  234. public function modalBatchDecreaseQuantityPreviewAction(Request $request): Response
  235. {
  236. /* @var UploadedFile $file */
  237. $file = $request->files->get('excel');
  238. try {
  239. $fileName = $file->getClientOriginalName();
  240. $items = $this->componentInventoryService->parseBatchDecreaseExcel($file);
  241. $hasErrors = false;
  242. foreach ($items as $item) {
  243. if (null !== $item['error']) {
  244. $hasErrors = true;
  245. break;
  246. }
  247. }
  248. $request->getSession()->remove(self::BATCH_DECREASE_DATA_KEY);
  249. if (!$hasErrors) {
  250. $data = [
  251. 'items' => $items,
  252. 'file' => $fileName,
  253. ];
  254. $request->getSession()->set(self::BATCH_DECREASE_DATA_KEY, $data);
  255. }
  256. return $this->render(
  257. 'ComponentInventory/batchDecreaseQuantityPreview.html.twig',
  258. [
  259. 'items' => $items,
  260. 'hasErrors' => $hasErrors,
  261. ]
  262. );
  263. } catch (Exception $e) {
  264. return $this->render(
  265. 'ComponentInventory/excelError.html.twig',
  266. [
  267. 'error' => $e->getMessage(),
  268. ],
  269. new Response('', 400)
  270. );
  271. }
  272. }
  273. #[Route(
  274. '/batch-increase-quantity',
  275. name: 'app.component_inventory.batch_increase_quantity.modal'
  276. )]
  277. public function modalBatchIncreaseQuantityAction(Request $request): ModalResponse
  278. {
  279. $response = new ModalResponse();
  280. $response->setSubmit(true);
  281. $response->setTitle(
  282. $this->trans('ui.batchIncreaseQuantity', [], 'ComponentInventoryItem')
  283. );
  284. $form = $this->createForm(
  285. BatchIncreaseQuantityType::class,
  286. null,
  287. [
  288. 'action' => $this->generateUrl(
  289. 'app.component_inventory.batch_increase_quantity.modal'
  290. )
  291. ]
  292. );
  293. $form->handleRequest($request);
  294. if ($form->isSubmitted()) {
  295. if ($form->isValid()) {
  296. try {
  297. /* @var BatchIncreaseQuantity $data */
  298. $data = $form->getData();
  299. $import = $request->getSession()->get(self::BATCH_INCREASE_DATA_KEY);
  300. foreach ($import['items'] as $item) {
  301. if (null !== $obj = $this->componentInventoryItemRepository->findByCode($item['code'])) {
  302. $data->updateItem($obj, $item['description'], $item['quantity'], $item['unitPrice'], $import['file']);
  303. } else {
  304. $obj = new ItemCreate(
  305. $item['code'],
  306. $item['type'],
  307. $item['quantity'],
  308. $item['unitPrice'],
  309. $item['description'],
  310. $data->getLogEntryNote(),
  311. $data->getPurchaseOrderNumber(),
  312. $import['file']
  313. )->toItem();
  314. }
  315. $this->componentInventoryItemRepository->save($obj);
  316. }
  317. $response->setRedirect(
  318. $this->generateUrl('app.component_inventory.index')
  319. );
  320. } catch (Exception $e) {
  321. $response->setError($e->getMessage());
  322. }
  323. }
  324. }
  325. $response->setBody(
  326. $this->renderView(
  327. 'ComponentInventory/batchIncreaseQuantityForm.html.twig',
  328. [
  329. 'form' => $form->createView(),
  330. ]
  331. )
  332. );
  333. return $response;
  334. }
  335. #[Route(
  336. '/batch-increase-quantity/preview',
  337. name: 'app.component_inventory.batch_increase_quantity.preview'
  338. )]
  339. public function modalBatchIncreaseQuantityPreviewAction(Request $request): Response
  340. {
  341. /* @var UploadedFile $file */
  342. $file = $request->files->get('excel');
  343. try {
  344. $fileName = $file->getClientOriginalName();
  345. $items = $this->componentInventoryService->parseBatchIncreaseExcel($file);
  346. $hasErrors = false;
  347. foreach ($items as $item) {
  348. if (null !== $item['error']) {
  349. $hasErrors = true;
  350. break;
  351. }
  352. }
  353. $request->getSession()->remove(self::BATCH_INCREASE_DATA_KEY);
  354. if (!$hasErrors) {
  355. $data = [
  356. 'items' => $items,
  357. 'file' => $fileName,
  358. ];
  359. $request->getSession()->set(self::BATCH_INCREASE_DATA_KEY, $data);
  360. }
  361. return $this->render(
  362. 'ComponentInventory/batchIncreaseQuantityPreview.html.twig',
  363. [
  364. 'items' => $items,
  365. 'hasErrors' => $hasErrors,
  366. ]
  367. );
  368. } catch (Exception $e) {
  369. return $this->render(
  370. 'ComponentInventory/excelError.html.twig',
  371. [
  372. 'error' => $e->getMessage(),
  373. ],
  374. new Response('', 400)
  375. );
  376. }
  377. }
  378. #[Route(
  379. '/batch-set-quantity',
  380. name: 'app.component_inventory.batch_set_quantity.modal'
  381. )]
  382. public function modalBatchSetQuantityAction(Request $request): ModalResponse
  383. {
  384. $response = new ModalResponse();
  385. $response->setSubmit(true);
  386. $response->setTitle(
  387. $this->trans('ui.batchSetQuantity', [], 'ComponentInventoryItem')
  388. );
  389. $form = $this->createForm(
  390. BatchSetQuantityType::class,
  391. null,
  392. [
  393. 'action' => $this->generateUrl(
  394. 'app.component_inventory.batch_set_quantity.modal'
  395. )
  396. ]
  397. );
  398. $form->handleRequest($request);
  399. if ($form->isSubmitted()) {
  400. if ($form->isValid()) {
  401. try {
  402. /* @var BatchSetQuantity $data */
  403. $data = $form->getData();
  404. $import = $request->getSession()->get(self::BATCH_SET_DATA_KEY);
  405. $codes = [];
  406. foreach ($import['items'] as $item) {
  407. $codes[] = $item['code'];
  408. if (null !== $obj = $this->componentInventoryItemRepository->findByCode($item['code'])) {
  409. if (null !== $item['description']) {
  410. $obj->setDescription($item['description']);
  411. }
  412. $data->updateItem($obj, $item['quantity'], $import['file']);
  413. $this->componentInventoryItemRepository->save($obj);
  414. }
  415. }
  416. // Set quantity zero to all codes that are not listed in the batch
  417. foreach ($this->componentInventoryItemRepository->findByMissingCodes($codes) as $obj) {
  418. $data->updateItem($obj, 0, $import['file']);
  419. $this->componentInventoryItemRepository->save($obj);
  420. }
  421. $response->setRedirect(
  422. $this->generateUrl('app.component_inventory.index')
  423. );
  424. } catch (Exception $e) {
  425. $response->setError($e->getMessage());
  426. }
  427. }
  428. }
  429. $response->setBody(
  430. $this->renderView(
  431. 'ComponentInventory/batchSetQuantityForm.html.twig',
  432. [
  433. 'form' => $form->createView(),
  434. ]
  435. )
  436. );
  437. return $response;
  438. }
  439. #[Route(
  440. '/batch-set-quantity/preview',
  441. name: 'app.component_inventory.batch_set_quantity.preview'
  442. )]
  443. public function modalBatchSetQuantityPreviewAction(Request $request): Response
  444. {
  445. /* @var UploadedFile $file */
  446. $file = $request->files->get('excel');
  447. try {
  448. $fileName = $file->getClientOriginalName();
  449. $items = $this->componentInventoryService->parseBatchSetExcel($file);
  450. $hasErrors = false;
  451. foreach ($items as $item) {
  452. if (null !== $item['error']) {
  453. $hasErrors = true;
  454. break;
  455. }
  456. }
  457. $request->getSession()->remove(self::BATCH_SET_DATA_KEY);
  458. if (!$hasErrors) {
  459. $data = [
  460. 'items' => $items,
  461. 'file' => $fileName,
  462. ];
  463. $request->getSession()->set(self::BATCH_SET_DATA_KEY, $data);
  464. }
  465. return $this->render(
  466. 'ComponentInventory/batchSetQuantityPreview.html.twig',
  467. [
  468. 'items' => $items,
  469. 'hasErrors' => $hasErrors,
  470. ]
  471. );
  472. } catch (Exception $e) {
  473. return $this->render(
  474. 'ComponentInventory/excelError.html.twig',
  475. [
  476. 'error' => $e->getMessage(),
  477. ],
  478. new Response('', 400)
  479. );
  480. }
  481. }
  482. #[Route(
  483. '/{item}/decrease-quantity',
  484. name: 'app.component_inventory.decrease_quantity.modal',
  485. requirements: ['item' => '\d+']
  486. )]
  487. public function modalDecreaseQuantityAction(Request $request, ComponentInventoryItem $item): ModalResponse
  488. {
  489. $response = new ModalResponse();
  490. $response->setSubmit(true);
  491. $response->setTitle(
  492. $this->trans('ui.decreaseQuantity', [], 'ComponentInventoryItem')
  493. );
  494. $response->setLabel($item->getCode());
  495. $form = $this->createForm(
  496. DecreaseQuantityType::class,
  497. null,
  498. [
  499. 'action' => $this->generateUrl(
  500. 'app.component_inventory.decrease_quantity.modal',
  501. [
  502. 'item' => $item->getId(),
  503. ]
  504. )
  505. ]
  506. );
  507. $form->handleRequest($request);
  508. if ($form->isSubmitted()) {
  509. if ($form->isValid()) {
  510. try {
  511. /* @var DecreaseQuantity $data */
  512. $data = $form->getData();
  513. $data->updateItem($item);
  514. $this->componentInventoryItemRepository->save($item);
  515. $response->setRedirect(
  516. $this->generateUrl('app.component_inventory.index')
  517. );
  518. } catch (Exception $e) {
  519. $response->setError($e->getMessage());
  520. }
  521. }
  522. }
  523. $response->setBody(
  524. $this->renderView(
  525. 'ComponentInventory/decreaseQuantityForm.html.twig',
  526. [
  527. 'form' => $form->createView(),
  528. ]
  529. )
  530. );
  531. return $response;
  532. }
  533. #[Route(
  534. '/{item}/increase-quantity',
  535. name: 'app.component_inventory.increase_quantity.modal',
  536. requirements: ['item' => '\d+']
  537. )]
  538. public function modalIncreaseQuantityAction(Request $request, ComponentInventoryItem $item): ModalResponse
  539. {
  540. $response = new ModalResponse();
  541. $response->setSubmit(true);
  542. $response->setTitle(
  543. $this->trans('ui.increaseQuantity', [], 'ComponentInventoryItem')
  544. );
  545. $response->setLabel($item->getCode());
  546. $form = $this->createForm(
  547. IncreaseQuantityType::class,
  548. null,
  549. [
  550. 'action' => $this->generateUrl(
  551. 'app.component_inventory.increase_quantity.modal',
  552. [
  553. 'item' => $item->getId(),
  554. ]
  555. )
  556. ]
  557. );
  558. $form->handleRequest($request);
  559. if ($form->isSubmitted()) {
  560. if ($form->isValid()) {
  561. try {
  562. /* @var IncreaseQuantity $data */
  563. $data = $form->getData();
  564. $data->updateItem($item);
  565. $this->componentInventoryItemRepository->save($item);
  566. $response->setRedirect(
  567. $this->generateUrl('app.component_inventory.index')
  568. );
  569. } catch (Exception $e) {
  570. $response->setError($e->getMessage());
  571. }
  572. }
  573. }
  574. $response->setBody(
  575. $this->renderView(
  576. 'ComponentInventory/increaseQuantityForm.html.twig',
  577. [
  578. 'form' => $form->createView(),
  579. ]
  580. )
  581. );
  582. return $response;
  583. }
  584. #[Route(
  585. '/create',
  586. name: 'app.component_inventory.create.modal'
  587. )]
  588. public function modalCreateAction(Request $request): ModalResponse
  589. {
  590. $response = new ModalResponse();
  591. $response->setSubmit(true);
  592. $response->setTitle(
  593. $this->trans('ui.create', [], 'ComponentInventoryItem')
  594. );
  595. $form = $this->createForm(
  596. ItemCreateType::class,
  597. null,
  598. [
  599. 'action' => $this->generateUrl(
  600. 'app.component_inventory.create.modal'
  601. )
  602. ]
  603. );
  604. $form->handleRequest($request);
  605. if ($form->isSubmitted()) {
  606. if ($form->isValid()) {
  607. try {
  608. /* @var ItemCreate $data */
  609. $data = $form->getData();
  610. $this->componentInventoryItemRepository->save($data->toItem());
  611. $response->setRedirect(
  612. $this->generateUrl('app.component_inventory.index')
  613. );
  614. } catch (Exception $e) {
  615. $response->setError($e->getMessage());
  616. }
  617. }
  618. }
  619. $response->setBody(
  620. $this->renderView(
  621. 'ComponentInventory/form.html.twig',
  622. [
  623. 'form' => $form->createView(),
  624. ]
  625. )
  626. );
  627. return $response;
  628. }
  629. #[Route(
  630. '/{item}/update',
  631. name: 'app.component_inventory.update.modal',
  632. requirements: ['item' => '\d+']
  633. )]
  634. public function modalUpdateAction(Request $request, ComponentInventoryItem $item): ModalResponse
  635. {
  636. $response = new ModalResponse();
  637. $response->setSubmit(true);
  638. $response->setTitle(
  639. $this->trans('ui.update', [], 'ComponentInventoryItem')
  640. );
  641. $response->setLabel($item->getCode());
  642. $form = $this->createForm(
  643. ItemUpdateType::class,
  644. ItemUpdate::fromItem($item),
  645. [
  646. 'action' => $this->generateUrl(
  647. 'app.component_inventory.update.modal',
  648. [
  649. 'item' => $item->getId(),
  650. ]
  651. )
  652. ]
  653. );
  654. $form->handleRequest($request);
  655. if ($form->isSubmitted()) {
  656. if ($form->isValid()) {
  657. try {
  658. /* @var ItemUpdate $data */
  659. $data = $form->getData();
  660. $data->updateItem($item);
  661. $this->componentInventoryItemRepository->save($item);
  662. $response->setRedirect(
  663. $this->generateUrl('app.component_inventory.index')
  664. );
  665. } catch (Exception $e) {
  666. $response->setError($e->getMessage());
  667. }
  668. }
  669. }
  670. $response->setBody(
  671. $this->renderView(
  672. 'ComponentInventory/updateForm.html.twig',
  673. [
  674. 'form' => $form->createView(),
  675. ]
  676. )
  677. );
  678. return $response;
  679. }
  680. }