src/FHI360/Access/Portal/Controller/ReportFormController.php line 113

Open in your IDE?
  1. <?php
  2. namespace App\FHI360\Access\Portal\Controller;
  3. use App\FHI360\Access\Portal\Service\Form\FormManager;
  4. use App\FHI360\Access\Portal\Service\Form\Report\ReportForm;
  5. use App\FHI360\Access\Portal\Service\HookService;
  6. use App\FHI360\Access\Portal\Service\Module\Form\ActiveEditor;
  7. use App\FHI360\Access\Portal\Service\TableBackupService;
  8. use App\FHI360\Access\Portal\Service\UploadFileHandler;
  9. use Curl\Curl;
  10. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
  11. use App\FHI360\Access\Portal\Service\Loader;
  12. use App\FHI360\Access\Portal\Service\Utility;
  13. use App\FHI360\Access\Portal\Service\DateTimeHelper;
  14. use App\FHI360\Access\Portal\Service\ReportDraftManager;
  15. use Symfony\Component\HttpFoundation\Response;
  16. class ReportFormController extends FormController
  17. {
  18.     public static $useJsonPostData false;
  19.     public $recordType 'report';
  20.     public $name 'report'// unused - use recordType instead
  21.     public $repositoryName 'reportDrafts'// deprecated - only use recordType
  22.     public $formClass 'Report\ReportForm';
  23.     public $accessRestrictions = [
  24.         'all' => ['reportNotInMaintenance'],
  25.         'edit' => ['userCanAccessReport'],
  26.     ];
  27.     public $formClasses = [
  28.         'english_program' => 'Report\ScholarshipReportForm',
  29.         'full' => 'Report\FullReportForm',
  30.         'students' => 'Report\StudentsReportForm',
  31.         'instructors' => 'Report\InstructorsReportForm',
  32.         'teacher_training' => 'Report\TrainingReportForm',
  33.     ];
  34.     public $recordsPerPage 10;
  35.     public function addHooks()
  36.     {
  37.         parent::addHooks();
  38.         HookService::add('form.controller.initialize', fn() => ActiveEditor::initialize());
  39.         HookService::add('form.controller.displayForm', function ($properties) {
  40.             $controller $properties['controller'];
  41.             if (!$controller->onlineReportsEnabled()) return ['abort' => $controller->abort()];
  42.         });
  43.     }
  44.     public function createReport($id)
  45.     {
  46.         // get record data
  47.         $reportData Loader::reports()->getRecord([
  48.             'fields' => ['agreement_id''period_number_c AS current_period'],
  49.             'wheres' => ['p0520_program_report.id = :id'],
  50.             'parameters' => ['id' => $id],
  51.         ]);
  52.         $draft = ['report_id' => $id];
  53.         $currentPeriod $reportData['current_period'];
  54.         // populate draft
  55.         $draft ReportDraftManager::populateDraft($draft$currentPeriod);
  56.         // insert draft and fetch the record (with defaults)
  57.         Loader::reportDrafts()->insert($draft);
  58.         // update report status
  59.         Loader::reports()->update($id, [
  60.             'report_status' => $draft['content']['current_status'],
  61.             'report_created_date' => date('Y-m-d'),
  62.         ]);
  63.     }
  64.     /**
  65.      * Show report data
  66.      * @Route("/modules/report/show/{id}", name="module-report-show")
  67.      * @param $id
  68.      * @return
  69.      */
  70.     public function show($id)
  71.     {
  72.         if($this->getParameter('app_env') !== 'dev'Utility::abort('This page is not available');
  73.         $draft Loader::reportDrafts()->getLatestByRecordId($id);
  74.         Utility::abort($draft);
  75.     }
  76.     /**
  77.      * Display form to view a previous revision of a record
  78.      * @Route("/modules/report/edit/{id}/{revision}", requirements={"revision"="\d+"}, name="module-report-previous-revision")
  79.      */
  80.     public function viewPreviousRevision($id$revision) {
  81.         return $this->displayForm($id, [
  82.             'pageMode' => 'previousRevision',
  83.             'revision' => $revision,
  84.         ]);
  85.     }
  86.     /**
  87.      * Display form to view record
  88.      * @Route("/modules/report/edit/{id}/readonly", name="module-report-view")
  89.      */
  90.     public function view($id) {
  91.         return $this->displayForm($id, [
  92.             'pageMode' => 'readOnly',
  93.         ]);
  94.     }
  95.     /**
  96.      * Display form to edit record
  97.      * @Route("/modules/report/edit/{id}", name="module-report-edit")
  98.      */
  99.     public function edit($id) {
  100.         return $this->displayForm($id, ['pageMode' => 'edit']);
  101.     }
  102.     /**
  103.      * Create a new report and display the form
  104.      * @Route("/modules/report/create/{id}", name="module-report-create")
  105.      */
  106.     public function create($id)
  107.     {
  108.         if (! $this->onlineReportsEnabled()) {
  109.             return $this->abort();
  110.         }
  111.         if ($this->getRestriction('edit', ['id' => $id])) {
  112.             return $this->abort();
  113.         }
  114.         $error $this->errorForReportCreation($id);
  115.         if ($error !== '') {
  116.             return $this->abort();
  117.         }
  118.         $this->createReport($id);
  119.         return $this->redirect('/modules/report/edit/' $id);
  120.     }
  121.     /**
  122.      * Resets the budget fields to the agreement values, then redirects to edit view
  123.      * @Route("/modules/report/reset-budget/{id}", name="module-report-reset-budget")
  124.      */
  125.     public function resetBudget($id): Response
  126.     {
  127.         if (Loader::users()->getRole($this->getUserId()) !== 'fhi') {
  128.             // Only FHI users can reset
  129.             return $this->abort();
  130.         }
  131.         ReportDraftManager::resetDraftBudget($id);
  132.         return $this->redirect('/modules/report/edit/' $id);
  133.     }
  134.     /**
  135.      * Resets the payments table to the latest values, then redirects to edit view
  136.      * @Route("/modules/report/reset-payments/{id}", name="module-report-reset-payments")
  137.      */
  138.     public function resetPayments($id): Response
  139.     {
  140.         if (Loader::users()->getRole($this->getUserId()) !== 'fhi') {
  141.             // Only FHI users can reset
  142.             return $this->abort();
  143.         }
  144.         ReportDraftManager::resetDraftDisbursements($id);
  145.         return $this->redirect('/modules/report/edit/' $id);
  146.     }
  147.     // build and display the form
  148.     // FIX: once this method is no longer deppendent on controller methods, it can move elsewhere as a static method (eg. FormManager)
  149.     protected function displayForm($id$options = [])
  150.     {
  151.         $properties HookService::trigger('form.controller.displayForm', ['controller' => $this'id' => $id]);
  152.         if ($properties['abort'] ?? null) return $properties['abort'];
  153.         if ($this->getRestriction('edit', ['id' => $id])) {
  154.             return $this->abort();
  155.         }
  156.         //  get record fields
  157.         $repository Loader::getRepositoryForRecordType($this->recordType);
  158.         $record $repository->getRecordFields($id);
  159.         // get the draft
  160.         $draftRepository Loader::getRepositoryForRecordType($this->recordType'draft');
  161.         if (!empty($options['revision'])) {
  162.             $draft $draftRepository->getByRecordIdAndMajorRevision($id$options['revision']);
  163.         } else {
  164.             $draft $draftRepository->getLatestByRecordId($id);
  165.             if (!$draft) {
  166.                 $this->createReport($id);
  167.                 $draft $draftRepository->getLatestByRecordId($id);
  168.                 if (!$draft) return $this->abort('A draft could not be created for this report.');
  169.             }
  170.         }
  171.         // upate draft fields and subforms
  172.         $draft ReportDraftManager::updateDraft($draft);
  173.         // build the form and set the data
  174.         $defaults = ['pageMode' => 'edit''formType' => 'english_program''navigationButtons' => true];
  175.         $options array_replace($defaults$options);
  176.         // get report information
  177.         $userRole Loader::users()->getRole($this->getUserId());
  178.         $options['recordInformation'] = $record;
  179.         $form $this->buildForm($draft$options);
  180.         $form->setDataFromDraft($draft);
  181.         // return the form
  182.         $statusRepository Loader::getRepositoryForRecordType($this->recordType'status');
  183.         $parameters = [
  184.             'form' => $form->getForm(),
  185.             'audits' => [],
  186.             'status' => $draft['current_status'],
  187.             'statusName' => $statusRepository->statuses[$draft['current_status']]['name'],
  188.             'stepper' => $this->getStepperData($form),
  189.             'user_role' => $userRole,
  190.             'title' => $draft['content']['name'],
  191.             'id' => $id,
  192.             'lastUpdated' => $draft['date_modified'],
  193.             'sideBarButtons' => $form->getSideBarButtonsToDisplay($id),
  194.             'sideBarPreferences' => Loader::userPreferences()->getSideBarPreferences(),
  195.             'majorVersion' => $form->getMajorVersion(),
  196.         ];
  197.         return $this->respond('edit'$parameters);
  198.     }
  199.     public function abortIfInputLimitExceeded(): ?Response
  200.     {
  201.         if (Utility::arrayExceedsMaxInput(self::getPostData())) {
  202.             return $this->error(['method' => 'input_limit_exceeded''error' => 'input limit exceeded']);
  203.         }
  204.         return null;
  205.     }
  206.     // check if record is in maintenance mode
  207.     public function abortIfInMaintenance($id)
  208.     {
  209.         if(Loader::reports()->isReportUnderMaintenance($id)) {
  210.             return $this->error(['method' => 'maintenance''error' => "This Report is under maintenance mode so it cannot be modified, please refresh the page in a few minutes and try again."]);
  211.         }
  212.     }
  213.     /**
  214.      * @Route("/modules/report/update/{id}", name="module-report-update")
  215.      */
  216.     public function handleEdit($id)
  217.     {
  218.         if (! $this->onlineReportsEnabled()) {
  219.             return $this->abort();
  220.         }
  221.         if ($this->getRestriction('edit', ['id' => $id])) {
  222.             return $this->abort();
  223.         }
  224.         
  225.         $abortResponse $this->abortIfInMaintenance($id);
  226.         if($abortResponse) return $abortResponse;
  227.         
  228.         $abortResponse $this->abortIfInputLimitExceeded();
  229.         if($abortResponse) return $abortResponse;
  230.         
  231.         // get existing draft
  232.         $draft Loader::reportDrafts()->getLatestByRecordId($id);
  233.         $draftId $draft['id'];
  234.         
  235.         Loader::backup()->run($idTableBackupService::REPORT_DRAFT);
  236.         
  237.         $data FormManager::updateFormDataFromPost($draft['content'], 'report');
  238.         $draft['content'] = $data;
  239.         // ensure that record status hasn't changed since form was loaded - otherwise trigger refresh
  240.         if ($draft['current_status'] !== ($_REQUEST['record_status'] ?? $_REQUEST['client_side_status'])) {
  241.             return $this->error(['method' => 'staleDataWarning''error' => "Form contains outdated data."]);
  242.         }
  243.         // ensure that form version number hasn't changed since form was loaded - otherwise trigger refresh
  244.         $formVersionNumber FormManager::$versionNumber;
  245.         if ($formVersionNumber !== intval($_REQUEST['version_number'])) {
  246.             return $this->error(['method' => 'staleDataWarning''error' => "The system has new updates, a refresh is required to continue."]);
  247.         }
  248.         return FormManager::saveData($id$draftId$draft'report');
  249.     }
  250.     public function buildForm(array $record$options = []): ReportForm
  251.     {
  252.         // get the report type
  253.         $programType = ($options['recordInformation']['program_type'] ?? $options['recordInformation']['program_type'] ?? null) ?: 'english_program';
  254.         $options['formType'] = $programType;
  255.         return parent::buildForm($record$options);
  256.     }
  257.     /** @Route("/modules/report/reset/{id}", defaults={"id"=""}, name="module-report-reset") */
  258.     public function reset($id)
  259.     {
  260.         if (! $this->onlineReportsEnabled()) {
  261.             return $this->abort();
  262.         }
  263.         if ($this->getRestriction('edit', ['id' => $id])) {
  264.             return $this->abort();
  265.         }
  266.         $drafts Loader::reportDrafts()->getRecords(
  267.             ['fields' => ['id'], 'wheres' => ['report_id = :report_id'], 'parameters' => ['report_id' => $id]]
  268.         );
  269.         foreach ($drafts as $draft) {
  270.             Loader::reportDrafts()->deleteById($draft['id'], ['softDelete' => false]);
  271.         }
  272.         return $this->success();
  273.     }
  274.     /** @Route("/modules/report/update-success/{id}", defaults={"id"=""}, name="module-report-update-success") */
  275.     public function updateSuccess($id)
  276.     {
  277.         $userId $this->getUserId();
  278.         $url Utility::getUrl("/modules/report/edit/$id");
  279.         $user_role Loader::users()->getRole($this->getUserId());
  280.         $draft Loader::reportDrafts()->getLatestByRecordId($id);
  281.         $current_status $draft['current_status'];
  282.         $params = [
  283.             'id' => compact('id'),
  284.             'user_role' => $user_role,
  285.             'report_url' => $url,
  286.         ];
  287.         return $this->respond('success'$params);
  288.     }
  289.     protected function getStepperData(ReportForm $form): array
  290.     {
  291.         $steps $this->getStepperSteps();
  292.         $status $this->getLabelForStatus($form->getProperty('status'));
  293.         $userId $this->getUserId();
  294.         $user_role Loader::users()->getRole($userId);
  295.         $audit_data $this->getAuditTrail($form->getProperty('id'));
  296.         $new_audit_data = [];
  297.         foreach ($audit_data as $data) {
  298.             if ($user_role === 'provider' && !in_array('provider'$data['roles_involved'])) {
  299.                 continue;
  300.             }
  301.             if ($data['roles_involved'] === ['eca''fhi']) {
  302.                 if ($user_role !== 'fhi' && $user_role !== 'eca') {
  303.                     unset($data['comment']);
  304.                 }
  305.             }
  306.             array_push($new_audit_data$data);
  307.         }
  308.         return [
  309.             'audits' => $new_audit_data,//$this->getAuditTrail($form->getProperty('id')),
  310.             'current' => $status,
  311.             'steps' => base64_encode(json_encode(array_values($steps))),
  312.             'user_role' => $user_role,
  313.         ];
  314.     }
  315.     // reportfix: consolidate code that report and proposal have in common
  316.     protected function getStepperSteps(): array
  317.     {
  318.         $statuses = [];
  319.         foreach (Loader::reportStatuses()->statuses as $name => $status) {
  320.             $label $this->getLabelForStatus($name);
  321.             if ($label) {
  322.                 $statuses[$label] = $label;
  323.             }
  324.         }
  325.         return $statuses;
  326.     }
  327.     protected function getAuditTrail($id): array
  328.     {
  329.         $formattedAudits = [];
  330.         $isProvider $this->isProvider();
  331.         $audits Loader::reportDrafts()->getAuditData($id);
  332.         $statuses Loader::reportStatuses()->statuses;
  333.         $actions = [
  334.             'create' => 'Created',
  335.             'submit' => 'Submitted',
  336.             'send_back' => 'Sent back',
  337.             'approve' => 'Approved',
  338.         ];
  339.         $providerStatuses = [
  340.             'provider_completes_report',
  341.             'provider_revises_report',
  342.         ];
  343.         //Now we have group the audit records by sequence, lets go thorough them and build the audit trail for each
  344.         foreach($audits as $audit){
  345.             $date $audit['date_entered'];
  346.             $date_to_et DateTimeHelper::dbToEt($date);
  347.             $audit['date_entered'] = $date_to_et;
  348.             $oldStatus $statuses[$audit['status_before_value']] ?? [];
  349.             $newStatus $statuses[$audit['status_after_value']] ?? [];
  350.             $labelKey $isProvider 'nameForProvider' 'name';
  351.             if ($isProvider){
  352.                 if (empty($newStatus[$labelKey]) && empty($oldStatus[$labelKey])) {
  353.                     continue;
  354.                 }
  355.             }else{
  356.                 if (empty($newStatus[$labelKey]) || empty($oldStatus[$labelKey])) {
  357.                     continue;
  358.                 }
  359.             }
  360.             if ($isProvider
  361.                 && !in_array($audit['status_after_value'], $providerStatusestrue)
  362.                 && !in_array($audit['status_before_value'], $providerStatusestrue)) {
  363.                 continue;
  364.             }
  365.             $formattedAudit['from_role'] = $oldStatus['awaitingAction'] ?? 'none';
  366.             $formattedAudit['to_role'] = $newStatus['awaitingAction'] ?? 'none';
  367.             $formattedAudit['roles_involved'] = [$formattedAudit['from_role'], $formattedAudit['to_role']];
  368.             sort($formattedAudit['roles_involved']);
  369.             $formattedAudit['date_entered'] = date("M j, Y H:i:s A"strtotime($audit['date_entered']));
  370.             $formattedAudit['modified_by'] = $audit['user_last_modified'];
  371.             $formattedAudit['old_status'] = $oldStatus[$labelKey];
  372.             $formattedAudit['new_status'] = $newStatus[$labelKey];
  373.             $formattedAudit['action'] = $actions[$audit['decision']] ?? 'Updated';
  374.             $formattedAudit['comment'] = $audit['decision_comment'];
  375.             $formattedAudits[] = $formattedAudit;
  376.         }
  377.         return $formattedAudits;
  378.     }
  379.     // reportfix: consolidate code that report and proposal have in common
  380.     protected function getLabelForStatus(string $status): string
  381.     {
  382.         $isProvider $this->isProvider();
  383.         $statuses Loader::reportStatuses()->statuses;
  384.         if (isset($statuses[$status]['nameForProvider']) && $isProvider) {
  385.             return $statuses[$status]['nameForProvider'];
  386.         }
  387.         return $statuses[$status]['name'] ?? 'Undefined Status';
  388.     }
  389.     // reportfix: consolidate code that report and proposal have in common
  390.     protected function isProvider(): bool
  391.     {
  392.         return Loader::users()->getRole($this->user->getId()) === 'provider';
  393.     }
  394.     /**
  395.      * @Route("/modules/report/save-document/{id}/{field}", name="module-report-save-document")
  396.      * @param $id
  397.      * @return Response
  398.      */
  399.     public function saveDocument($id$field)
  400.     {
  401.         $file UploadFileHandler::forReports()->getFile();
  402.         $filename $file['name'];
  403.         $document_id Loader::documents()->createFromFile($file);
  404.         return $this->success(compact('document_id''filename'));
  405.     }
  406.     /**
  407.      * @Route("/modules/report/check-provider-setup/{report_id}", name="module-report-check-provider-setup")
  408.      * @param $report_id
  409.      * @return Response
  410.      */
  411.     public function checkProviderSetup($report_id)
  412.     {
  413.         return new Response(json_encode(['error' => $this->errorForReportCreation($report_id)]));
  414.     }
  415.     public function errorForReportCreation(string $id): string
  416.     {
  417.         $suiteUrl $this->container->getParameter('suitecrml_url');
  418.         $url $suiteUrl "/index.php?entryPoint=OnlineReportCheck&report_id=" $id;
  419.         $curl = new Curl($url);
  420.         $curl->setOpt(CURLOPT_SSL_VERIFYPEERfalse);
  421.         $curl->setOpt(CURLOPT_RETURNTRANSFERtrue);
  422.         $curl->setOpt(CURLOPT_FOLLOWLOCATIONfalse);
  423.         $curl->get($url);
  424.         $data json_decode($curl->responsetrue);
  425.         return $data['error'];
  426.     }
  427.     protected function onlineReportsEnabled(): bool
  428.     {
  429.         return $this->container->getParameter('online_reporting') ?? false;
  430.     }
  431. }