From c1bd61dfc78b23f12431e82fd651459883ddb86d Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Mon, 2 Dec 2024 23:06:44 +0800 Subject: [PATCH 01/54] Admin Workflow Initial Commit --- .../General/Eloquent/Filters/IsPrevious.php | 21 + .../General/Eloquent/Filters/QuestionId.php | 20 + .../Eloquent/Filters/QuestionNumber.php | 20 + .../Eloquent/Filters/QuestionnaireSetId.php | 20 + .../Processors/UploadDocumentProcessor.php | 50 ++ .../FetchQuestionV1AdminWFLogic.php | 62 ++ .../UpdateNextQuestionV1AdminWFLogic.php | 164 +++++ .../QAUserSourceObject.php | 56 ++ .../FetchFirstQuestionV1AdminWFProcessor.php | 39 + .../FetchNextQuestionV1AdminWFProcessor.php | 87 +++ .../SaveAnswerV1AdminWFProcessor.php | 85 +++ .../Services/CreatesQAUserSource.php | 26 + .../Services/FetchesQAUserSource.php | 33 + .../Services/FetchesQuestion.php | 33 + .../Services/FetchesUserAnswerSelected.php | 33 + .../Services/ListsQuestions.php | 33 + .../Services/ListsUserAnswerSelected.php | 33 + .../Standards/Rules/CanFetchQuestion.php | 38 + .../Rules/CanListHelpMenuQuestions.php | 45 ++ .../ValueObjects/Constants/DocumentType.php | 2 + app/Classes/ValueObjects/Constants/QAType.php | 25 + .../FetchQuestionV1AdminWFController.php | 21 + .../UpdateNextQuestionV1AdminWFController.php | 31 + app/Http/Resources/AnswerOptionsResource.php | 27 + app/Http/Resources/QuestionResource.php | 71 ++ .../QuestionnaireAnswersResource.php | 25 + .../Resources/QuestionnaireSetsResource.php | 25 + .../Resources/QuestionsAnswersResource.php | 44 ++ app/Http/Resources/UserSourceResource.php | 24 + app/Models/Booking.php | 12 +- app/Models/QAAnswerOptions.php | 9 + app/Models/QAQuestionnaireSet.php | 9 + app/Models/QAQuestions.php | 19 + app/Models/QAUserAnswerSelected.php | 71 ++ app/Models/QAUserSource.php | 9 + ...olumn_to_text_in_key_value_pairs_table.php | 32 + ..._025531_create_qa_answer_options_table.php | 41 ++ ...531_create_qa_questionnaire_sets_table.php | 38 + ...11_26_025531_create_qa_questions_table.php | 47 ++ ...1_create_qa_user_answer_selected_table.php | 46 ++ ..._26_025531_create_qa_user_source_table.php | 34 + database/seeds/DatabaseSeeder.php | 3 + database/seeds/QAWorkFlow2Seeder.php | 437 ++++++++++++ .../forms/PurchaseOrderFormComponent.vue | 8 +- .../PurchaseOrderFormReadOnlyComponent.vue | 86 +++ .../forms/PurchaseOrderItemFormComponent.vue | 4 +- .../forms/AdminWorkFlowFormComponent.vue | 640 +++++++++++++++++ .../AdminWorkFlowSectionComponent.vue | 670 ++++++++++++++++++ .../assets/vue/general/mixins/request.js | 2 +- .../dashboards/admin_work_flow.blade.php | 5 + routes/admin_work_flow.php | 228 ++++++ routes/api.php | 4 + routes/questionnaires.php | 9 + routes/web.php | 22 +- 54 files changed, 3662 insertions(+), 16 deletions(-) create mode 100644 app/Classes/General/Eloquent/Filters/IsPrevious.php create mode 100644 app/Classes/General/Eloquent/Filters/QuestionId.php create mode 100644 app/Classes/General/Eloquent/Filters/QuestionNumber.php create mode 100644 app/Classes/General/Eloquent/Filters/QuestionnaireSetId.php create mode 100644 app/Classes/Modules/Documents/Processors/UploadDocumentProcessor.php create mode 100644 app/Classes/Modules/Questionnaires/ControllersLogic/FetchQuestionV1AdminWFLogic.php create mode 100644 app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php create mode 100644 app/Classes/Modules/Questionnaires/DataTransferObjects/QAUserSourceObject.php create mode 100644 app/Classes/Modules/Questionnaires/Processors/FetchFirstQuestionV1AdminWFProcessor.php create mode 100644 app/Classes/Modules/Questionnaires/Processors/FetchNextQuestionV1AdminWFProcessor.php create mode 100644 app/Classes/Modules/Questionnaires/Processors/SaveAnswerV1AdminWFProcessor.php create mode 100644 app/Classes/Modules/Questionnaires/Services/CreatesQAUserSource.php create mode 100644 app/Classes/Modules/Questionnaires/Services/FetchesQAUserSource.php create mode 100644 app/Classes/Modules/Questionnaires/Services/FetchesQuestion.php create mode 100644 app/Classes/Modules/Questionnaires/Services/FetchesUserAnswerSelected.php create mode 100644 app/Classes/Modules/Questionnaires/Services/ListsQuestions.php create mode 100644 app/Classes/Modules/Questionnaires/Services/ListsUserAnswerSelected.php create mode 100644 app/Classes/Modules/Questionnaires/Standards/Rules/CanFetchQuestion.php create mode 100644 app/Classes/Modules/Questionnaires/Standards/Rules/CanListHelpMenuQuestions.php create mode 100644 app/Classes/ValueObjects/Constants/QAType.php create mode 100644 app/Http/Controllers/Questionnaires/FetchQuestionV1AdminWFController.php create mode 100644 app/Http/Controllers/Questionnaires/UpdateNextQuestionV1AdminWFController.php create mode 100644 app/Http/Resources/AnswerOptionsResource.php create mode 100644 app/Http/Resources/QuestionResource.php create mode 100644 app/Http/Resources/QuestionnaireAnswersResource.php create mode 100644 app/Http/Resources/QuestionnaireSetsResource.php create mode 100644 app/Http/Resources/QuestionsAnswersResource.php create mode 100644 app/Http/Resources/UserSourceResource.php create mode 100644 app/Models/QAAnswerOptions.php create mode 100644 app/Models/QAQuestionnaireSet.php create mode 100644 app/Models/QAQuestions.php create mode 100644 app/Models/QAUserAnswerSelected.php create mode 100644 app/Models/QAUserSource.php create mode 100644 database/migrations/2024_11_26_025531_change_value_column_to_text_in_key_value_pairs_table.php create mode 100644 database/migrations/2024_11_26_025531_create_qa_answer_options_table.php create mode 100644 database/migrations/2024_11_26_025531_create_qa_questionnaire_sets_table.php create mode 100644 database/migrations/2024_11_26_025531_create_qa_questions_table.php create mode 100644 database/migrations/2024_11_26_025531_create_qa_user_answer_selected_table.php create mode 100644 database/migrations/2024_11_26_025531_create_qa_user_source_table.php create mode 100644 database/seeds/QAWorkFlow2Seeder.php create mode 100644 resources/assets/vue/components/bookings/forms/PurchaseOrderFormReadOnlyComponent.vue create mode 100644 resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue create mode 100644 resources/assets/vue/components/general/sections/AdminWorkFlowSectionComponent.vue create mode 100644 resources/views/pages/dashboards/admin_work_flow.blade.php create mode 100644 routes/admin_work_flow.php create mode 100644 routes/questionnaires.php diff --git a/app/Classes/General/Eloquent/Filters/IsPrevious.php b/app/Classes/General/Eloquent/Filters/IsPrevious.php new file mode 100644 index 00000000..feeb8a0a --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/IsPrevious.php @@ -0,0 +1,21 @@ +where('is_previous', $value); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/QuestionId.php b/app/Classes/General/Eloquent/Filters/QuestionId.php new file mode 100644 index 00000000..9c78f51a --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/QuestionId.php @@ -0,0 +1,20 @@ +where('question_id', $value); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/QuestionNumber.php b/app/Classes/General/Eloquent/Filters/QuestionNumber.php new file mode 100644 index 00000000..02d67343 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/QuestionNumber.php @@ -0,0 +1,20 @@ +where('question_number', $value); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/QuestionnaireSetId.php b/app/Classes/General/Eloquent/Filters/QuestionnaireSetId.php new file mode 100644 index 00000000..aa9efe19 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/QuestionnaireSetId.php @@ -0,0 +1,20 @@ +where('questionnaire_set_id', $value); + } + +} diff --git a/app/Classes/Modules/Documents/Processors/UploadDocumentProcessor.php b/app/Classes/Modules/Documents/Processors/UploadDocumentProcessor.php new file mode 100644 index 00000000..7868f06f --- /dev/null +++ b/app/Classes/Modules/Documents/Processors/UploadDocumentProcessor.php @@ -0,0 +1,50 @@ +createsDocument = $createsDocument; + $this->createsFiles = $createsFiles; + } + + + /** + * @param $filesUpload + * @param QAUserAnswerSelected qaUserAnswerSelected + * @param string $path + * @throws \App\Classes\Exceptions\MalformedRequestException + */ + public function execute($filesUpload, QAUserAnswerSelected $qaUserAnswerSelected, string $path) { + $object = new DocumentObject(DocumentType::ADMIN_WORK_FLOW, $filesUpload, '', ApprovalStatus::PENDING_VERIFICATION, $path); + + /** @var Document $document */ + $document = $this->createsDocument->execute($qaUserAnswerSelected, $object); + + $result = $this->createsFiles->execute($document, $object); + return $result; + } +} diff --git a/app/Classes/Modules/Questionnaires/ControllersLogic/FetchQuestionV1AdminWFLogic.php b/app/Classes/Modules/Questionnaires/ControllersLogic/FetchQuestionV1AdminWFLogic.php new file mode 100644 index 00000000..0b6bfdd8 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/ControllersLogic/FetchQuestionV1AdminWFLogic.php @@ -0,0 +1,62 @@ + 'Retrieved Admin Workflow First Question', + 'message' => 'You have successfully retrieved a Admin Workflow First Question' + ]; + } + + /** @var CanFetchQuestion */ + private $canFetchQuestion; + + /** @var FetchFirstQuestionV1AdminWFProcessor */ + private $fetchFirstQuestionQAProcessor; + + /** + * FetchQuestionV1AdminWFLogic constructor. + * @param CanFetchQuestion $canFetchQuestion + * @param FetchFirstQuestionV1AdminWFProcessor $fetchFirstQuestionQAProcessor + */ + public function __construct(CanFetchQuestion $canFetchQuestion, FetchFirstQuestionV1AdminWFProcessor $fetchFirstQuestionQAProcessor) + { + $this->canFetchQuestion = $canFetchQuestion; + $this->fetchFirstQuestionQAProcessor = $fetchFirstQuestionQAProcessor; + } + + + /** + * @param Request $request + * @return JsonResponse + * @throws ErrorException + */ + public function logic(Request $request) : JsonResponse + { + $this->canFetchQuestion->passes(); + + $userId = Auth::user()->id; + + $query = $this->fetchFirstQuestionQAProcessor->execute($request); + + return $this->resourceResponse(new QuestionResource($query, $userId)); + } + +} diff --git a/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php b/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php new file mode 100644 index 00000000..fed2d673 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php @@ -0,0 +1,164 @@ + 'Retrieved Admin Workflow Next Question', + 'message' => 'You have successfully retrieved a Admin Workflow Next Question' + ]; + } + + /** @var CanFetchQuestion */ + private $canFetchQuestion; + + /** @var SaveAnswerV1AdminWFProcessor */ + private $saveAnswerProcessor; + + /** @var FetchNextQuestionV1AdminWFProcessor */ + private $fetchNextQuestionQAProcessor; + + /** @var FetchFirstQuestionV1AdminWFProcessor */ + private $fetchFirstQuestionQAProcessor; + + /** @var ListsUserAnswerSelected */ + private $listsUserAnswerSelected; + + /** @var CreatesKeyValuePair */ + private $createsKeyValuePair; + + /** + * UpdateNextQuestionV1AdminWFLogic constructor. + * @param CanFetchQuestion $canFetchQuestion + * @param SaveAnswerV1AdminWFProcessor $saveAnswerProcessor + * @param FetchNextQuestionV1AdminWFProcessor $fetchNextQuestionQAProcessor + * @param FetchFirstQuestionV1AdminWFProcessor $fetchFirstQuestionQAProcessor + * @param ListsUserAnswerSelected $listsUserAnswerSelected + * @param CreatesKeyValuePair $createsKeyValuePair + */ + public function __construct(CanFetchQuestion $canFetchQuestion, SaveAnswerV1AdminWFProcessor $saveAnswerProcessor, FetchNextQuestionV1AdminWFProcessor $fetchNextQuestionQAProcessor, FetchFirstQuestionV1AdminWFProcessor $fetchFirstQuestionQAProcessor, ListsUserAnswerSelected $listsUserAnswerSelected, CreatesKeyValuePair $createsKeyValuePair) + { + $this->canFetchQuestion = $canFetchQuestion; + $this->saveAnswerProcessor = $saveAnswerProcessor; + $this->fetchNextQuestionQAProcessor = $fetchNextQuestionQAProcessor; + $this->fetchFirstQuestionQAProcessor = $fetchFirstQuestionQAProcessor; + $this->listsUserAnswerSelected = $listsUserAnswerSelected; + $this->createsKeyValuePair = $createsKeyValuePair; + } + + + /** + * @param Request $request + * @return JsonResponse + * @throws MalformedRequestException + * @throws ResourceNotFoundException + */ + public function logic(Request $request) : JsonResponse + { + $questionId = 0; + $userId = Auth::user()->id; + $currentQuestion = $request->question; + $questionId = $currentQuestion['id']; + $questionType = $currentQuestion['question_type']; + $currentQuestionNumber = $currentQuestion['question_number']; + $answerNextQuestionNumber = null; + $answerValue = null; + $timeUsedSeconds = 0; + $reference = null; + $questionMetadata = null; + + $this->canFetchQuestion->passes(); + + Log::info('currentQuestion: '. json_encode($currentQuestion)); + + if ($request->has('answerObj')) { + $answer = $request->answerObj; + $answerNextQuestionNumber = $answer['next_question_number']; + $answerValue = $answer['value']; + Log::info('answerNextQuestionNumber: '. $answerNextQuestionNumber); + Log::info('answerValue: '. $answerValue); + } + + if ($request->has('extra')) { + $extra = $request->extra; + $questionMetadata = $extra['questionMetadata'] ?? null; + $timeUsedSeconds = intval($extra['timeUsedSeconds']) ?? 0; + $reference = $extra['session_id']; + } + + //Save answer given by user (both next and previous) + $filesUpload = $request->input('files'); + $answerOptionId = 0; + if ($request->has('answerObj')) { + $answerOptionId = intval($request->answerObj['id']); + } + $answerInText = $request->answer; + if ($request->has('isPrevious')){ + $answerInText = "go_back"; + } + $answer = $this->saveAnswerProcessor->execute($userId, 0, $questionId, $questionType, $questionMetadata, $answerInText, $answerOptionId, $filesUpload, $timeUsedSeconds, $request->has('isPrevious'), $reference); + + //Marked data that has already been processed so that it does not appear again + $isNoGoingBack = null; + if($currentQuestion && $currentQuestion['is_end'] === 1){ + $booking = Booking::where('id',$questionMetadata['booking']['id'])->first(); + $kvp = $answer->attributesKVP()->where('key', 'App\Models\Bank')->where('value', $questionMetadata['booking']['id'])->latest()->first(); + + $key = "admin_workflow_processed"; + if (strpos($currentQuestion['question_number'], '1688') === 0) { + $key = '1688_'.$key; + } + if (strpos($currentQuestion['question_number'], 'fill_po') === 0) { + $key = 'fill_po_'.$key; + } + if (strpos($currentQuestion['question_number'], 'approve_po') === 0) { + $key = 'approve_po_'.$key; + } + + if(!$kvp){ + $keyValuePairObject = new KeyValuePairObject($key, true); + $this->createsKeyValuePair->execute($booking, $keyValuePairObject); + } + // $isNoGoingBack = 1; + } + + $previousAnswer = null; + if ($request->has('extra')) { + $extra = $request->extra; + $reference = $extra['session_id']; + $previousAnswer = $this->listsUserAnswerSelected->execute(['user_id' => $userId, 'reference' => $reference, 'is_previous' => 0, 'order_by' => (object)['column' => 'id','DESC' => true]])[0]; + } + + //Get returned question (previous or next) + $returnQuestion = $this->fetchNextQuestionQAProcessor->execute($request, $previousAnswer); + + //When a questionnaire ended, return back the first question + if(is_null($returnQuestion)){ + $returnQuestion = $this->fetchFirstQuestionQAProcessor->execute($request); + } + + return $this->resourceResponse(new QuestionResource($returnQuestion, $userId, $request->has('isPrevious'), $isNoGoingBack, $previousAnswer)); + } +} diff --git a/app/Classes/Modules/Questionnaires/DataTransferObjects/QAUserSourceObject.php b/app/Classes/Modules/Questionnaires/DataTransferObjects/QAUserSourceObject.php new file mode 100644 index 00000000..510a945c --- /dev/null +++ b/app/Classes/Modules/Questionnaires/DataTransferObjects/QAUserSourceObject.php @@ -0,0 +1,56 @@ +system = $system; + $this->marking = $marking; + $this->email = $email; + } + + /** + * @return string + */ + public function getSystem(): string + { + return $this->system; + } + + /** + * @return string + */ + public function getMarking(): string + { + return $this->marking; + } + + /** + * @return string + */ + public function getEmail(): string + { + return $this->email; + } + +} diff --git a/app/Classes/Modules/Questionnaires/Processors/FetchFirstQuestionV1AdminWFProcessor.php b/app/Classes/Modules/Questionnaires/Processors/FetchFirstQuestionV1AdminWFProcessor.php new file mode 100644 index 00000000..c2448786 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Processors/FetchFirstQuestionV1AdminWFProcessor.php @@ -0,0 +1,39 @@ +fetchesQuestion = $fetchesQuestion; + } + + public function execute(Request $request){ + try { + $setId = $request->route('set_id'); + if($setId === '0'){ + $set = QAQuestionnaireSet::where('group', 'workflow')->latest('id')->first(); + $setId = $set->id; + } + $query = $this->fetchesQuestion->execute(['questionnaire_set_id' => $setId]); + return $query; + } catch (\Exception $exception){ + throw new ErrorException($exception->getMessage(), $exception->getCode()); + } + } +} diff --git a/app/Classes/Modules/Questionnaires/Processors/FetchNextQuestionV1AdminWFProcessor.php b/app/Classes/Modules/Questionnaires/Processors/FetchNextQuestionV1AdminWFProcessor.php new file mode 100644 index 00000000..5a87eb34 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Processors/FetchNextQuestionV1AdminWFProcessor.php @@ -0,0 +1,87 @@ +fetchesQuestion = $fetchesQuestion; + } + + public function execute(Request $request, $previousAnswer){ + $isEnd = 0; + $questionId = 0; + $nextQuestionNumber = 0; + $nextNestedQuestion = 0; + $nextMainQuestion= 0; + $questionnaireSetId = 0; + $currentQuestion = $request->question; + $questionType = QAType::DEFAULT; + $returnQuestion = null; + + if ($currentQuestion && array_key_exists('id', $currentQuestion)) { + $questionId = $currentQuestion['id']; + $isEnd = $currentQuestion['is_end']; + $questionType = $currentQuestion['question_type']; + $nextNestedQuestion = $currentQuestion['next_nested_question']; + $nextMainQuestion = $currentQuestion['next_main_question']; + $questionnaireSetId = $currentQuestion['questionnaire_set_id']; + } + + if ($request->has('answerObj') && $request->answerObj['next_question_number']) { + $nextQuestionNumber = $request->answerObj['next_question_number']; + } + + if ($request->has('isPrevious')) { + if($previousAnswer) + { + $previousAnswer->delete(); + } + else{ + return null; + } + + //Get previous question + $previousQuestion = $this->fetchesQuestion->execute(['questionnaire_set_id' => $questionnaireSetId, 'id' => $previousAnswer['question_id']]); + $returnQuestion = $previousQuestion; + } + else{ + //Get next question + $nextQuestion = null; + $question_number = 0; + if($nextQuestionNumber !== 0){ + $question_number = $nextQuestionNumber; + } + else if($nextNestedQuestion !== 0){ + $question_number = $nextNestedQuestion; + } + else { + $question_number = $nextMainQuestion; + } + + if($question_number !== 0){ + $nextQuestion = $this->fetchesQuestion->execute(['questionnaire_set_id' => $questionnaireSetId, 'question_number' => $question_number]); + $returnQuestion = $nextQuestion; + } + else if (array_key_exists('id', $request->input('question'))) { + $nextQuestion = $this->fetchesQuestion->execute(['questionnaire_set_id' => $questionnaireSetId, 'id_next' => $questionId, 'next_main_question' => null]); + $returnQuestion = $nextQuestion; + } + } + + return $returnQuestion; + } +} diff --git a/app/Classes/Modules/Questionnaires/Processors/SaveAnswerV1AdminWFProcessor.php b/app/Classes/Modules/Questionnaires/Processors/SaveAnswerV1AdminWFProcessor.php new file mode 100644 index 00000000..2b91e60c --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Processors/SaveAnswerV1AdminWFProcessor.php @@ -0,0 +1,85 @@ +fetchesUserAnswerSelected = $fetchesUserAnswerSelected; + $this->uploadDocumentForProcessor = $uploadDocumentForProcessor; + } + + /** + * @return QAUserAnswerSelected|\Illuminate\Database\Eloquent\Model + */ + public function execute($userId, $sourceId, $questionId, $questionType, $questionMetadata, $answerInText, $answerOptionId, $filesUpload, $timeUsedSeconds, $isPrevious, $reference = null){ + + $answer = null; + $attachments = null; + + if(is_null($answer)) + { + $answer = new QAUserAnswerSelected; + } + $answer->user_id = $userId; + $answer->source_id = $sourceId; + $answer->question_id = $questionId; + $answer->question_metadata = json_encode($questionMetadata); + $answer->answer_option_id = $answerOptionId; + $answer->time_used_seconds = $timeUsedSeconds; + $answer->is_previous = $isPrevious; + if($reference){ + $answer->reference = $reference; + } + $answer->save(); //cief todo: why 2 save() in this file, this is wrong + + if($filesUpload){ + $result = $this->uploadDocumentForProcessor->execute($filesUpload, $answer, 'questionnaires'); + $attachments = array_map(function ($item) { + return [ + 'document_id' => $item->document_id, + 'file_id' => $item->id, + ]; + }, $result); + } + + if($questionType === QAType::REMARKS_WITH_DOCUMENT_UPLOAD){ + $structuredAnswer = [ + 'text' => $answerInText, + 'files' => $attachments, + ]; + $answer->answer = json_encode($structuredAnswer); + } + else if($questionType === QAType::DOCUMENT_UPLOAD){ + $structuredAnswer = [ + 'files' => $attachments, + ]; + $answer->answer = json_encode($structuredAnswer); + } + else { + $answer->answer = $answerInText; + } + $answer->save(); + + return $answer; + } +} diff --git a/app/Classes/Modules/Questionnaires/Services/CreatesQAUserSource.php b/app/Classes/Modules/Questionnaires/Services/CreatesQAUserSource.php new file mode 100644 index 00000000..2268cf7f --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Services/CreatesQAUserSource.php @@ -0,0 +1,26 @@ +system = $object->getSystem(); + $model->marking = $object->getMarking(); + $model->email = $object->getEmail(); + + return $this->handler($model); + + } +} diff --git a/app/Classes/Modules/Questionnaires/Services/FetchesQAUserSource.php b/app/Classes/Modules/Questionnaires/Services/FetchesQAUserSource.php new file mode 100644 index 00000000..c8b467c1 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Services/FetchesQAUserSource.php @@ -0,0 +1,33 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Questionnaires/Services/FetchesQuestion.php b/app/Classes/Modules/Questionnaires/Services/FetchesQuestion.php new file mode 100644 index 00000000..5fe948e5 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Services/FetchesQuestion.php @@ -0,0 +1,33 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Questionnaires/Services/FetchesUserAnswerSelected.php b/app/Classes/Modules/Questionnaires/Services/FetchesUserAnswerSelected.php new file mode 100644 index 00000000..34007879 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Services/FetchesUserAnswerSelected.php @@ -0,0 +1,33 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Questionnaires/Services/ListsQuestions.php b/app/Classes/Modules/Questionnaires/Services/ListsQuestions.php new file mode 100644 index 00000000..b736f454 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Services/ListsQuestions.php @@ -0,0 +1,33 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Questionnaires/Services/ListsUserAnswerSelected.php b/app/Classes/Modules/Questionnaires/Services/ListsUserAnswerSelected.php new file mode 100644 index 00000000..0dca07ac --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Services/ListsUserAnswerSelected.php @@ -0,0 +1,33 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Questionnaires/Standards/Rules/CanFetchQuestion.php b/app/Classes/Modules/Questionnaires/Standards/Rules/CanFetchQuestion.php new file mode 100644 index 00000000..de9b3989 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Standards/Rules/CanFetchQuestion.php @@ -0,0 +1,38 @@ +user()->type; + if (in_array($roleToCheck, RoleTypes::ADMIN_ROLES)) { + return true; + } else { + return false; + } + } + + /** + * @param $object + * @return bool + */ + protected function validators($object): bool + { + return true; + + } + + + /** + * @param $object + * @return bool + */ + protected function criteria($object): bool + { + return true; + } + +} diff --git a/app/Classes/ValueObjects/Constants/DocumentType.php b/app/Classes/ValueObjects/Constants/DocumentType.php index 42f8222b..a7b22383 100644 --- a/app/Classes/ValueObjects/Constants/DocumentType.php +++ b/app/Classes/ValueObjects/Constants/DocumentType.php @@ -2,6 +2,7 @@ namespace App\Classes\ValueObjects\Constants; + final class DocumentType { public const PROFILE_PICTURE = 'PROFILE_PICTURE'; @@ -27,4 +28,5 @@ final class DocumentType { public const BULK_PURCHASE_ORDER = 'BULK_PURCHASE_ORDER'; public const BILL_GROUP_PAYMENT_PROOF = 'BILL_GROUP_PAYMENT_PROOF'; + public const ADMIN_WORK_FLOW = 'ADMIN_WORK_FLOW'; } diff --git a/app/Classes/ValueObjects/Constants/QAType.php b/app/Classes/ValueObjects/Constants/QAType.php new file mode 100644 index 00000000..245c78ba --- /dev/null +++ b/app/Classes/ValueObjects/Constants/QAType.php @@ -0,0 +1,25 @@ +execute($request); + } + +} diff --git a/app/Http/Controllers/Questionnaires/UpdateNextQuestionV1AdminWFController.php b/app/Http/Controllers/Questionnaires/UpdateNextQuestionV1AdminWFController.php new file mode 100644 index 00000000..87ed672b --- /dev/null +++ b/app/Http/Controllers/Questionnaires/UpdateNextQuestionV1AdminWFController.php @@ -0,0 +1,31 @@ +question; + // $questionnaireSetId = $currentQuestion['questionnaire_set_id']; + // $questionnaireSet = QAQuestionnaireSet::where('id', $questionnaireSetId)->first(); + // $group = ''; + // $version = 0; + // if($questionnaireSet){ + // $group = $questionnaireSet->group; + // $version = $questionnaireSet->version; + // } + return $logic->execute($request); + } +} diff --git a/app/Http/Resources/AnswerOptionsResource.php b/app/Http/Resources/AnswerOptionsResource.php new file mode 100644 index 00000000..6c168847 --- /dev/null +++ b/app/Http/Resources/AnswerOptionsResource.php @@ -0,0 +1,27 @@ + $this->id, + 'display_text' => $this->display_text, + 'value' => $this->value, + 'order' => $this->order, + 'question_number' => $this->question_number, + 'next_question_number' => $this->next_question_number, + ]; + } +} diff --git a/app/Http/Resources/QuestionResource.php b/app/Http/Resources/QuestionResource.php new file mode 100644 index 00000000..3519a27a --- /dev/null +++ b/app/Http/Resources/QuestionResource.php @@ -0,0 +1,71 @@ +question = $question; + $this->userId = $userId; + $this->isPrevious = $isPrevious; + $this->isNoGoingBack = $isNoGoingBack; + $this->previousAnswer = $previousAnswer; + } + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request) + { + if($this->question){ + $q = (object)$this->question; + return [ + 'id' => $q->id, + 'question_number' => $q->question_number, + 'question_title' => $q->question_title, + 'question_description' => $q->question_description, + 'question_type' => $q->question_type, + 'question_answers' => AnswerOptionsResource::collection(QAAnswerOptions::where('question_number', $q->question_number)->where('questionnaire_set_id', $q->questionnaire_set_id)->orderBy('order', 'ASC')->get()), + 'questionnaire_set_id' => $q->questionnaire_set_id, + // 'questionnaire' => new QuestionnaireSetsResource($q->questionnaire), + // 'questionnaire_answers' => $q->is_end ? QuestionnaireAnswersResource::collection(QAUserAnswerSelected::where('user_id', $this->userId)->get()) : null, + 'next_nested_question' => $q->next_nested_question, + 'next_main_question' => $q->next_main_question, + 'is_start' => $q->is_start, + 'is_end' => $q->is_end, + 'end_text' => $q->end_text, + 'order' => $q->order, + 'answer' => $this->previousAnswer, + 'url' => $q->url, + 'is_no_going_back' => $this->isNoGoingBack, + 'is_previous' => $this->isPrevious, + ]; + } + else{ + return []; + } + } +} diff --git a/app/Http/Resources/QuestionnaireAnswersResource.php b/app/Http/Resources/QuestionnaireAnswersResource.php new file mode 100644 index 00000000..1deda19d --- /dev/null +++ b/app/Http/Resources/QuestionnaireAnswersResource.php @@ -0,0 +1,25 @@ +question_id)->first(); + return [ + 'question_title' => $question ? $question->question_title : null, + 'answer' => $this->answer + ]; + } +} diff --git a/app/Http/Resources/QuestionnaireSetsResource.php b/app/Http/Resources/QuestionnaireSetsResource.php new file mode 100644 index 00000000..8a2e39ec --- /dev/null +++ b/app/Http/Resources/QuestionnaireSetsResource.php @@ -0,0 +1,25 @@ + $this->name, + // 'description' => $this->description, + 'group' => $this->group, + 'version' => $this->version, + ]; + } +} diff --git a/app/Http/Resources/QuestionsAnswersResource.php b/app/Http/Resources/QuestionsAnswersResource.php new file mode 100644 index 00000000..48599583 --- /dev/null +++ b/app/Http/Resources/QuestionsAnswersResource.php @@ -0,0 +1,44 @@ +question_id)->first(); + $source = new UserSourceResource($this->userSource); + $user = $this->source_id === 0 ? new UserResource($this->user) : null; + $answerOption = QAAnswerOptions::where('id', $this->answer_option_id)->first(); + + $user_marking = ''; + if($user){ + $companyModule = $user->companyModule()->first(); + $user_marking = $companyModule ? $companyModule->inviters()->withPivot('invitee_reference')->first()->pivot->invitee_reference : ""; + } + + return [ + 'question_id' => $this->question_id, + 'questionnaire' => new QuestionnaireSetsResource($this->question->questionnaire), + 'question_title' => $question ? $question->question_title : null, + 'answer' => $answerOption ? $answerOption->display_text : null, + 'answer_value' => $answerOption ? $answerOption->value : null, + 'source_system' => null, + 'source_marking' => $user ? $user_marking : $source->marking, + 'source_email' => $user ? $user->email : $source->email, + 'created_at' => Carbon::parse($this->created_at)->format('d-m-Y'), + ]; + } +} diff --git a/app/Http/Resources/UserSourceResource.php b/app/Http/Resources/UserSourceResource.php new file mode 100644 index 00000000..f39fbdfe --- /dev/null +++ b/app/Http/Resources/UserSourceResource.php @@ -0,0 +1,24 @@ + $this->system, + 'email' => $this->email, + 'marking' => $this->marking, + ]; + } +} diff --git a/app/Models/Booking.php b/app/Models/Booking.php index ff0b45b9..2f1b4074 100644 --- a/app/Models/Booking.php +++ b/app/Models/Booking.php @@ -2,10 +2,12 @@ namespace App\Models; + use App\Classes\General\Interfaces\Documentable; use App\Classes\General\Interfaces\Transactionable; use App\Classes\General\Traits\LogData; use App\Classes\ValueObjects\Constants\RoleTypes; +use App\Classes\General\Interfaces\KeyValueInterface; use App\Scopes\CustomerBookingsScope; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphMany; @@ -25,7 +27,7 @@ use Staudenmeir\EloquentHasManyDeep\HasRelationships; * @property int convertible_currency_id * @property int conversion_currency_id */ -class Booking extends AbstractModel implements Documentable, Transactionable +class Booking extends AbstractModel implements Documentable, Transactionable, KeyValueInterface { use HasRelationships; use SoftDeletes; @@ -121,5 +123,11 @@ class Booking extends AbstractModel implements Documentable, Transactionable } } - + /** + * @return MorphMany + */ + public function attributesKVP(): MorphMany + { + return $this->morphMany(KeyValuePair::class, 'owner'); + } } diff --git a/app/Models/QAAnswerOptions.php b/app/Models/QAAnswerOptions.php new file mode 100644 index 00000000..cd2eca79 --- /dev/null +++ b/app/Models/QAAnswerOptions.php @@ -0,0 +1,9 @@ +BelongsTo(QAQuestionnaireSet::class, 'questionnaire_set_id', 'id'); + } +} diff --git a/app/Models/QAUserAnswerSelected.php b/app/Models/QAUserAnswerSelected.php new file mode 100644 index 00000000..56515d31 --- /dev/null +++ b/app/Models/QAUserAnswerSelected.php @@ -0,0 +1,71 @@ +morphMany(Document::class, 'owner'); + } + + /** + * @return BelongsTo + */ + public function userSource(): BelongsTo + { + return $this->BelongsTo(QAUserSource::class, 'source_id', 'id'); + } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->BelongsTo(User::class, 'user_id', 'id'); + } + + /** + * @return BelongsTo + */ + public function question(): BelongsTo + { + return $this->BelongsTo(QAQuestions::class, 'question_id', 'id'); + } + + + /** + * @return BelongsTo + */ + public function answer(): BelongsTo + { + return $this->BelongsTo(QAAnswerOptions::class, 'answer_option_id', 'id'); + } + + /** + * @return MorphMany + */ + public function attributesKVP(): MorphMany + { + return $this->morphMany(KeyValuePair::class, 'owner'); + } + +} diff --git a/app/Models/QAUserSource.php b/app/Models/QAUserSource.php new file mode 100644 index 00000000..21401df5 --- /dev/null +++ b/app/Models/QAUserSource.php @@ -0,0 +1,9 @@ +text('value')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('key_value_pairs', function (Blueprint $table) { + $table->string('value', 191)->change(); + }); + } +} diff --git a/database/migrations/2024_11_26_025531_create_qa_answer_options_table.php b/database/migrations/2024_11_26_025531_create_qa_answer_options_table.php new file mode 100644 index 00000000..9198768a --- /dev/null +++ b/database/migrations/2024_11_26_025531_create_qa_answer_options_table.php @@ -0,0 +1,41 @@ +id(); + $table->text('display_text'); + $table->string('value'); + $table->unsignedBigInteger('order')->default(0); + $table->string('question_number'); + $table->string('next_question_number')->nullable(); + $table->unsignedBigInteger('questionnaire_set_id'); + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('questionnaire_set_id')->references('id')->on('qa_questionnaire_sets'); + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('qa_answer_options'); + } +} diff --git a/database/migrations/2024_11_26_025531_create_qa_questionnaire_sets_table.php b/database/migrations/2024_11_26_025531_create_qa_questionnaire_sets_table.php new file mode 100644 index 00000000..bb826252 --- /dev/null +++ b/database/migrations/2024_11_26_025531_create_qa_questionnaire_sets_table.php @@ -0,0 +1,38 @@ +id(); + $table->string('name'); + $table->string('description')->nullable(); + $table->string('group')->nullable(); + $table->unsignedBigInteger('order')->default(0); + $table->unsignedBigInteger('next_set')->nullable(); + $table->unsignedBigInteger('version')->default(1); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('qa_questionnaire_sets'); + } +} diff --git a/database/migrations/2024_11_26_025531_create_qa_questions_table.php b/database/migrations/2024_11_26_025531_create_qa_questions_table.php new file mode 100644 index 00000000..c6811f4e --- /dev/null +++ b/database/migrations/2024_11_26_025531_create_qa_questions_table.php @@ -0,0 +1,47 @@ +id(); + $table->string('question_number'); + $table->text('question_title'); + $table->text('question_description')->nullable(); + $table->unsignedBigInteger('questionnaire_set_id'); + $table->string('next_nested_question')->nullable(); + $table->string('next_main_question')->nullable(); + $table->unsignedBigInteger('question_type'); + $table->tinyInteger('is_start'); + $table->tinyInteger('is_end'); + $table->string('end_text')->nullable(); + $table->unsignedBigInteger('order')->default(0); + $table->string('url')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('questionnaire_set_id')->references('id')->on('qa_questionnaire_sets'); + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('qa_questions'); + } +} diff --git a/database/migrations/2024_11_26_025531_create_qa_user_answer_selected_table.php b/database/migrations/2024_11_26_025531_create_qa_user_answer_selected_table.php new file mode 100644 index 00000000..80374062 --- /dev/null +++ b/database/migrations/2024_11_26_025531_create_qa_user_answer_selected_table.php @@ -0,0 +1,46 @@ +id(); + $table->unsignedBigInteger('user_id')->default(0); + $table->unsignedBigInteger('source_id')->default(0); + $table->unsignedBigInteger('question_id'); + $table->text('question_metadata')->nullable(); + $table->unsignedBigInteger('answer_option_id'); + $table->text('answer')->nullable(); + $table->string('reference')->nullable(); + $table->integer('time_used_seconds')->nullable(); + $table->tinyInteger('is_previous')->default(0); + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('question_id')->references('id')->on('qa_questions'); + // $table->foreign('answer_option_id')->references('id')->on('qa_answer_options'); + // $table->foreign('user_id')->references('id')->on('users'); + // $table->foreign('source_id')->references('id')->on('qa_user_source'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('qa_user_answer_selected'); + } +} diff --git a/database/migrations/2024_11_26_025531_create_qa_user_source_table.php b/database/migrations/2024_11_26_025531_create_qa_user_source_table.php new file mode 100644 index 00000000..638a8d44 --- /dev/null +++ b/database/migrations/2024_11_26_025531_create_qa_user_source_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('system'); + $table->string('email'); + $table->string('marking'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('qa_user_source'); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index eb94b74f..03cb13d4 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -48,6 +48,9 @@ class DatabaseSeeder extends Seeder $this->call(DummyDataSeeder::class); } + // Admin Work Flow + // $this->call(QAWorkFlow2Seeder::class); + // DB::commit(); } } diff --git a/database/seeds/QAWorkFlow2Seeder.php b/database/seeds/QAWorkFlow2Seeder.php new file mode 100644 index 00000000..2c7f6602 --- /dev/null +++ b/database/seeds/QAWorkFlow2Seeder.php @@ -0,0 +1,437 @@ + 'Admin Work Flow', + 'description' => 'Admin Work Flow', + 'group' => 'workflow', + 'version' => 1, + 'questions' => [ + [ + 'question_number' => 'node_0', + 'question_title' => 'Are you ready to work today?', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => true, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Yes', 'value' => 'start_work', 'next_question_number' => 'start_work'], + ['display_text' => 'No', 'value' => 'no_work', 'next_question_number' => 'no_work'], + ], + ], + [ + 'question_number' => 'start_work', + 'question_title' => 'What will you work on?', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => '1688', 'value' => '1688', 'next_question_number' => '1688'], + ['display_text' => 'Approve PO', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], + ['display_text' => 'Fill PO', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], + ], + ], + [ + 'question_number' => 'no_work', + 'question_title' => 'Come back when you are ready to work', + 'question_type' => QAType::DEFAULT, + 'is_start' => false, + 'is_end' => true, + ], + [ + 'question_number' => '1688', + 'question_title' => '1688', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'url' => 'api.admin_work_flow.fetch_oldest_order', //'http://localhost:8082/api/v1/admin-work-flow/fetch-oldest-order', //this.route("api.admin_work_flow.fetch_oldest_order"), + 'is_start' => true, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Login Issue', 'value' => '1688_login_issue', 'btn_color' => 'warning', 'next_question_number' => '1688_login_issue'], + ['display_text' => 'Login Successful', 'value' => '1688_login_successful', 'next_question_number' => '1688_login_successful'], + ], + ], + [ + 'question_number' => '1688_login_issue', + 'question_title' => '1688_login_issue', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Need TAC', 'value' => 'Need Tac', 'next_question_number' => '1688_issue_submit'], + ['display_text' => 'Wrong login details', 'value' => 'Wrong Login Details', 'next_question_number' => '1688_issue_submit'], + ['display_text' => 'Others', 'value' => '1688_login_issue_others', 'next_question_number' => '1688_login_issue_others'], + ['display_text' => 'Order Cancelled', 'value' => '1688_refund_request', 'next_question_number' => '1688_refund_request'], + ], + ], + [ + 'question_number' => '1688_login_successful', + 'question_title' => '1688_login_successful', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'url' => 'api.admin_work_flow.fetch_model_attributes', //'http://localhost:8082/api/v1/admin-work-flow/{booking_id}/fetch-model-attributes', + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => "Can't verify order?", 'value' => '1688_login_successful_cannot_verify', 'next_question_number' => '1688_login_successful_cannot_verify'], + ['display_text' => 'Order Verified', 'value' => '1688_order_verify', 'next_question_number' => '1688_order_verify'], + ], + ], + [ + 'question_number' => '1688_login_successful_cannot_verify', + 'question_title' => '1688_login_successful_cannot_verify', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Amount not found', 'value' => 'Amount not found', 'next_question_number' => '1688_issue_submit'], + ['display_text' => 'Plus Member', 'value' => 'Plus Member', 'next_question_number' => '1688_issue_submit'], + ['display_text' => 'Others', 'value' => '1688_login_successful_cannot_verify_others', 'next_question_number' => '1688_login_successful_cannot_verify_others'], + ['display_text' => 'Customer did not verify 1688 account', 'value' => 'Customer did not verify 1688 account', 'next_question_number' => '1688_issue_submit'], + ['display_text' => 'WorldFirst account linked another account', 'value' => 'WorldFirst account linked another account', 'next_question_number' => '1688_issue_submit'], + ], + ], + [ + 'question_number' => '1688_login_successful_cannot_verify_others', + 'question_title' => 'What other issues did you encounter? Upload documents if needed', + 'question_type' => QAType::REMARKS_WITH_DOCUMENT_UPLOAD, + 'next_nested_question' => '1688_issue_submit', + 'is_start' => false, + 'is_end' => false, + ], + [ + 'question_number' => '1688_order_verify', + 'question_title' => 'Order Amount', + 'question_description' => 'Please key in the order amount', + 'question_' => 'Order Amount', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'url' => 'api.admin_work_flow.fetch_model_attributes', + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Yes', 'value' => '1688_order_verification', 'next_question_number' => '1688_order_verification'], + ], + ], + [ + 'question_number' => '1688_order_verification', + 'question_title' => 'Are You Sure this is the correct amount?', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Yes', 'value' => '1688_proceed_order', 'next_question_number' => '1688_proceed_order'], + ['display_text' => 'No', 'value' => 'insufficient_order', 'next_question_number' => 'insufficient_order'], + ], + ], + [ + 'question_number' => '1688_proceed_order', + 'question_title' => 'Proceed the order on 1688?', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Yes', 'value' => '1688_submit', 'next_question_number' => '1688_submit'], + ['display_text' => 'Got Issue', 'value' => '1688_proceed_order_issue', 'next_question_number' => '1688_proceed_order_issue'], + ], + ], + [ + 'question_number' => '1688_proceed_order_issue', + 'question_title' => '1688_proceed_order_issue', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Wrong Pin Number', 'value' => 'Wrong Pin Number', 'next_question_number' => '1688_issue_submit'], + ['display_text' => 'Not Enough Stock', 'value' => 'Not Enough Stock', 'next_question_number' => '1688_issue_submit'], + ['display_text' => 'Others', 'value' => 'Others', 'next_question_number' => '1688_proceed_order_issue_others'], + ['display_text' => 'No CrossBoarder', 'value' => 'No CrossBoarder', 'next_question_number' => '1688_issue_submit'], + ['display_text' => 'AngPau', 'value' => 'AngPau', 'next_question_number' => '1688_issue_submit'], + ], + ], + [ + 'question_number' => '1688_proceed_order_issue_others', + 'question_title' => 'What other issues did you encounter? Upload documents if needed', + 'question_type' => QAType::REMARKS_WITH_DOCUMENT_UPLOAD, + 'next_nested_question' => '1688_issue_submit', + 'is_start' => false, + 'is_end' => false, + ], + [ + 'question_number' => '1688_login_issue_others', + 'question_title' => 'What other issues did you encounter? Upload documents if needed', + 'question_type' => QAType::REMARKS_WITH_DOCUMENT_UPLOAD, + 'next_nested_question' => '1688_issue_submit', + 'is_start' => false, + 'is_end' => false, + ], + [ + 'question_number' => '1688_submit', + 'question_title' => '1688_submit', + 'question_type' => QAType::SUBMIT_1688_3_TYPES_DOCUMENTS, + 'next_nested_question' => '1688_issue_submit', + 'is_start' => false, + 'is_end' => false, + ], + [ + 'question_number' => '1688_issue_submit', + 'question_title' => 'Issue has been submitted', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => true, + 'answer_options' => [ + ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], + ['display_text' => 'Next Order', 'value' => '1688', 'next_question_number' => '1688'], + ], + ], + [ + 'question_number' => '1688_refund_request', + 'question_title' => 'Refund Request sent', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => true, + 'answer_options' => [ + ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], + ['display_text' => 'Next Order', 'value' => '1688', 'next_question_number' => '1688'], + ], + ], + [ + 'question_number' => 'approve_po', + 'question_title' => 'approve_po', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'url' => "api.admin_work_flow.fetch_pending_approve_po", + 'is_start' => true, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Approve', 'value' => 'approve_po_approved', 'next_question_number' => 'approve_po_approved'], + ['display_text' => 'Edit PO', 'value' => 'approve_po_edit', 'next_question_number' => 'approve_po_edit'], + ['display_text' => 'Reject', 'value' => 'approve_po_reject', 'next_question_number' => 'approve_po_reject'], + ], + ], + [ + 'question_number' => 'fill_po', + 'question_title' => 'fill_po', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'url' => "api.admin_work_flow.fetch_pending_fill_po", + 'is_start' => true, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Edit PO', 'value' => 'fill_po_edit', 'next_question_number' => 'fill_po_edit'], + ], + ], + [ + 'question_number' => 'approve_po_filled', + 'question_title' => 'PO Filled', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => true, + 'answer_options' => [ + ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], + ['display_text' => 'Next PO (Fill)', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], + ['display_text' => 'Next PO (Approve)', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], + ], + ], + [ + 'question_number' => 'approve_po_approved', + 'question_title' => 'PO Approved', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => true, + 'answer_options' => [ + ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], + ['display_text' => 'Next PO (Fill)', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], + ['display_text' => 'Next PO (Approve)', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], + ], + ], + [ + 'question_number' => 'approve_po_edit', + 'question_title' => 'approve_po_edit', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'url' => "api.booking.show", + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Issue?', 'value' => 'approve_po_edit_po_issue', 'next_question_number' => 'approve_po_edit_po_issue'], + ['display_text' => 'Done', 'value' => 'approve_po_filled', 'next_question_number' => 'approve_po_filled'], + ], + ], + [ + 'question_number' => 'approve_po_reject', + 'question_title' => 'PO Rejected', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Sensitive Goods', 'value' => 'Sensitive Goods'], + ['display_text' => 'Others', 'value' => 'PO Others', 'next_question_number' => 'reject_po_others'], + ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], + ['display_text' => 'Next Order', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], + ], + ], + [ + 'question_number' => 'reject_po_others', + 'question_title' => 'What other issues did you encounter? Upload documents if needed', + 'question_type' => QAType::REMARKS_WITH_DOCUMENT_UPLOAD, + 'next_nested_question' => 'reject_po_others_complete', + 'is_start' => false, + 'is_end' => false, + ], + [ + 'question_number' => 'reject_po_others_complete', + 'question_title' => 'Issue has been submitted', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => true, + 'answer_options' => [ + ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], + ['display_text' => 'Next PO (Fill)', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], + ['display_text' => 'Next PO (Approve)', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], + ], + ], + [ + 'question_number' => 'approve_po_edit_po_issue', + 'question_title' => 'approve_po_edit_po_issue', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Sensitive Goods', 'value' => 'Sensitive Goods', 'next_question_number' => 'fill_po_issue_submit'], + ['display_text' => 'Others', 'value' => 'fill_po_issue_others', 'next_question_number' => 'fill_po_issue_others'], + ], + ], + [ + 'question_number' => 'fill_po_issue_others', + 'question_title' => 'What other issues did you encounter? Upload documents if needed', + 'question_type' => QAType::REMARKS_WITH_DOCUMENT_UPLOAD, + 'next_nested_question' => 'fill_po_issue_submit', + 'is_start' => false, + 'is_end' => false, + ], + [ + 'question_number' => 'fill_po_issue_submit', + 'question_title' => 'Issue has been submitted', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => true, + 'answer_options' => [ + ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], + ['display_text' => 'Next PO (Fill)', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], + ['display_text' => 'Next PO (Approve)', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], + ], + ], + [ + 'question_number' => 'stop_working', + 'question_title' => 'Thank you. Reload page to restart.', + 'question_type' => QAType::DEFAULT, + 'is_start' => false, + 'is_end' => true, + ], + [ + 'question_number' => 'insufficient_order', + 'question_title' => 'Are You Sure this is the correct amount?', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Underpaid Order', 'value' => 'underpaird_order_1', 'next_question_number' => ''], + ['display_text' => 'Underpaid Order', 'value' => 'underpaird_order_2', 'next_question_number' => ''], + ['display_text' => 'Overpaid Order', 'value' => 'underpaird_order_2', 'next_question_number' => ''], + ], + ], + [ + 'question_number' => 'fill_po_edit', + 'question_title' => 'fill_po_edit', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'url' => "api.booking.show", + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Issue?', 'value' => 'fill_po_edit_issue', 'next_question_number' => 'fill_po_edit_issue'], + ['display_text' => 'Done', 'value' => 'fill_po_edit_filled', 'next_question_number' => 'fill_po_edit_filled'], + ], + ], + [ + 'question_number' => 'fill_po_edit_issue', + 'question_title' => 'fill_po_edit_issue', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => false, + 'answer_options' => [ + ['display_text' => 'Sensitive Goods', 'value' => 'Sensitive Goods', 'next_question_number' => 'fill_po_issue_submit'], + ['display_text' => 'Others', 'value' => 'fill_po_issue_others', 'next_question_number' => 'fill_po_issue_others'], + ], + ], + [ + 'question_number' => 'fill_po_edit_filled', + 'question_title' => 'PO Filled', + 'question_type' => QAType::MULTIPLE_CHOICES, + 'is_start' => false, + 'is_end' => true, + 'answer_options' => [ + ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], + ['display_text' => 'Next PO (Fill)', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], + ['display_text' => 'Next PO (Approve)', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], + ], + ], + ] + ], + ]; + + foreach ($questionnaireSets as $set) { + $questionnaireSet = QAQuestionnaireSet::create([ + 'name' => $set['name'], + 'description' => $set['description'], + 'group' => $set['group'], + ]); + + foreach ($set['questions'] as $key => $questionData) { + $question = new QAQuestions; + $question->question_number = $questionData['question_number']; + $question->question_title = $questionData['question_title']; + if (isset($questionData['question_description'])) { + $question->question_description = $questionData['question_description']; + } + $question->question_type = $questionData['question_type']; + $question->questionnaire_set_id = $questionnaireSet->id; + + if (isset($questionData['next_nested_question'])) { + $question->next_nested_question = $questionData['next_nested_question']; + } + if (isset($questionData['next_main_question'])) { + $question->next_main_question = $questionData['next_main_question']; + } + + $question->is_start = $questionData['is_start']; + $question->is_end = $questionData['is_end']; + if (isset($questionData['end_text'])) { + $question->end_text = $questionData['end_text']; + } + + if (isset($questionData['url'])) { + $question->url = $questionData['url']; + } + + $question->order = $key + 1; + $question->save(); + + if (isset($questionData['answer_options'])) { + foreach ($questionData['answer_options'] as $optionData) { + $answerOption = new QAAnswerOptions; + $answerOption->display_text = $optionData['display_text']; + $answerOption->value = $optionData['value']; + $answerOption->question_number = $questionData['question_number']; + $answerOption->next_question_number = $optionData['next_question_number'] ?? null; + $answerOption->questionnaire_set_id = $questionnaireSet->id; + $answerOption->save(); + } + } + } + } + } +} diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue index de4e496b..83afca8d 100644 --- a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue @@ -283,7 +283,9 @@ this.parameters = { products: this.products }; - + this.$emit('update-parameters', { + parameters: this.parameters, + }); this.submit(route('api.transaction.po.create', this.data.id), 'post', this.section, true, true); }, uploadProducts() { @@ -291,7 +293,9 @@ this.parameters = { files: this.files }; - + this.$emit('update-parameters', { + parameters: this.parameters, + }); this.submit(route('api.transaction.po.import', this.data.id), 'post', this.section, true, true); }, successHandler(){ diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormReadOnlyComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormReadOnlyComponent.vue new file mode 100644 index 00000000..8d3b22cb --- /dev/null +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormReadOnlyComponent.vue @@ -0,0 +1,86 @@ + + diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderItemFormComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderItemFormComponent.vue index d91d2661..518dfda1 100644 --- a/resources/assets/vue/components/bookings/forms/PurchaseOrderItemFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderItemFormComponent.vue @@ -35,7 +35,7 @@

Unit Price

-

{{product.unit_price}}

+

{{product.unit_price.toFixed(3)}}

Quantity

@@ -144,7 +144,7 @@ }, created() { this.product = this.data; - this.product.unit_price = (Math.round((this.product.unit_price+ Number.EPSILON) * 1000) / 1000).toFixed(3) + this.product.unit_price = (Math.round((this.product.unit_price+ Number.EPSILON) * 1000) / 1000); }, computed: { productTotal(){ diff --git a/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue b/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue new file mode 100644 index 00000000..edb45304 --- /dev/null +++ b/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue @@ -0,0 +1,640 @@ + + + diff --git a/resources/assets/vue/components/general/sections/AdminWorkFlowSectionComponent.vue b/resources/assets/vue/components/general/sections/AdminWorkFlowSectionComponent.vue new file mode 100644 index 00000000..4770fdf8 --- /dev/null +++ b/resources/assets/vue/components/general/sections/AdminWorkFlowSectionComponent.vue @@ -0,0 +1,670 @@ + + + diff --git a/resources/assets/vue/general/mixins/request.js b/resources/assets/vue/general/mixins/request.js index 4048415a..dc944b45 100644 --- a/resources/assets/vue/general/mixins/request.js +++ b/resources/assets/vue/general/mixins/request.js @@ -41,7 +41,7 @@ export default { } successNotification ? this.$store.dispatch('createNotification', { title: response.title, message: response.message, type: 'success' }) : null; - this.successHandler(response) + this.successHandler(response, section); }); diff --git a/resources/views/pages/dashboards/admin_work_flow.blade.php b/resources/views/pages/dashboards/admin_work_flow.blade.php new file mode 100644 index 00000000..558ab193 --- /dev/null +++ b/resources/views/pages/dashboards/admin_work_flow.blade.php @@ -0,0 +1,5 @@ +@extends('layouts.base_portal') +@section('inner_content') + + +@endsection diff --git a/routes/admin_work_flow.php b/routes/admin_work_flow.php new file mode 100644 index 00000000..df0b714e --- /dev/null +++ b/routes/admin_work_flow.php @@ -0,0 +1,228 @@ + 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'AdminWorkFlow'], function () { + + // Generalized JSON response function + function jsonResponse($data = null, $message = null, $status = 200) + { + $response = ['message' => $message]; + + if ($data !== null) { + $response['data'] = $data; + } + + return response()->json($response, $status); + } + + + Route::get('/fetch-oldest-order', function () { + $excludedBookingIds = KeyValuePair::where('owner_type', 'App\Models\Booking') + ->where('key', '1688_admin_workflow_processed') + ->pluck('owner_id') + ->filter(function ($value) { + return is_numeric($value); + }) + ->toArray(); + + $booking = Booking::where('service_id', 4) + ->where('status', ApprovalStatus::APPROVED) + ->whereNotIn('id', $excludedBookingIds) + ->first(); + + return $booking ? jsonResponse([ + 'passwords' => $booking->bank->holder_name ?? null, + 'account_no' => $booking->bank->account_no ?? null, + 'pin' => $booking->bank->bank_branch ?? null, + 'holder_name' => $booking->bank->holder_name ?? null, + 'booking' => $booking + ]) : jsonResponse(null, 'No booking found', 404); + })->name('fetch_oldest_order'); + + + Route::get('/fetch-pending-approved-po', function () { + $excludedBookingIds = KeyValuePair::where('owner_type', 'App\Models\Booking') + ->where('key', 'approve_po_admin_workflow_processed') + ->pluck('owner_id') + ->filter(function ($value) { + return is_numeric($value); + }) + ->toArray(); + + $booking = Booking::with('transactions') + ->where('service_id', 4) + ->where('status', ApprovalStatus::APPROVED) + ->whereNotIn('id', $excludedBookingIds) + ->whereHas('transactions', fn ($query) => $query->where('type', TransactionType::PURCHASE_ORDER)->where('status', '<', ApprovalStatus::APPROVED)) + ->first(); + + $result = new BookingResource($booking); + return jsonResponse([ + 'booking' => $result, + ]); + + // return $booking ? jsonResponse(new BookingResource($booking)) : jsonResponse(null, 'No booking found', 404); + })->name('fetch_pending_approve_po'); + + + + Route::get('/fetch-pending-fill-po', function () { + $excludedBookingIds = KeyValuePair::where('owner_type', 'App\Models\Booking') + ->where('key', 'fill_po_admin_workflow_processed') + ->pluck('owner_id') + ->filter(function ($value) { + return is_numeric($value); + }) + ->toArray(); + + $booking = Booking::with('transactions') + ->where('service_id', 4) + ->where('status', ApprovalStatus::APPROVED) + ->whereNotIn('id', $excludedBookingIds) + ->whereDoesntHave('transactions', fn ($query) => $query->where('type', TransactionType::PURCHASE_ORDER)) + ->first(); + + $result = new BookingResource($booking); + return jsonResponse([ + 'booking' => $result, + ]); + + // return $booking ? jsonResponse(new BookingResource($booking)) : jsonResponse(null, 'No booking found', 404); + })->name('fetch_pending_fill_po'); + + + + Route::get('{booking_id}/fetch-model-attributes', function ($bookingId) { + $booking = Booking::find($bookingId); + + if (!$booking) { + return jsonResponse(null, 'No approved booking found', 404); + } + + $attributes = $booking->modelAttributes() + ->where('name', BookingAttributeNames::ORDER_REFERENCE_NO) + ->get(['id', 'value']) + ->map(fn ($attr) => $attr->only(['id', 'value'])); + + return jsonResponse([ + 'booking' => $booking, + 'booking_attributes' => $attributes, + ]); + })->name('fetch_model_attributes'); + + + // Route::post('/add-workflow-timestamp', function (Request $request) { + // $validator = Validator::make($request->all(), [ + // 'current_node' => 'required|string', + // 'next_node' => 'required|string', + // 'seconds' => 'required|integer', + // 'user_id' => 'required|integer|exists:users,id', + // 'session_id' => 'required|string', + // ]); + + // if ($validator->fails()) { + // throw new RequestValidationException($validator->messages()->first()); + // } + + // $workflowTimestamp = WorkflowTimestamp::create($validator->validated()); + + // return jsonResponse($workflowTimestamp, 'Workflow timestamp created successfully', 201); + // })->name('add_workflow_timestamp'); + + + // Route::post('/create-1688-login-issue-remark', function (Request $request) { + // $validator = Validator::make($request->all(), [ + // 'booking_id' => 'required', + // 'remark' => 'required', + // 'user_id' => 'required', + // ]); + + // if ($validator->fails()) { + // throw new RequestValidationException($validator->messages()->first()); + // } + + // $booking = Booking::find($request->booking_id); + + // if (!$booking) { + // return jsonResponse(null, 'Booking not found', 404); + // } + + // $remark = new Remark([ + // 'commenter_id' => $request->user_id, + // 'content' => $request->remark, + // 'owner_type' => get_class($booking), + // 'owner_id' => $booking->id, + // ]); + + // $remark->save(); + + // return jsonResponse($remark, 'Remark created successfully', 201); + // })->name('create_1688_login_issue_remark'); + + // Route::post('/create_issue_remark', function (Request $request) { + // $validator = Validator::make($request->all(), [ + // 'booking_id' => 'required', + // 'remark' => 'required', + // 'user_id' => 'required', + // ]); + + // if ($validator->fails()) { + // throw new RequestValidationException($validator->messages()->first()); + // } + + // $booking = Booking::find($request->booking_id); + + + // if (!$booking) { + // return jsonResponse(null, 'Booking not found', 404); + // } + + // $remark = new Remark([ + // 'commenter_id' => $request->user_id, + // 'content' => $request->remark, + // 'owner_type' => get_class($booking), + // 'owner_id' => $booking->id, + // ]); + + // $remark->save(); + + // return jsonResponse($remark, 'Remark created successfully', 201); + // })->name('create_issue_remark'); + + // Route::post('/create_po_issue_remark', function (Request $request) { + // $validator = Validator::make($request->all(), [ + // 'booking_id' => 'required', + // 'remark' => 'required', + // 'user_id' => 'required', + // ]); + + // if ($validator->fails()) { + // return response()->json(['message' => $validator->messages()->first()], 400); // Return validation error + // } + + // $booking = Booking::find($request->booking_id); + + // if (!$booking) { + // return response()->json(['message' => 'Booking not found'], 404); + // } + + // $remark = new Remark([ + // 'commenter_id' => $request->user_id, + // 'content' => $request->remark, + // 'owner_type' => get_class($booking), + // 'owner_id' => $booking->id, + // ]); + + // $remark->save(); + + // return response()->json(['data' => $remark, 'message' => 'Remark created successfully'], 201); + // })->name('create_po_issue_remark'); + +}); diff --git a/routes/api.php b/routes/api.php index ef7025a5..5bfb257e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -71,6 +71,10 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function require __DIR__ . '/job.php'; + require __DIR__ . '/questionnaires.php'; + + require __DIR__ . '/admin_work_flow.php'; + // require __DIR__ . '/rate.php'; // require __DIR__ . '/receipt.php'; diff --git a/routes/questionnaires.php b/routes/questionnaires.php new file mode 100644 index 00000000..e3749d81 --- /dev/null +++ b/routes/questionnaires.php @@ -0,0 +1,9 @@ + 'questionnaires', 'as' => 'questionnaires.', 'namespace' => 'Questionnaires'], function () { + Route::get('/{set_id}', 'FetchQuestionV1AdminWFController@fetch')->name('first.question'); + Route::post('/question', 'UpdateNextQuestionV1AdminWFController@fetch')->name('next.question'); + Route::get('/list', 'ListQuestionsAnswersQAController@list')->name('list.questions.answers'); +}); diff --git a/routes/web.php b/routes/web.php index 80725e55..c2812a80 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1183,42 +1183,42 @@ Route::get('/show-white-form-transactions-in-date-range/{from_date}/{to_date}', const fileInput = form.querySelector("input[type=\'file\']"); const files = fileInput.files; const userToken = localStorage.getItem("user-token"); - + if (files.length === 0) { alert("Please select at least one file to upload."); return; } - + const fileReaders = []; let totalFiles = files.length; - + // Create a promise for each file to read it as Base64 Array.from(files).forEach((file) => { const reader = new FileReader(); - + // Create a promise that resolves when the file is read const fileReadPromise = new Promise((resolve, reject) => { reader.onload = (event) => { resolve(event.target.result); }; - + reader.onerror = (error) => { reject(error); }; - + reader.readAsDataURL(file); // Read the file as Data URL (Base64) }); - + fileReaders.push(fileReadPromise); }); - + // Wait for all files to be read Promise.all(fileReaders) .then((fileData) => { const payload = { files: fileData, // Array of objects with name, type, and Base64 content }; - + // Send the payload as JSON return fetch(`/api/v1/transactions/${billId}/bill/verification`, { method: "POST", @@ -1339,3 +1339,7 @@ Route::get('/site.webmanifest', function () { $contents = File::get($filePath); return response($contents, 200)->header('Content-Type', 'application/manifest+json'); }); + +Route::get('/admin-work-flow', function () { + return view('pages.dashboards.admin_work_flow'); +})->name('admin-work-flow'); From b360019668637b6598c3f2cf3c75e461fda6286e Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Mon, 2 Dec 2024 23:57:47 +0800 Subject: [PATCH 02/54] Admin Workflow --- ...p => 2024_11_26_005531_create_qa_questionnaire_sets_table.php} | 0 ..._table.php => 2024_11_26_015531_create_qa_questions_table.php} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename database/migrations/{2024_11_26_025531_create_qa_questionnaire_sets_table.php => 2024_11_26_005531_create_qa_questionnaire_sets_table.php} (100%) rename database/migrations/{2024_11_26_025531_create_qa_questions_table.php => 2024_11_26_015531_create_qa_questions_table.php} (100%) diff --git a/database/migrations/2024_11_26_025531_create_qa_questionnaire_sets_table.php b/database/migrations/2024_11_26_005531_create_qa_questionnaire_sets_table.php similarity index 100% rename from database/migrations/2024_11_26_025531_create_qa_questionnaire_sets_table.php rename to database/migrations/2024_11_26_005531_create_qa_questionnaire_sets_table.php diff --git a/database/migrations/2024_11_26_025531_create_qa_questions_table.php b/database/migrations/2024_11_26_015531_create_qa_questions_table.php similarity index 100% rename from database/migrations/2024_11_26_025531_create_qa_questions_table.php rename to database/migrations/2024_11_26_015531_create_qa_questions_table.php From d5c81fb57f76766453bdec848492a45e5b13ae73 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Tue, 3 Dec 2024 00:20:13 +0800 Subject: [PATCH 03/54] Admin Workflow --- database/seeds/QAWorkFlow2Seeder.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/database/seeds/QAWorkFlow2Seeder.php b/database/seeds/QAWorkFlow2Seeder.php index 2c7f6602..0aace36b 100644 --- a/database/seeds/QAWorkFlow2Seeder.php +++ b/database/seeds/QAWorkFlow2Seeder.php @@ -127,7 +127,7 @@ class QAWorkFlow2Seeder extends Seeder 'is_end' => false, 'answer_options' => [ ['display_text' => 'Yes', 'value' => '1688_proceed_order', 'next_question_number' => '1688_proceed_order'], - ['display_text' => 'No', 'value' => 'insufficient_order', 'next_question_number' => 'insufficient_order'], + ['display_text' => 'No', 'value' => '1688_insufficient_order', 'next_question_number' => '1688_insufficient_order'], ], ], [ @@ -269,21 +269,21 @@ class QAWorkFlow2Seeder extends Seeder 'is_end' => false, 'answer_options' => [ ['display_text' => 'Sensitive Goods', 'value' => 'Sensitive Goods'], - ['display_text' => 'Others', 'value' => 'PO Others', 'next_question_number' => 'reject_po_others'], + ['display_text' => 'Others', 'value' => 'approve_po_reject_others', 'next_question_number' => 'approve_po_reject_others'], ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], ['display_text' => 'Next Order', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], ], ], [ - 'question_number' => 'reject_po_others', + 'question_number' => 'approve_po_reject_others', 'question_title' => 'What other issues did you encounter? Upload documents if needed', 'question_type' => QAType::REMARKS_WITH_DOCUMENT_UPLOAD, - 'next_nested_question' => 'reject_po_others_complete', + 'next_nested_question' => 'approve_po_reject_others_complete', 'is_start' => false, 'is_end' => false, ], [ - 'question_number' => 'reject_po_others_complete', + 'question_number' => 'approve_po_reject_others_complete', 'question_title' => 'Issue has been submitted', 'question_type' => QAType::MULTIPLE_CHOICES, 'is_start' => false, @@ -333,7 +333,7 @@ class QAWorkFlow2Seeder extends Seeder 'is_end' => true, ], [ - 'question_number' => 'insufficient_order', + 'question_number' => '1688_insufficient_order', 'question_title' => 'Are You Sure this is the correct amount?', 'question_type' => QAType::MULTIPLE_CHOICES, 'is_start' => false, From a24f41e5bd543f9bd5c3b95a878bc131e7b12b44 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Wed, 4 Dec 2024 11:18:47 +0800 Subject: [PATCH 04/54] Admin Workflow --- .../UpdateNextQuestionV1AdminWFLogic.php | 24 +++++++---- ...1_create_qa_user_answer_selected_table.php | 2 +- routes/admin_work_flow.php | 42 +++++++++++++++++-- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php b/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php index fed2d673..af701bbd 100644 --- a/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php +++ b/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php @@ -123,22 +123,32 @@ class UpdateNextQuestionV1AdminWFLogic extends AbstractControllerLogic //Marked data that has already been processed so that it does not appear again $isNoGoingBack = null; if($currentQuestion && $currentQuestion['is_end'] === 1){ - $booking = Booking::where('id',$questionMetadata['booking']['id'])->first(); - $kvp = $answer->attributesKVP()->where('key', 'App\Models\Bank')->where('value', $questionMetadata['booking']['id'])->latest()->first(); + $booking = Booking::where('id', $questionMetadata['booking']['id'])->first(); - $key = "admin_workflow_processed"; + $key1 = "admin_workflow_processed"; + $key2 = "admin_workflow_processing"; if (strpos($currentQuestion['question_number'], '1688') === 0) { - $key = '1688_'.$key; + $key1 = '1688_'.$key1; + $key2 = '1688_'.$key2; } if (strpos($currentQuestion['question_number'], 'fill_po') === 0) { - $key = 'fill_po_'.$key; + $key1 = 'fill_po_'.$key1; + $key2 = 'fill_po_'.$key2; } if (strpos($currentQuestion['question_number'], 'approve_po') === 0) { - $key = 'approve_po_'.$key; + $key1 = 'approve_po_'.$key1; + $key2 = 'approve_po_'.$key2; } + $processingRecord = $booking->attributesKVP()->where('key', $key2)->first(); + if ($processingRecord) { + $processingRecord->delete(); + } + + $kvp = $booking->attributesKVP()->where('key', $key1)->first(); + if(!$kvp){ - $keyValuePairObject = new KeyValuePairObject($key, true); + $keyValuePairObject = new KeyValuePairObject($key1, true); $this->createsKeyValuePair->execute($booking, $keyValuePairObject); } // $isNoGoingBack = 1; diff --git a/database/migrations/2024_11_26_025531_create_qa_user_answer_selected_table.php b/database/migrations/2024_11_26_025531_create_qa_user_answer_selected_table.php index 80374062..a7ce3977 100644 --- a/database/migrations/2024_11_26_025531_create_qa_user_answer_selected_table.php +++ b/database/migrations/2024_11_26_025531_create_qa_user_answer_selected_table.php @@ -18,7 +18,7 @@ class CreateQAUserAnswerSelectedTable extends Migration $table->unsignedBigInteger('user_id')->default(0); $table->unsignedBigInteger('source_id')->default(0); $table->unsignedBigInteger('question_id'); - $table->text('question_metadata')->nullable(); + $table->longText('question_metadata')->nullable(); $table->unsignedBigInteger('answer_option_id'); $table->text('answer')->nullable(); $table->string('reference')->nullable(); diff --git a/routes/admin_work_flow.php b/routes/admin_work_flow.php index df0b714e..5f8a0d06 100644 --- a/routes/admin_work_flow.php +++ b/routes/admin_work_flow.php @@ -1,11 +1,14 @@ 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'AdminWorkFlow'], function () { @@ -22,10 +25,25 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp return response()->json($response, $status); } + function markedProcessing($booking, $key){ + $kvp = $booking->attributesKVP()->where('key', $key)->first(); + + if(!$kvp){ + $keyValuePairObject = new KeyValuePairObject($key, true); + (App()->make(CreatesKeyValuePair::class))->execute($booking, $keyValuePairObject); + } + } + Route::get('/fetch-oldest-order', function () { $excludedBookingIds = KeyValuePair::where('owner_type', 'App\Models\Booking') - ->where('key', '1688_admin_workflow_processed') + ->where(function ($query) { + $query->where('key', '1688_admin_workflow_processed') + ->orWhere(function ($query) { + $query->where('key', '1688_admin_workflow_processing') + ->where('updated_at', '>', Carbon::now()->subHour()); + }); + }) ->pluck('owner_id') ->filter(function ($value) { return is_numeric($value); @@ -37,6 +55,8 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->whereNotIn('id', $excludedBookingIds) ->first(); + markedProcessing($booking, '1688_admin_workflow_processing'); + return $booking ? jsonResponse([ 'passwords' => $booking->bank->holder_name ?? null, 'account_no' => $booking->bank->account_no ?? null, @@ -49,7 +69,13 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp Route::get('/fetch-pending-approved-po', function () { $excludedBookingIds = KeyValuePair::where('owner_type', 'App\Models\Booking') - ->where('key', 'approve_po_admin_workflow_processed') + ->where(function ($query) { + $query->where('key', 'approve_po_admin_workflow_processed') + ->orWhere(function ($query) { + $query->where('key', 'approve_po_admin_workflow_processing') + ->where('updated_at', '>', Carbon::now()->subHour()); + }); + }) ->pluck('owner_id') ->filter(function ($value) { return is_numeric($value); @@ -63,6 +89,8 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->whereHas('transactions', fn ($query) => $query->where('type', TransactionType::PURCHASE_ORDER)->where('status', '<', ApprovalStatus::APPROVED)) ->first(); + markedProcessing($booking, 'approve_po_admin_workflow_processing'); + $result = new BookingResource($booking); return jsonResponse([ 'booking' => $result, @@ -75,7 +103,13 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp Route::get('/fetch-pending-fill-po', function () { $excludedBookingIds = KeyValuePair::where('owner_type', 'App\Models\Booking') - ->where('key', 'fill_po_admin_workflow_processed') + ->where(function ($query) { + $query->where('key', 'fill_po_admin_workflow_processed') + ->orWhere(function ($query) { + $query->where('key', 'fill_po_admin_workflow_processing') + ->where('updated_at', '>', Carbon::now()->subHour()); + }); + }) ->pluck('owner_id') ->filter(function ($value) { return is_numeric($value); @@ -89,6 +123,8 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->whereDoesntHave('transactions', fn ($query) => $query->where('type', TransactionType::PURCHASE_ORDER)) ->first(); + markedProcessing($booking, 'fill_po_admin_workflow_processing'); + $result = new BookingResource($booking); return jsonResponse([ 'booking' => $result, From 63d7bb677bbe9ecdefb5aede29ece5f4137ee6c6 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Wed, 4 Dec 2024 12:17:28 +0800 Subject: [PATCH 05/54] Admin Workflow --- .../UpdateNextQuestionV1AdminWFLogic.php | 2 -- .../forms/AdminWorkFlowFormComponent.vue | 29 ++++++++++++++++--- .../assets/vue/general/mixins/request.js | 2 +- routes/admin_work_flow.php | 14 ++++++++- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php b/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php index af701bbd..b6cdb372 100644 --- a/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php +++ b/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php @@ -91,8 +91,6 @@ class UpdateNextQuestionV1AdminWFLogic extends AbstractControllerLogic $this->canFetchQuestion->passes(); - Log::info('currentQuestion: '. json_encode($currentQuestion)); - if ($request->has('answerObj')) { $answer = $request->answerObj; $answerNextQuestionNumber = $answer['next_question_number']; diff --git a/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue b/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue index edb45304..b5ce6eac 100644 --- a/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue +++ b/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue @@ -3,7 +3,7 @@

{{ formattedTime }}

-
+
@@ -333,7 +333,7 @@
-
+
@@ -348,6 +348,20 @@
+
+
+
+
+ {{error}} +
+
+
+
+ +
+
+
+
@@ -383,6 +397,7 @@ stepTime: 0, interval: 1000, isFetching: false, + noMoreWork: false, } }, validations() { @@ -513,8 +528,14 @@ } } }, - errorHandler(error){ - this.error = 'We are sorry, something went wrong. Please inform tech team.'; + errorHandler(error, statusCode, section){ + if(section === 'adminWorkFlowFormGetExternalApiResponse' && statusCode === 404){ + this.noMoreWork = true; + this.error = '[' + this.question.question_title + '] All records processed. Please reload page to continue' ; + } + else{ + this.error = 'We are sorry, something went wrong. Please inform tech team.'; + } this.isFetching = false; }, submitForm(direction) { diff --git a/resources/assets/vue/general/mixins/request.js b/resources/assets/vue/general/mixins/request.js index dc944b45..0b246f2a 100644 --- a/resources/assets/vue/general/mixins/request.js +++ b/resources/assets/vue/general/mixins/request.js @@ -37,7 +37,7 @@ export default { if (!success) { this.openModal(); errorNotification ? this.$store.dispatch('createNotification', { title: response.title, message: response.message, type: 'error' }) : null; - this.errorHandler(response, statusCode); return; + this.errorHandler(response, statusCode, section); return; } successNotification ? this.$store.dispatch('createNotification', { title: response.title, message: response.message, type: 'success' }) : null; diff --git a/routes/admin_work_flow.php b/routes/admin_work_flow.php index 5f8a0d06..c7f55f0f 100644 --- a/routes/admin_work_flow.php +++ b/routes/admin_work_flow.php @@ -55,6 +55,10 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->whereNotIn('id', $excludedBookingIds) ->first(); + if(!$booking){ + return jsonResponse(null, 'No booking found', 404); + } + markedProcessing($booking, '1688_admin_workflow_processing'); return $booking ? jsonResponse([ @@ -89,6 +93,10 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->whereHas('transactions', fn ($query) => $query->where('type', TransactionType::PURCHASE_ORDER)->where('status', '<', ApprovalStatus::APPROVED)) ->first(); + if(!$booking){ + return jsonResponse(null, 'No booking found', 404); + } + markedProcessing($booking, 'approve_po_admin_workflow_processing'); $result = new BookingResource($booking); @@ -123,6 +131,10 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->whereDoesntHave('transactions', fn ($query) => $query->where('type', TransactionType::PURCHASE_ORDER)) ->first(); + if(!$booking){ + return jsonResponse(null, 'No booking found', 404); + } + markedProcessing($booking, 'fill_po_admin_workflow_processing'); $result = new BookingResource($booking); @@ -139,7 +151,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp $booking = Booking::find($bookingId); if (!$booking) { - return jsonResponse(null, 'No approved booking found', 404); + return jsonResponse(null, 'No booking found', 404); } $attributes = $booking->modelAttributes() From 4df4822595e8790bb0de329d4e83f8157b6f8821 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Wed, 4 Dec 2024 12:30:24 +0800 Subject: [PATCH 06/54] Admin Workflow --- routes/{admin_work_flow.php => admin_workflow.php} | 0 routes/api.php | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename routes/{admin_work_flow.php => admin_workflow.php} (100%) diff --git a/routes/admin_work_flow.php b/routes/admin_workflow.php similarity index 100% rename from routes/admin_work_flow.php rename to routes/admin_workflow.php diff --git a/routes/api.php b/routes/api.php index 5bfb257e..c9b6a9c8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -73,7 +73,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function require __DIR__ . '/questionnaires.php'; - require __DIR__ . '/admin_work_flow.php'; + require __DIR__ . '/admin_workflow.php'; // require __DIR__ . '/rate.php'; // require __DIR__ . '/receipt.php'; From 76ba4035934166c81433bb2031c6cf774702b51e Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Wed, 4 Dec 2024 12:57:19 +0800 Subject: [PATCH 07/54] Admin Workflow --- .../forms/AdminWorkFlowFormComponent.vue | 4 +-- routes/admin_workflow.php | 34 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue b/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue index b5ce6eac..941a61e8 100644 --- a/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue +++ b/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue @@ -366,7 +366,7 @@
- ID: {{question.question_number}}
+ {{question.question_number}}, Booking Id: {{externalApiResponse.data.booking.id}}
@@ -650,7 +650,7 @@ position: fixed; bottom: 0px; left: 0px; - width: 200px; + width: 400px; background-color: rgba(0, 0, 0, 0.8); color: white; font-size: 10px; diff --git a/routes/admin_workflow.php b/routes/admin_workflow.php index c7f55f0f..8b83131f 100644 --- a/routes/admin_workflow.php +++ b/routes/admin_workflow.php @@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Route; Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'AdminWorkFlow'], function () { // Generalized JSON response function - function jsonResponse($data = null, $message = null, $status = 200) + function jsonResp($data = null, $message = null, $status = 200) { $response = ['message' => $message]; @@ -56,18 +56,18 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->first(); if(!$booking){ - return jsonResponse(null, 'No booking found', 404); + return jsonResp(null, 'No booking found', 404); } markedProcessing($booking, '1688_admin_workflow_processing'); - return $booking ? jsonResponse([ + return $booking ? jsonResp([ 'passwords' => $booking->bank->holder_name ?? null, 'account_no' => $booking->bank->account_no ?? null, 'pin' => $booking->bank->bank_branch ?? null, 'holder_name' => $booking->bank->holder_name ?? null, 'booking' => $booking - ]) : jsonResponse(null, 'No booking found', 404); + ]) : jsonResp(null, 'No booking found', 404); })->name('fetch_oldest_order'); @@ -94,17 +94,17 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->first(); if(!$booking){ - return jsonResponse(null, 'No booking found', 404); + return jsonResp(null, 'No booking found', 404); } markedProcessing($booking, 'approve_po_admin_workflow_processing'); $result = new BookingResource($booking); - return jsonResponse([ + return jsonResp([ 'booking' => $result, ]); - // return $booking ? jsonResponse(new BookingResource($booking)) : jsonResponse(null, 'No booking found', 404); + // return $booking ? jsonResp(new BookingResource($booking)) : jsonResp(null, 'No booking found', 404); })->name('fetch_pending_approve_po'); @@ -132,17 +132,17 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->first(); if(!$booking){ - return jsonResponse(null, 'No booking found', 404); + return jsonResp(null, 'No booking found', 404); } markedProcessing($booking, 'fill_po_admin_workflow_processing'); $result = new BookingResource($booking); - return jsonResponse([ + return jsonResp([ 'booking' => $result, ]); - // return $booking ? jsonResponse(new BookingResource($booking)) : jsonResponse(null, 'No booking found', 404); + // return $booking ? jsonResp(new BookingResource($booking)) : jsonResp(null, 'No booking found', 404); })->name('fetch_pending_fill_po'); @@ -151,7 +151,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp $booking = Booking::find($bookingId); if (!$booking) { - return jsonResponse(null, 'No booking found', 404); + return jsonResp(null, 'No booking found', 404); } $attributes = $booking->modelAttributes() @@ -159,7 +159,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->get(['id', 'value']) ->map(fn ($attr) => $attr->only(['id', 'value'])); - return jsonResponse([ + return jsonResp([ 'booking' => $booking, 'booking_attributes' => $attributes, ]); @@ -181,7 +181,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp // $workflowTimestamp = WorkflowTimestamp::create($validator->validated()); - // return jsonResponse($workflowTimestamp, 'Workflow timestamp created successfully', 201); + // return jsonResp($workflowTimestamp, 'Workflow timestamp created successfully', 201); // })->name('add_workflow_timestamp'); @@ -199,7 +199,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp // $booking = Booking::find($request->booking_id); // if (!$booking) { - // return jsonResponse(null, 'Booking not found', 404); + // return jsonResp(null, 'Booking not found', 404); // } // $remark = new Remark([ @@ -211,7 +211,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp // $remark->save(); - // return jsonResponse($remark, 'Remark created successfully', 201); + // return jsonResp($remark, 'Remark created successfully', 201); // })->name('create_1688_login_issue_remark'); // Route::post('/create_issue_remark', function (Request $request) { @@ -229,7 +229,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp // if (!$booking) { - // return jsonResponse(null, 'Booking not found', 404); + // return jsonResp(null, 'Booking not found', 404); // } // $remark = new Remark([ @@ -241,7 +241,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp // $remark->save(); - // return jsonResponse($remark, 'Remark created successfully', 201); + // return jsonResp($remark, 'Remark created successfully', 201); // })->name('create_issue_remark'); // Route::post('/create_po_issue_remark', function (Request $request) { From 91e5b1f400f087fd90254fe3a66e2731e55ac1a0 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Wed, 4 Dec 2024 13:34:35 +0800 Subject: [PATCH 08/54] Admin Workflow --- routes/admin_workflow.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/routes/admin_workflow.php b/routes/admin_workflow.php index 8b83131f..e7105f0f 100644 --- a/routes/admin_workflow.php +++ b/routes/admin_workflow.php @@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Route; Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'AdminWorkFlow'], function () { // Generalized JSON response function - function jsonResp($data = null, $message = null, $status = 200) + function responseJson($data = null, $message = null, $status = 200) { $response = ['message' => $message]; @@ -56,18 +56,18 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->first(); if(!$booking){ - return jsonResp(null, 'No booking found', 404); + return responseJson(null, 'No booking found', 404); } markedProcessing($booking, '1688_admin_workflow_processing'); - return $booking ? jsonResp([ + return responseJson([ 'passwords' => $booking->bank->holder_name ?? null, 'account_no' => $booking->bank->account_no ?? null, 'pin' => $booking->bank->bank_branch ?? null, 'holder_name' => $booking->bank->holder_name ?? null, 'booking' => $booking - ]) : jsonResp(null, 'No booking found', 404); + ]); })->name('fetch_oldest_order'); @@ -94,17 +94,17 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->first(); if(!$booking){ - return jsonResp(null, 'No booking found', 404); + return responseJson(null, 'No booking found', 404); } markedProcessing($booking, 'approve_po_admin_workflow_processing'); $result = new BookingResource($booking); - return jsonResp([ + return responseJson([ 'booking' => $result, ]); - // return $booking ? jsonResp(new BookingResource($booking)) : jsonResp(null, 'No booking found', 404); + // return $booking ? responseJson(new BookingResource($booking)) : responseJson(null, 'No booking found', 404); })->name('fetch_pending_approve_po'); @@ -132,17 +132,17 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->first(); if(!$booking){ - return jsonResp(null, 'No booking found', 404); + return responseJson(null, 'No booking found', 404); } markedProcessing($booking, 'fill_po_admin_workflow_processing'); $result = new BookingResource($booking); - return jsonResp([ + return responseJson([ 'booking' => $result, ]); - // return $booking ? jsonResp(new BookingResource($booking)) : jsonResp(null, 'No booking found', 404); + // return $booking ? responseJson(new BookingResource($booking)) : responseJson(null, 'No booking found', 404); })->name('fetch_pending_fill_po'); @@ -151,7 +151,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp $booking = Booking::find($bookingId); if (!$booking) { - return jsonResp(null, 'No booking found', 404); + return responseJson(null, 'No booking found', 404); } $attributes = $booking->modelAttributes() @@ -159,7 +159,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->get(['id', 'value']) ->map(fn ($attr) => $attr->only(['id', 'value'])); - return jsonResp([ + return responseJson([ 'booking' => $booking, 'booking_attributes' => $attributes, ]); @@ -181,7 +181,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp // $workflowTimestamp = WorkflowTimestamp::create($validator->validated()); - // return jsonResp($workflowTimestamp, 'Workflow timestamp created successfully', 201); + // return responseJson($workflowTimestamp, 'Workflow timestamp created successfully', 201); // })->name('add_workflow_timestamp'); @@ -199,7 +199,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp // $booking = Booking::find($request->booking_id); // if (!$booking) { - // return jsonResp(null, 'Booking not found', 404); + // return responseJson(null, 'Booking not found', 404); // } // $remark = new Remark([ @@ -211,7 +211,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp // $remark->save(); - // return jsonResp($remark, 'Remark created successfully', 201); + // return responseJson($remark, 'Remark created successfully', 201); // })->name('create_1688_login_issue_remark'); // Route::post('/create_issue_remark', function (Request $request) { @@ -229,7 +229,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp // if (!$booking) { - // return jsonResp(null, 'Booking not found', 404); + // return responseJson(null, 'Booking not found', 404); // } // $remark = new Remark([ @@ -241,7 +241,7 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp // $remark->save(); - // return jsonResp($remark, 'Remark created successfully', 201); + // return responseJson($remark, 'Remark created successfully', 201); // })->name('create_issue_remark'); // Route::post('/create_po_issue_remark', function (Request $request) { From 6f9dd969e0bed48b51ee3c3637f49ca85e25e35a Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Wed, 4 Dec 2024 13:55:01 +0800 Subject: [PATCH 09/54] Admin Workflow --- routes/admin_workflow.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/routes/admin_workflow.php b/routes/admin_workflow.php index e7105f0f..e2cadb45 100644 --- a/routes/admin_workflow.php +++ b/routes/admin_workflow.php @@ -11,9 +11,9 @@ use App\Models\KeyValuePair; use Carbon\Carbon; use Illuminate\Support\Facades\Route; -Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'AdminWorkFlow'], function () { +// Generalized JSON response function - // Generalized JSON response function +if (!function_exists('responseJson')) { function responseJson($data = null, $message = null, $status = 200) { $response = ['message' => $message]; @@ -24,7 +24,9 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp return response()->json($response, $status); } +} +if (!function_exists('markedProcessing')) { function markedProcessing($booking, $key){ $kvp = $booking->attributesKVP()->where('key', $key)->first(); @@ -33,6 +35,9 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp (App()->make(CreatesKeyValuePair::class))->execute($booking, $keyValuePairObject); } } +} + +Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'AdminWorkFlow'], function () { Route::get('/fetch-oldest-order', function () { @@ -108,7 +113,6 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp })->name('fetch_pending_approve_po'); - Route::get('/fetch-pending-fill-po', function () { $excludedBookingIds = KeyValuePair::where('owner_type', 'App\Models\Booking') ->where(function ($query) { @@ -146,7 +150,6 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp })->name('fetch_pending_fill_po'); - Route::get('{booking_id}/fetch-model-attributes', function ($bookingId) { $booking = Booking::find($bookingId); From a76d03d54818468264eff04b8b2f76b4c1c57ad8 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Wed, 4 Dec 2024 14:15:50 +0800 Subject: [PATCH 10/54] Admin Workflow --- routes/web.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/web.php b/routes/web.php index c2812a80..a7881fdc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1340,6 +1340,6 @@ Route::get('/site.webmanifest', function () { return response($contents, 200)->header('Content-Type', 'application/manifest+json'); }); -Route::get('/admin-work-flow', function () { +Route::get('/admin-workflow', function () { return view('pages.dashboards.admin_work_flow'); })->name('admin-work-flow'); From 2bbe7e4eab8e9e2e5134653e6294ba3b7fae6d75 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Wed, 25 Dec 2024 20:59:49 +0800 Subject: [PATCH 11/54] Admin Workflow - Update business logic to fetch 1688 data for processing --- .../DownloadMockUpWhiteFormPdfLogic.php | 4 +- .../AdminWorkflowBaseController.php | 61 +++++++++++++++++++ .../forms/AdminWorkFlowFormComponent.vue | 20 +++++- routes/admin_workflow.php | 55 ++++++----------- 4 files changed, 98 insertions(+), 42 deletions(-) create mode 100644 app/Http/Controllers/AdminWorkflow/AdminWorkflowBaseController.php diff --git a/app/Classes/Modules/Transactions/ControllersLogic/DownloadMockUpWhiteFormPdfLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/DownloadMockUpWhiteFormPdfLogic.php index 4d80785f..7c68536d 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/DownloadMockUpWhiteFormPdfLogic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/DownloadMockUpWhiteFormPdfLogic.php @@ -48,7 +48,7 @@ class DownloadMockUpWhiteFormPdfLogic return ['id' => $value]; }, json_decode($request->input('payments'))); - DB::beginTransaction(); + DB::beginTransaction(); //cief todo: 74 $this->createSupplierTransactionProcessor->execute($supplier, $rate, $payments); @@ -56,7 +56,7 @@ class DownloadMockUpWhiteFormPdfLogic $pdf = LaravelMpdf::loadView('pages.pdfs.currency_vendor_order', ['transactions' => $this->createSupplierTransactionProcessor->getBills(), 'transferFeeTransactions' => $this->createSupplierTransactionProcessor->getTransferTransactions(), 'supplier' => $supplier]); - DB::rollBack(); + DB::rollBack(); //cief todo: 74 $exportFileName = 'MockUpWhiteForm.pdf'; $filesystemDriver = Storage::getDefaultDriver(); diff --git a/app/Http/Controllers/AdminWorkflow/AdminWorkflowBaseController.php b/app/Http/Controllers/AdminWorkflow/AdminWorkflowBaseController.php new file mode 100644 index 00000000..55e16882 --- /dev/null +++ b/app/Http/Controllers/AdminWorkflow/AdminWorkflowBaseController.php @@ -0,0 +1,61 @@ +where(function ($query) { + $query->where('key', '1688_admin_workflow_processed') + ->orWhere(function ($query) { + $query->where('key', '1688_admin_workflow_processing') + ->where('updated_at', '>', Carbon::now()->subHour()); + }); + }) + ->pluck('owner_id') + ->filter(function ($value) { + return is_numeric($value); + }) + ->toArray(); + + + $timeAgo = Carbon::now()->subMonths(6); + $serviceId = 4; + $booking = Booking::where('service_id', $serviceId) + ->where('status', ApprovalStatus::APPROVED) + ->whereNotIn('id', $excludedBookingIds) + ->whereHas('bills', function ($query) { + $query->whereHas('groupTransaction', function ($query) { + $query->whereHas('group', function ($query) { + $query->whereDoesntHave('billGroup')->whereIn('issuer', [2]); + }); + }); + }) + ->where('created_at', '>=', $timeAgo) + ->latest() + ->first(); + + if(!$booking){ + return responseJson(null, 'No booking found', 404); + } + + markedProcessing($booking, '1688_admin_workflow_processing'); + + return responseJson([ + 'passwords' => $booking->bank->holder_name ?? null, + 'account_no' => $booking->bank->account_no ?? null, + 'pin' => $booking->bank->bank_branch ?? null, + 'holder_name' => $booking->bank->holder_name ?? null, + 'booking' => $booking + ]); + } +} diff --git a/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue b/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue index 941a61e8..96fc0717 100644 --- a/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue +++ b/resources/assets/vue/components/general/forms/AdminWorkFlowFormComponent.vue @@ -73,7 +73,23 @@ {{ externalApiResponse.data.booking.marking }} -
+
+
+

+ Reference: {{ externalApiResponse.data.reference }} +

+

+ Marking: {{ externalApiResponse.data.marking }} +

+

+ Currency Rate: {{ externalApiResponse.data.currency_rate }} +

+

+ Total Amount: {{ externalApiResponse.data.total_amount }} +

+
+
+
diff --git a/routes/admin_workflow.php b/routes/admin_workflow.php index e2cadb45..8c2f535d 100644 --- a/routes/admin_workflow.php +++ b/routes/admin_workflow.php @@ -5,10 +5,12 @@ use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\TransactionType; use App\Classes\ValueObjects\Constants\BookingAttributeNames; +use App\Http\Controllers\AdminWorkflow\AdminWorkflowBaseController; use App\Http\Resources\BookingResource; use App\Models\Booking; use App\Models\KeyValuePair; use Carbon\Carbon; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Route; // Generalized JSON response function @@ -27,53 +29,25 @@ if (!function_exists('responseJson')) { } if (!function_exists('markedProcessing')) { - function markedProcessing($booking, $key){ - $kvp = $booking->attributesKVP()->where('key', $key)->first(); + function markedProcessing($model, $key){ + $kvp = $model->attributesKVP()->where('key', $key)->first(); + + if($kvp){ + $kvp->delete(); + $kvp = null; + } if(!$kvp){ $keyValuePairObject = new KeyValuePairObject($key, true); - (App()->make(CreatesKeyValuePair::class))->execute($booking, $keyValuePairObject); + (App()->make(CreatesKeyValuePair::class))->execute($model, $keyValuePairObject); } } } -Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'AdminWorkFlow'], function () { + Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'AdminWorkFlow'], function () { - Route::get('/fetch-oldest-order', function () { - $excludedBookingIds = KeyValuePair::where('owner_type', 'App\Models\Booking') - ->where(function ($query) { - $query->where('key', '1688_admin_workflow_processed') - ->orWhere(function ($query) { - $query->where('key', '1688_admin_workflow_processing') - ->where('updated_at', '>', Carbon::now()->subHour()); - }); - }) - ->pluck('owner_id') - ->filter(function ($value) { - return is_numeric($value); - }) - ->toArray(); - - $booking = Booking::where('service_id', 4) - ->where('status', ApprovalStatus::APPROVED) - ->whereNotIn('id', $excludedBookingIds) - ->first(); - - if(!$booking){ - return responseJson(null, 'No booking found', 404); - } - - markedProcessing($booking, '1688_admin_workflow_processing'); - - return responseJson([ - 'passwords' => $booking->bank->holder_name ?? null, - 'account_no' => $booking->bank->account_no ?? null, - 'pin' => $booking->bank->bank_branch ?? null, - 'holder_name' => $booking->bank->holder_name ?? null, - 'booking' => $booking - ]); - })->name('fetch_oldest_order'); + Route::get('/fetch-oldest-order', [AdminWorkflowBaseController::class, 'fetchOldestOrder'])->name('fetch_oldest_order'); Route::get('/fetch-pending-approved-po', function () { @@ -162,9 +136,14 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp ->get(['id', 'value']) ->map(fn ($attr) => $attr->only(['id', 'value'])); + $transaction = $booking->bills()->first(); //cief todo: 74 - more than 1 record? return responseJson([ 'booking' => $booking, 'booking_attributes' => $attributes, + 'reference' =>$transaction->owner->owner->marking, + 'marking' => $transaction->owner->owner->company->reference, + 'currency_rate' => $transaction->currency_rate, + 'total_amount' =>$transaction->currency->short_code . ' ' . number_format((float)$transaction->amount, 2, '.', '') ]); })->name('fetch_model_attributes'); From 2e625828e79a967dc59de2b79786309b26a89da7 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Fri, 3 Jan 2025 01:51:33 +0800 Subject: [PATCH 12/54] Admin Workflow - New UI for Admin to go through questions and answers --- .../General/Eloquent/Filters/EndDate.php | 20 +++ .../Eloquent/Filters/HasQuestionnaire.php | 24 +++ .../General/Eloquent/Filters/IdAfter.php | 35 ++++ .../General/Eloquent/Filters/StartDate.php | 20 +++ .../General/Eloquent/Filters/WithAnswers.php | 27 +++ .../ExportQuestionsAnswersLogic.php | 65 +++++++ .../Services/ExportsQuestionsAnswers.php | 114 ++++++++++++ .../Rules/CanExportQuestionsAnswers.php | 48 +++++ .../ListQuestionnaireSetLogic.php | 60 +++++++ .../ListQuestionsAnswersLogic.php | 60 +++++++ .../UpdateNextQuestionV1AdminWFLogic.php | 8 +- ...hp => ListsQuestionUserAnswerSelected.php} | 4 +- .../Services/ListsQuestionnaireSet.php | 33 ++++ ...MenuQuestions.php => CanListQuestions.php} | 0 .../ValueObjects/Constants/QuestionGroup.php | 22 +++ .../ExportQuestionsAnswersController.php | 22 +++ .../AdminWorkflowBaseController.php | 2 +- .../ListQuestionnaireSetController.php | 21 +++ .../ListQuestionsAnswersController.php | 21 +++ .../Resources/QuestionnaireSetsResource.php | 5 +- .../Resources/QuestionsAnswersResource.php | 19 +- app/Models/QAQuestions.php | 8 + ...142918_add_group_to_qa_questions_table.php | 32 ++++ database/seeds/DatabaseSeeder.php | 5 +- ...rkFlow2Seeder.php => QAWorkFlowSeeder.php} | 46 ++++- .../elements/QuestionAnswerComponent.vue | 144 +++++++++++++++ .../elements/QuestionAnswerInnerComponent.vue | 40 +++++ .../QuestionAnswerSectionComponent.vue | 165 ++++++++++++++++++ .../elements/OpenLinkInNewTabComponent.vue | 7 + .../vue/vuex/modules/loadRequestQueue.js | 11 +- .../admin-workflow/list-workflow.blade.php | 14 ++ routes/admin_workflow.php | 6 +- routes/questionnaires.php | 7 +- routes/web.php | 7 +- 34 files changed, 1097 insertions(+), 25 deletions(-) create mode 100644 app/Classes/General/Eloquent/Filters/EndDate.php create mode 100644 app/Classes/General/Eloquent/Filters/HasQuestionnaire.php create mode 100644 app/Classes/General/Eloquent/Filters/IdAfter.php create mode 100644 app/Classes/General/Eloquent/Filters/StartDate.php create mode 100644 app/Classes/General/Eloquent/Filters/WithAnswers.php create mode 100644 app/Classes/Modules/Exports/ControllersLogic/ExportQuestionsAnswersLogic.php create mode 100644 app/Classes/Modules/Exports/Services/ExportsQuestionsAnswers.php create mode 100644 app/Classes/Modules/Exports/Standards/Rules/CanExportQuestionsAnswers.php create mode 100644 app/Classes/Modules/Questionnaires/ControllersLogic/ListQuestionnaireSetLogic.php create mode 100644 app/Classes/Modules/Questionnaires/ControllersLogic/ListQuestionsAnswersLogic.php rename app/Classes/Modules/Questionnaires/Services/{ListsUserAnswerSelected.php => ListsQuestionUserAnswerSelected.php} (83%) create mode 100644 app/Classes/Modules/Questionnaires/Services/ListsQuestionnaireSet.php rename app/Classes/Modules/Questionnaires/Standards/Rules/{CanListHelpMenuQuestions.php => CanListQuestions.php} (100%) create mode 100644 app/Classes/ValueObjects/Constants/QuestionGroup.php create mode 100644 app/Http/Controllers/Exports/ExportQuestionsAnswersController.php rename app/Http/Controllers/{AdminWorkflow => Questionnaires}/AdminWorkflowBaseController.php (97%) create mode 100644 app/Http/Controllers/Questionnaires/ListQuestionnaireSetController.php create mode 100644 app/Http/Controllers/Questionnaires/ListQuestionsAnswersController.php create mode 100644 database/migrations/2024_12_28_142918_add_group_to_qa_questions_table.php rename database/seeds/{QAWorkFlow2Seeder.php => QAWorkFlowSeeder.php} (93%) create mode 100644 resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue create mode 100644 resources/assets/vue/components/admin-workflow/elements/QuestionAnswerInnerComponent.vue create mode 100644 resources/assets/vue/components/admin-workflow/sections/QuestionAnswerSectionComponent.vue create mode 100644 resources/views/pages/admin-workflow/list-workflow.blade.php diff --git a/app/Classes/General/Eloquent/Filters/EndDate.php b/app/Classes/General/Eloquent/Filters/EndDate.php new file mode 100644 index 00000000..b6fb0d43 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/EndDate.php @@ -0,0 +1,20 @@ +whereDate('created_at', '<=', Carbon::parse($value)->format('Y-m-d')); + } +} diff --git a/app/Classes/General/Eloquent/Filters/HasQuestionnaire.php b/app/Classes/General/Eloquent/Filters/HasQuestionnaire.php new file mode 100644 index 00000000..ae4e6787 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/HasQuestionnaire.php @@ -0,0 +1,24 @@ +whereHas('question', function ($subquery) use($value){ + $subquery->whereHas('questionnaire', function ($subsubquery) use($value){ + $subsubquery->where('id', $value); + }); + }); + } +} diff --git a/app/Classes/General/Eloquent/Filters/IdAfter.php b/app/Classes/General/Eloquent/Filters/IdAfter.php new file mode 100644 index 00000000..55959e75 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/IdAfter.php @@ -0,0 +1,35 @@ +', $id) + ->whereIn('answer', $questionGroups) + ->orderBy('id', 'asc') + ->limit(1) + ->value('id'); + + if ($nextId) { + return $builder->where('id', '>', $id) + ->where('id', '<', $nextId); + } + + return $builder->where('id', '>', $id); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/StartDate.php b/app/Classes/General/Eloquent/Filters/StartDate.php new file mode 100644 index 00000000..ce2281b8 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/StartDate.php @@ -0,0 +1,20 @@ +whereDate('created_at', '>=', Carbon::parse($value)->format('Y-m-d')); + } +} diff --git a/app/Classes/General/Eloquent/Filters/WithAnswers.php b/app/Classes/General/Eloquent/Filters/WithAnswers.php new file mode 100644 index 00000000..a8ff3bb9 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/WithAnswers.php @@ -0,0 +1,27 @@ +where(function ($query) use($value){ + if($value){ + $query->where('answer', '=', $value); + } + else{ + $query->whereIn('answer', QuestionGroup::QUESTION_GROUPS); + } + }); + } +} diff --git a/app/Classes/Modules/Exports/ControllersLogic/ExportQuestionsAnswersLogic.php b/app/Classes/Modules/Exports/ControllersLogic/ExportQuestionsAnswersLogic.php new file mode 100644 index 00000000..4c8c1500 --- /dev/null +++ b/app/Classes/Modules/Exports/ControllersLogic/ExportQuestionsAnswersLogic.php @@ -0,0 +1,65 @@ + 'Export Questions & Answers', + 'message' => 'You have successfully exported data' + ]; + } + + /** @var ExportsQuestionsAnswers */ + private $exportsQuestionsAnswers; + + /** @var CanExportQuestionsAnswers */ + private $canExport; + + /** + * ExportFeedbackDataLogic constructor. + * @param ExportsQuestionsAnswers $exportsQuestionsAnswers + * @param CanExportQuestionsAnswers $canExport + */ + public function __construct(ExportsQuestionsAnswers $exportsQuestionsAnswers, CanExportQuestionsAnswers $canExport) + { + $this->exportsQuestionsAnswers = $exportsQuestionsAnswers; + $this->canExport = $canExport; + } + + + /** + * @param Request $request + * @return Response + */ + public function logic(Request $request) : JsonResponse + { + $this->canExport->passes(); + + $this->exportsQuestionsAnswers->setFilters(Helper::deserializeFilters($request->input('filters'))); + + $exportFileName = 'qas.xls'; + $filesystemDriver = Storage::getDefaultDriver(); + if($filesystemDriver === 's3'){ + return $this->response([ 'src' => AWSS3Helper::S3Exportable($exportFileName, $this->exportsQuestionsAnswers) ]); + } + + return $this->response([ 'src' => null ]); + } + +} diff --git a/app/Classes/Modules/Exports/Services/ExportsQuestionsAnswers.php b/app/Classes/Modules/Exports/Services/ExportsQuestionsAnswers.php new file mode 100644 index 00000000..68b6f5da --- /dev/null +++ b/app/Classes/Modules/Exports/Services/ExportsQuestionsAnswers.php @@ -0,0 +1,114 @@ +filters = $filters; + } + + /** + * @return \Illuminate\Support\Collection|mixed + */ + public function query() + { + $data = (new ApplyFiltersToQuery())->execute(QAUserAnswerSelected::query(), $this->filters, true); + + return $data; + } + + /** + * @param QAUserAnswerSelected $userAnswer + * + * @return array + */ + public function map($userAnswer): array + { + $source = $userAnswer->userSource; + $user = $userAnswer->source_id === 0 ? $userAnswer->user : null; + $answer = $userAnswer->answer()->first(); + $rows = [[ + $userAnswer->id, + $userAnswer->question->questionnaire->version, + $userAnswer->question->questionnaire->description, + $userAnswer->question->question_title, + $answer ? $answer->display_text : null, + $answer ? $answer->value : null, + $user ? $user->email : $source->email, + $userAnswer->time_used_seconds, + Carbon::parse($userAnswer->created_at)->format('d-m-Y'), + "YES" + ]]; + + + $nextId = QAUserAnswerSelected::where('id', '>', $userAnswer->id) + ->whereIn('answer', QuestionGroup::QUESTION_GROUPS) + ->orderBy('id', 'asc') + ->limit(1) + ->value('id'); + + if ($nextId) { + $temp = QAUserAnswerSelected::where('reference', '=', $userAnswer->reference)->where('id', '>', $userAnswer->id) + ->where('id', '<', $nextId)->get(); + } + else{ + $temp = QAUserAnswerSelected::where('reference', '=', $userAnswer->reference)->where('id', '>', $userAnswer->id)->get(); + } + + foreach ($temp as $relatedAnswer) { + $source = $relatedAnswer->userSource; + $user = $relatedAnswer->source_id === 0 ? $relatedAnswer->user : null; + $answer = $relatedAnswer->answer()->first(); + + $rows[] = [ + $relatedAnswer->id, + $relatedAnswer->question->questionnaire->version, + $relatedAnswer->question->questionnaire->description, + $relatedAnswer->question->question_title, + $answer ? $answer->display_text : null, + $answer ? $answer->value : null, + $user ? $user->email : $source->email, + $relatedAnswer->time_used_seconds, + Carbon::parse($relatedAnswer->created_at)->format('d-m-Y'), + ]; + } + + return $rows; + } +} diff --git a/app/Classes/Modules/Exports/Standards/Rules/CanExportQuestionsAnswers.php b/app/Classes/Modules/Exports/Standards/Rules/CanExportQuestionsAnswers.php new file mode 100644 index 00000000..5072a6b3 --- /dev/null +++ b/app/Classes/Modules/Exports/Standards/Rules/CanExportQuestionsAnswers.php @@ -0,0 +1,48 @@ +user()){ + $roleToCheck = Auth()->user()->type; + if (in_array($roleToCheck, RoleTypes::ADMIN_ROLES)) { + return true; + } + else { + return false; + } + } + return false; + } + + /** + * @param $object + * @return bool + */ + protected function validators($object): bool + { + return true; + } + + + /** + * @param $object + * @return bool + */ + protected function criteria($object): bool + { + return true; + } + +} diff --git a/app/Classes/Modules/Questionnaires/ControllersLogic/ListQuestionnaireSetLogic.php b/app/Classes/Modules/Questionnaires/ControllersLogic/ListQuestionnaireSetLogic.php new file mode 100644 index 00000000..d8fe5667 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/ControllersLogic/ListQuestionnaireSetLogic.php @@ -0,0 +1,60 @@ + 'Retrieved Questionnaire Sets', + 'message' => 'You have successfully retrieved a list of questionnaire sets' + ]; + } + + /** @var CanListQuestions */ + private $canListQuestions; + + /** @var ListsQuestionnaireSet */ + private $listsQuestionnaireSet; + + /** + * ListQuestionnaireSetLogic constructor. + * @param CanListQuestions $canListQuestions + * @param ListsQuestionnaireSet $listsQuestionnaires + */ + public function __construct(CanListQuestions $canListQuestions, ListsQuestionnaireSet $listsQuestionnaireSet) + { + $this->canListQuestions = $canListQuestions; + $this->listsQuestionnaireSet = $listsQuestionnaireSet; + } + + + /** + * @param Request $request + * @return JsonResponse + * @throws \App\Classes\Exceptions\AccessForbiddenException + * @throws \App\Classes\Exceptions\MalformedRequestException + * @throws \App\Classes\Exceptions\RequestValidationException + */ + public function logic(Request $request) : JsonResponse + { + // $this->canListQuestions->passes(); //cief todo: 74 + + $query = $this->listsQuestionnaireSet->execute($this->listsQuestionnaireSet->deserializeFilters($request->input('filters'))); + + return $this->collectionResponse(QuestionnaireSetsResource::collection($query)); + } + +} diff --git a/app/Classes/Modules/Questionnaires/ControllersLogic/ListQuestionsAnswersLogic.php b/app/Classes/Modules/Questionnaires/ControllersLogic/ListQuestionsAnswersLogic.php new file mode 100644 index 00000000..bac51604 --- /dev/null +++ b/app/Classes/Modules/Questionnaires/ControllersLogic/ListQuestionsAnswersLogic.php @@ -0,0 +1,60 @@ + 'Retrieved Questions Answers', + 'message' => 'You have successfully retrieved a list of questions and answers' + ]; + } + + /** @var CanListQuestions */ + private $canListQuestions; + + /** @var ListsQuestionUserAnswerSelected */ + private $listsQuestionUserAnswerSelected; + + /** + * ListQuestionsAnswersQALogic constructor. + * @param CanListQuestions $canListQuestions + * @param ListsQuestionUserAnswerSelected $listsQuestionUserAnswerSelected + */ + public function __construct(CanListQuestions $canListQuestions, ListsQuestionUserAnswerSelected $listsQuestionUserAnswerSelected) + { + $this->canListQuestions = $canListQuestions; + $this->listsQuestionUserAnswerSelected = $listsQuestionUserAnswerSelected; + } + + + /** + * @param Request $request + * @return JsonResponse + * @throws \App\Classes\Exceptions\AccessForbiddenException + * @throws \App\Classes\Exceptions\MalformedRequestException + * @throws \App\Classes\Exceptions\RequestValidationException + */ + public function logic(Request $request) : JsonResponse + { + $this->canListQuestions->passes(); + + $query = $this->listsQuestionUserAnswerSelected->execute($this->listsQuestionUserAnswerSelected->deserializeFilters($request->input('filters'))); + + return $this->collectionResponse(QuestionsAnswersResource::collection($query)); + } + +} diff --git a/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php b/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php index b6cdb372..53b36cce 100644 --- a/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php +++ b/app/Classes/Modules/Questionnaires/ControllersLogic/UpdateNextQuestionV1AdminWFLogic.php @@ -8,7 +8,7 @@ use App\Classes\Modules\Questionnaires\Standards\Rules\CanFetchQuestion; use App\Classes\Modules\Questionnaires\Processors\SaveAnswerV1AdminWFProcessor; use App\Classes\Modules\Questionnaires\Processors\FetchNextQuestionV1AdminWFProcessor; use App\Classes\Modules\Questionnaires\Processors\FetchFirstQuestionV1AdminWFProcessor; -use App\Classes\Modules\Questionnaires\Services\ListsUserAnswerSelected; +use App\Classes\Modules\Questionnaires\Services\ListsQuestionUserAnswerSelected; use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair; use App\Classes\Modules\Accounts\DataTransferObjects\KeyValuePairObject; use Illuminate\Http\JsonResponse; @@ -43,7 +43,7 @@ class UpdateNextQuestionV1AdminWFLogic extends AbstractControllerLogic /** @var FetchFirstQuestionV1AdminWFProcessor */ private $fetchFirstQuestionQAProcessor; - /** @var ListsUserAnswerSelected */ + /** @var ListsQuestionUserAnswerSelected */ private $listsUserAnswerSelected; /** @var CreatesKeyValuePair */ @@ -55,10 +55,10 @@ class UpdateNextQuestionV1AdminWFLogic extends AbstractControllerLogic * @param SaveAnswerV1AdminWFProcessor $saveAnswerProcessor * @param FetchNextQuestionV1AdminWFProcessor $fetchNextQuestionQAProcessor * @param FetchFirstQuestionV1AdminWFProcessor $fetchFirstQuestionQAProcessor - * @param ListsUserAnswerSelected $listsUserAnswerSelected + * @param ListsQuestionUserAnswerSelected $listsUserAnswerSelected * @param CreatesKeyValuePair $createsKeyValuePair */ - public function __construct(CanFetchQuestion $canFetchQuestion, SaveAnswerV1AdminWFProcessor $saveAnswerProcessor, FetchNextQuestionV1AdminWFProcessor $fetchNextQuestionQAProcessor, FetchFirstQuestionV1AdminWFProcessor $fetchFirstQuestionQAProcessor, ListsUserAnswerSelected $listsUserAnswerSelected, CreatesKeyValuePair $createsKeyValuePair) + public function __construct(CanFetchQuestion $canFetchQuestion, SaveAnswerV1AdminWFProcessor $saveAnswerProcessor, FetchNextQuestionV1AdminWFProcessor $fetchNextQuestionQAProcessor, FetchFirstQuestionV1AdminWFProcessor $fetchFirstQuestionQAProcessor, ListsQuestionUserAnswerSelected $listsUserAnswerSelected, CreatesKeyValuePair $createsKeyValuePair) { $this->canFetchQuestion = $canFetchQuestion; $this->saveAnswerProcessor = $saveAnswerProcessor; diff --git a/app/Classes/Modules/Questionnaires/Services/ListsUserAnswerSelected.php b/app/Classes/Modules/Questionnaires/Services/ListsQuestionUserAnswerSelected.php similarity index 83% rename from app/Classes/Modules/Questionnaires/Services/ListsUserAnswerSelected.php rename to app/Classes/Modules/Questionnaires/Services/ListsQuestionUserAnswerSelected.php index 0dca07ac..358ab612 100644 --- a/app/Classes/Modules/Questionnaires/Services/ListsUserAnswerSelected.php +++ b/app/Classes/Modules/Questionnaires/Services/ListsQuestionUserAnswerSelected.php @@ -7,14 +7,14 @@ use App\Classes\General\Eloquent\AbstractListRecord; use Illuminate\Database\Eloquent\Builder; use App\Models\QAUserAnswerSelected; -class ListsUserAnswerSelected extends AbstractListRecord +class ListsQuestionUserAnswerSelected extends AbstractListRecord { /** @var QAUserAnswerSelected */ private $repository; /** - * ListsUserAnswerSelected constructor. + * ListsQuestionUserAnswerSelected constructor. * @param QAUserAnswerSelected $repository */ public function __construct(QAUserAnswerSelected $repository) diff --git a/app/Classes/Modules/Questionnaires/Services/ListsQuestionnaireSet.php b/app/Classes/Modules/Questionnaires/Services/ListsQuestionnaireSet.php new file mode 100644 index 00000000..fb58304b --- /dev/null +++ b/app/Classes/Modules/Questionnaires/Services/ListsQuestionnaireSet.php @@ -0,0 +1,33 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Questionnaires/Standards/Rules/CanListHelpMenuQuestions.php b/app/Classes/Modules/Questionnaires/Standards/Rules/CanListQuestions.php similarity index 100% rename from app/Classes/Modules/Questionnaires/Standards/Rules/CanListHelpMenuQuestions.php rename to app/Classes/Modules/Questionnaires/Standards/Rules/CanListQuestions.php diff --git a/app/Classes/ValueObjects/Constants/QuestionGroup.php b/app/Classes/ValueObjects/Constants/QuestionGroup.php new file mode 100644 index 00000000..3491792d --- /dev/null +++ b/app/Classes/ValueObjects/Constants/QuestionGroup.php @@ -0,0 +1,22 @@ + '1688', 'id' => QuestionGroup::_1688], + ['text' => 'approve_po', 'id' => QuestionGroup::APPROVE_PO], + ['text' => 'fill_po', 'id' => QuestionGroup::FILL_PO], + ]; + + const QUESTION_GROUPS = [ + QuestionGroup::_1688, + QuestionGroup::APPROVE_PO, + QuestionGroup::FILL_PO, + ]; +} diff --git a/app/Http/Controllers/Exports/ExportQuestionsAnswersController.php b/app/Http/Controllers/Exports/ExportQuestionsAnswersController.php new file mode 100644 index 00000000..58c05a5d --- /dev/null +++ b/app/Http/Controllers/Exports/ExportQuestionsAnswersController.php @@ -0,0 +1,22 @@ +execute($request); + } +} diff --git a/app/Http/Controllers/AdminWorkflow/AdminWorkflowBaseController.php b/app/Http/Controllers/Questionnaires/AdminWorkflowBaseController.php similarity index 97% rename from app/Http/Controllers/AdminWorkflow/AdminWorkflowBaseController.php rename to app/Http/Controllers/Questionnaires/AdminWorkflowBaseController.php index 55e16882..068728a7 100644 --- a/app/Http/Controllers/AdminWorkflow/AdminWorkflowBaseController.php +++ b/app/Http/Controllers/Questionnaires/AdminWorkflowBaseController.php @@ -1,7 +1,7 @@ execute($request); + } + +} diff --git a/app/Http/Controllers/Questionnaires/ListQuestionsAnswersController.php b/app/Http/Controllers/Questionnaires/ListQuestionsAnswersController.php new file mode 100644 index 00000000..091eba5e --- /dev/null +++ b/app/Http/Controllers/Questionnaires/ListQuestionsAnswersController.php @@ -0,0 +1,21 @@ +execute($request); + } + +} diff --git a/app/Http/Resources/QuestionnaireSetsResource.php b/app/Http/Resources/QuestionnaireSetsResource.php index 8a2e39ec..5072a2a6 100644 --- a/app/Http/Resources/QuestionnaireSetsResource.php +++ b/app/Http/Resources/QuestionnaireSetsResource.php @@ -16,8 +16,9 @@ class QuestionnaireSetsResource extends JsonResource public function toArray($request) { return [ - // 'name' => $this->name, - // 'description' => $this->description, + 'id' => $this->id, + 'name' => $this->name, + 'description' => $this->description, 'group' => $this->group, 'version' => $this->version, ]; diff --git a/app/Http/Resources/QuestionsAnswersResource.php b/app/Http/Resources/QuestionsAnswersResource.php index 48599583..f5bf38f6 100644 --- a/app/Http/Resources/QuestionsAnswersResource.php +++ b/app/Http/Resources/QuestionsAnswersResource.php @@ -3,6 +3,8 @@ namespace App\Http\Resources; +use App\Classes\ValueObjects\Constants\DocumentType; +use App\Classes\ValueObjects\Constants\QuestionGroup; use Illuminate\Http\Resources\Json\JsonResource; use App\Models\QAAnswerOptions; use App\Models\QAQuestions; @@ -24,20 +26,27 @@ class QuestionsAnswersResource extends JsonResource $answerOption = QAAnswerOptions::where('id', $this->answer_option_id)->first(); $user_marking = ''; - if($user){ - $companyModule = $user->companyModule()->first(); - $user_marking = $companyModule ? $companyModule->inviters()->withPivot('invitee_reference')->first()->pivot->invitee_reference : ""; - } + // if($user){ + // $companyModule = $user->companyModule()->first(); + // $user_marking = $companyModule ? $companyModule->inviters()->withPivot('invitee_reference')->first()->pivot->invitee_reference : ""; + // } return [ + 'id' => $this->id, 'question_id' => $this->question_id, + 'question_groups' => QuestionGroup::QUESTION_GROUPS, 'questionnaire' => new QuestionnaireSetsResource($this->question->questionnaire), 'question_title' => $question ? $question->question_title : null, + 'question_type' => $question ? $question->question_type : 0, + 'question_metadata' => $this->question_metadata, 'answer' => $answerOption ? $answerOption->display_text : null, - 'answer_value' => $answerOption ? $answerOption->value : null, + 'answer_value' => $this->answer, + 'documents' => new DocumentResource($this->documents->whereIn('document_type', DocumentType::ADMIN_WORK_FLOW)->first()), + 'reference' => $this->reference, 'source_system' => null, 'source_marking' => $user ? $user_marking : $source->marking, 'source_email' => $user ? $user->email : $source->email, + 'time' => $this->time_used_seconds, 'created_at' => Carbon::parse($this->created_at)->format('d-m-Y'), ]; } diff --git a/app/Models/QAQuestions.php b/app/Models/QAQuestions.php index dd01ee15..43c4cce7 100644 --- a/app/Models/QAQuestions.php +++ b/app/Models/QAQuestions.php @@ -16,4 +16,12 @@ class QAQuestions extends AbstractModel { return $this->BelongsTo(QAQuestionnaireSet::class, 'questionnaire_set_id', 'id'); } + + /** + * @return HasMany + */ + public function userAnswers() + { + return $this->hasMany(QAUserAnswerSelected::class, 'question_id', 'id'); + } } diff --git a/database/migrations/2024_12_28_142918_add_group_to_qa_questions_table.php b/database/migrations/2024_12_28_142918_add_group_to_qa_questions_table.php new file mode 100644 index 00000000..45cdc103 --- /dev/null +++ b/database/migrations/2024_12_28_142918_add_group_to_qa_questions_table.php @@ -0,0 +1,32 @@ +string('group')->after('questionnaire_set_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('questionnaire_set_id', function (Blueprint $table) { + $table->dropColumn('group'); + }); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 03cb13d4..c46c7227 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -5,6 +5,7 @@ use Database\Seeders\CompaniesTableDevelopmentSeeder; use Database\Seeders\CurrenciesTableDevelopmentSeeder; use Database\Seeders\CurrencyRatesTableDevelopmentSeeder; use Database\Seeders\DummyDataSeeder; +use Database\Seeders\QAWorkFlowSeeder; use Database\Seeders\SegmentConstantsTableDevelopmentSeeder; use Database\Seeders\SegmentsTableDevelopmentSeeder; use Database\Seeders\ServiceTypesTableDevelopmentSeeder; @@ -20,7 +21,7 @@ class DatabaseSeeder extends Seeder */ public function run() { - DB::beginTransaction(); + DB::beginTransaction(); //General $this->call(CountriesTableSeeder::class); @@ -49,7 +50,7 @@ class DatabaseSeeder extends Seeder } // Admin Work Flow - // $this->call(QAWorkFlow2Seeder::class); + // $this->call(QAWorkFlowSeeder::class); // DB::commit(); } diff --git a/database/seeds/QAWorkFlow2Seeder.php b/database/seeds/QAWorkFlowSeeder.php similarity index 93% rename from database/seeds/QAWorkFlow2Seeder.php rename to database/seeds/QAWorkFlowSeeder.php index 0aace36b..47fadbed 100644 --- a/database/seeds/QAWorkFlow2Seeder.php +++ b/database/seeds/QAWorkFlowSeeder.php @@ -1,13 +1,21 @@ 'Yes', 'value' => 'start_work', 'next_question_number' => 'start_work'], ['display_text' => 'No', 'value' => 'no_work', 'next_question_number' => 'no_work'], ], + 'group' => '', ], [ 'question_number' => 'start_work', @@ -39,6 +48,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Approve PO', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], ['display_text' => 'Fill PO', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], ], + 'group' => '', ], [ 'question_number' => 'no_work', @@ -46,6 +56,7 @@ class QAWorkFlow2Seeder extends Seeder 'question_type' => QAType::DEFAULT, 'is_start' => false, 'is_end' => true, + 'group' => '', ], [ 'question_number' => '1688', @@ -58,6 +69,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Login Issue', 'value' => '1688_login_issue', 'btn_color' => 'warning', 'next_question_number' => '1688_login_issue'], ['display_text' => 'Login Successful', 'value' => '1688_login_successful', 'next_question_number' => '1688_login_successful'], ], + 'group' => '1688', ], [ 'question_number' => '1688_login_issue', @@ -71,6 +83,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Others', 'value' => '1688_login_issue_others', 'next_question_number' => '1688_login_issue_others'], ['display_text' => 'Order Cancelled', 'value' => '1688_refund_request', 'next_question_number' => '1688_refund_request'], ], + 'group' => '1688', ], [ 'question_number' => '1688_login_successful', @@ -83,6 +96,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => "Can't verify order?", 'value' => '1688_login_successful_cannot_verify', 'next_question_number' => '1688_login_successful_cannot_verify'], ['display_text' => 'Order Verified', 'value' => '1688_order_verify', 'next_question_number' => '1688_order_verify'], ], + 'group' => '1688', ], [ 'question_number' => '1688_login_successful_cannot_verify', @@ -97,6 +111,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Customer did not verify 1688 account', 'value' => 'Customer did not verify 1688 account', 'next_question_number' => '1688_issue_submit'], ['display_text' => 'WorldFirst account linked another account', 'value' => 'WorldFirst account linked another account', 'next_question_number' => '1688_issue_submit'], ], + 'group' => '1688', ], [ 'question_number' => '1688_login_successful_cannot_verify_others', @@ -105,6 +120,7 @@ class QAWorkFlow2Seeder extends Seeder 'next_nested_question' => '1688_issue_submit', 'is_start' => false, 'is_end' => false, + 'group' => '1688', ], [ 'question_number' => '1688_order_verify', @@ -118,6 +134,7 @@ class QAWorkFlow2Seeder extends Seeder 'answer_options' => [ ['display_text' => 'Yes', 'value' => '1688_order_verification', 'next_question_number' => '1688_order_verification'], ], + 'group' => '1688', ], [ 'question_number' => '1688_order_verification', @@ -129,6 +146,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Yes', 'value' => '1688_proceed_order', 'next_question_number' => '1688_proceed_order'], ['display_text' => 'No', 'value' => '1688_insufficient_order', 'next_question_number' => '1688_insufficient_order'], ], + 'group' => '1688', ], [ 'question_number' => '1688_proceed_order', @@ -140,6 +158,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Yes', 'value' => '1688_submit', 'next_question_number' => '1688_submit'], ['display_text' => 'Got Issue', 'value' => '1688_proceed_order_issue', 'next_question_number' => '1688_proceed_order_issue'], ], + 'group' => '1688', ], [ 'question_number' => '1688_proceed_order_issue', @@ -154,6 +173,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'No CrossBoarder', 'value' => 'No CrossBoarder', 'next_question_number' => '1688_issue_submit'], ['display_text' => 'AngPau', 'value' => 'AngPau', 'next_question_number' => '1688_issue_submit'], ], + 'group' => '1688', ], [ 'question_number' => '1688_proceed_order_issue_others', @@ -162,6 +182,7 @@ class QAWorkFlow2Seeder extends Seeder 'next_nested_question' => '1688_issue_submit', 'is_start' => false, 'is_end' => false, + 'group' => '1688', ], [ 'question_number' => '1688_login_issue_others', @@ -170,6 +191,7 @@ class QAWorkFlow2Seeder extends Seeder 'next_nested_question' => '1688_issue_submit', 'is_start' => false, 'is_end' => false, + 'group' => '1688', ], [ 'question_number' => '1688_submit', @@ -178,6 +200,7 @@ class QAWorkFlow2Seeder extends Seeder 'next_nested_question' => '1688_issue_submit', 'is_start' => false, 'is_end' => false, + 'group' => '1688', ], [ 'question_number' => '1688_issue_submit', @@ -189,6 +212,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], ['display_text' => 'Next Order', 'value' => '1688', 'next_question_number' => '1688'], ], + 'group' => '1688', ], [ 'question_number' => '1688_refund_request', @@ -200,6 +224,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], ['display_text' => 'Next Order', 'value' => '1688', 'next_question_number' => '1688'], ], + 'group' => '1688', ], [ 'question_number' => 'approve_po', @@ -213,6 +238,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Edit PO', 'value' => 'approve_po_edit', 'next_question_number' => 'approve_po_edit'], ['display_text' => 'Reject', 'value' => 'approve_po_reject', 'next_question_number' => 'approve_po_reject'], ], + 'group' => 'approve_po', ], [ 'question_number' => 'fill_po', @@ -224,6 +250,7 @@ class QAWorkFlow2Seeder extends Seeder 'answer_options' => [ ['display_text' => 'Edit PO', 'value' => 'fill_po_edit', 'next_question_number' => 'fill_po_edit'], ], + 'group' => 'fill_po', ], [ 'question_number' => 'approve_po_filled', @@ -236,6 +263,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Next PO (Fill)', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], ['display_text' => 'Next PO (Approve)', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], ], + 'group' => 'approve_po', ], [ 'question_number' => 'approve_po_approved', @@ -248,6 +276,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Next PO (Fill)', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], ['display_text' => 'Next PO (Approve)', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], ], + 'group' => 'approve_po', ], [ 'question_number' => 'approve_po_edit', @@ -260,6 +289,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Issue?', 'value' => 'approve_po_edit_po_issue', 'next_question_number' => 'approve_po_edit_po_issue'], ['display_text' => 'Done', 'value' => 'approve_po_filled', 'next_question_number' => 'approve_po_filled'], ], + 'group' => 'approve_po', ], [ 'question_number' => 'approve_po_reject', @@ -273,6 +303,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], ['display_text' => 'Next Order', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], ], + 'group' => 'approve_po', ], [ 'question_number' => 'approve_po_reject_others', @@ -281,6 +312,7 @@ class QAWorkFlow2Seeder extends Seeder 'next_nested_question' => 'approve_po_reject_others_complete', 'is_start' => false, 'is_end' => false, + 'group' => 'approve_po', ], [ 'question_number' => 'approve_po_reject_others_complete', @@ -293,6 +325,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Next PO (Fill)', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], ['display_text' => 'Next PO (Approve)', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], ], + 'group' => 'approve_po', ], [ 'question_number' => 'approve_po_edit_po_issue', @@ -304,6 +337,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Sensitive Goods', 'value' => 'Sensitive Goods', 'next_question_number' => 'fill_po_issue_submit'], ['display_text' => 'Others', 'value' => 'fill_po_issue_others', 'next_question_number' => 'fill_po_issue_others'], ], + 'group' => 'approve_po', ], [ 'question_number' => 'fill_po_issue_others', @@ -312,6 +346,7 @@ class QAWorkFlow2Seeder extends Seeder 'next_nested_question' => 'fill_po_issue_submit', 'is_start' => false, 'is_end' => false, + 'group' => 'fill_po', ], [ 'question_number' => 'fill_po_issue_submit', @@ -324,6 +359,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Next PO (Fill)', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], ['display_text' => 'Next PO (Approve)', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], ], + 'group' => 'fill_po', ], [ 'question_number' => 'stop_working', @@ -331,6 +367,7 @@ class QAWorkFlow2Seeder extends Seeder 'question_type' => QAType::DEFAULT, 'is_start' => false, 'is_end' => true, + 'group' => '', ], [ 'question_number' => '1688_insufficient_order', @@ -343,6 +380,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Underpaid Order', 'value' => 'underpaird_order_2', 'next_question_number' => ''], ['display_text' => 'Overpaid Order', 'value' => 'underpaird_order_2', 'next_question_number' => ''], ], + 'group' => '1688', ], [ 'question_number' => 'fill_po_edit', @@ -355,6 +393,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Issue?', 'value' => 'fill_po_edit_issue', 'next_question_number' => 'fill_po_edit_issue'], ['display_text' => 'Done', 'value' => 'fill_po_edit_filled', 'next_question_number' => 'fill_po_edit_filled'], ], + 'group' => 'fill_po', ], [ 'question_number' => 'fill_po_edit_issue', @@ -366,6 +405,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Sensitive Goods', 'value' => 'Sensitive Goods', 'next_question_number' => 'fill_po_issue_submit'], ['display_text' => 'Others', 'value' => 'fill_po_issue_others', 'next_question_number' => 'fill_po_issue_others'], ], + 'group' => 'fill_po', ], [ 'question_number' => 'fill_po_edit_filled', @@ -378,6 +418,7 @@ class QAWorkFlow2Seeder extends Seeder ['display_text' => 'Next PO (Fill)', 'value' => 'fill_po', 'next_question_number' => 'fill_po'], ['display_text' => 'Next PO (Approve)', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], ], + 'group' => 'fill_po', ], ] ], @@ -399,6 +440,9 @@ class QAWorkFlow2Seeder extends Seeder } $question->question_type = $questionData['question_type']; $question->questionnaire_set_id = $questionnaireSet->id; + if (isset($questionData['group']) && $questionData['group'] != '') { + $question->group = $questionData['group']; + } if (isset($questionData['next_nested_question'])) { $question->next_nested_question = $questionData['next_nested_question']; diff --git a/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue b/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue new file mode 100644 index 00000000..a5293588 --- /dev/null +++ b/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue @@ -0,0 +1,144 @@ + + + diff --git a/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerInnerComponent.vue b/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerInnerComponent.vue new file mode 100644 index 00000000..fc3ca949 --- /dev/null +++ b/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerInnerComponent.vue @@ -0,0 +1,40 @@ + + diff --git a/resources/assets/vue/components/admin-workflow/sections/QuestionAnswerSectionComponent.vue b/resources/assets/vue/components/admin-workflow/sections/QuestionAnswerSectionComponent.vue new file mode 100644 index 00000000..095d7492 --- /dev/null +++ b/resources/assets/vue/components/admin-workflow/sections/QuestionAnswerSectionComponent.vue @@ -0,0 +1,165 @@ + + + + diff --git a/resources/assets/vue/components/general/elements/OpenLinkInNewTabComponent.vue b/resources/assets/vue/components/general/elements/OpenLinkInNewTabComponent.vue index 145dd0f7..b7a92111 100644 --- a/resources/assets/vue/components/general/elements/OpenLinkInNewTabComponent.vue +++ b/resources/assets/vue/components/general/elements/OpenLinkInNewTabComponent.vue @@ -33,6 +33,10 @@ type: Boolean, default: true }, + onClick: { + type: Function, + default: null + } }, data() { return { @@ -75,6 +79,9 @@ }); }, handleClick(){ + if(this.onClick){ + this.url = this.onClick(); + } this.isDownloading = true; if(window.LARAVEL_VAPOR_ENABLED){ this.submit(this.url, 'get', this.section, false, false); diff --git a/resources/assets/vue/vuex/modules/loadRequestQueue.js b/resources/assets/vue/vuex/modules/loadRequestQueue.js index 3bb574c5..2bd794aa 100644 --- a/resources/assets/vue/vuex/modules/loadRequestQueue.js +++ b/resources/assets/vue/vuex/modules/loadRequestQueue.js @@ -14,8 +14,15 @@ export default { getListDetails: (state) => (name) => { return state.queue.find(list => list.name === name); }, + // getListData: (state, getters) => (name) => { + // return (!getters.isInCompleteQueue()) ? state.queue.find(list => list.name === name).data : []; + // }, getListData: (state, getters) => (name) => { - return (!getters.isInCompleteQueue()) ? state.queue.find(list => list.name === name).data : []; + if (!getters.isInCompleteQueue(name)) { + const list = state.queue.find(list => list.name === name); + return list ? list.data : []; + } + return []; }, getSelectableList: (state, getters) => (name) => { let list = state.queue.find(list => list.name === name); @@ -68,4 +75,4 @@ export default { store.commit('SET_LIST_COMPLETE', {name: payload.name, data: payload.data}); } } -} \ No newline at end of file +} diff --git a/resources/views/pages/admin-workflow/list-workflow.blade.php b/resources/views/pages/admin-workflow/list-workflow.blade.php new file mode 100644 index 00000000..d7f54663 --- /dev/null +++ b/resources/views/pages/admin-workflow/list-workflow.blade.php @@ -0,0 +1,14 @@ +@extends('layouts.base_portal') +@section('inner_content') + + +
+
+ +
+
+@endsection diff --git a/routes/admin_workflow.php b/routes/admin_workflow.php index 8c2f535d..13a1f5d7 100644 --- a/routes/admin_workflow.php +++ b/routes/admin_workflow.php @@ -5,7 +5,7 @@ use App\Classes\Modules\Accounts\Services\CreatesKeyValuePair; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\TransactionType; use App\Classes\ValueObjects\Constants\BookingAttributeNames; -use App\Http\Controllers\AdminWorkflow\AdminWorkflowBaseController; +use App\Http\Controllers\Questionnaires\AdminWorkflowBaseController; use App\Http\Resources\BookingResource; use App\Models\Booking; use App\Models\KeyValuePair; @@ -44,12 +44,10 @@ if (!function_exists('markedProcessing')) { } } - Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'AdminWorkFlow'], function () { - +Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'Questionnaires'], function () { Route::get('/fetch-oldest-order', [AdminWorkflowBaseController::class, 'fetchOldestOrder'])->name('fetch_oldest_order'); - Route::get('/fetch-pending-approved-po', function () { $excludedBookingIds = KeyValuePair::where('owner_type', 'App\Models\Booking') ->where(function ($query) { diff --git a/routes/questionnaires.php b/routes/questionnaires.php index e3749d81..73b1f897 100644 --- a/routes/questionnaires.php +++ b/routes/questionnaires.php @@ -3,7 +3,12 @@ use Illuminate\Support\Facades\Route; Route::group(['prefix' => 'questionnaires', 'as' => 'questionnaires.', 'namespace' => 'Questionnaires'], function () { + Route::get('/list', 'ListQuestionnaireSetController@list')->name('list'); Route::get('/{set_id}', 'FetchQuestionV1AdminWFController@fetch')->name('first.question'); Route::post('/question', 'UpdateNextQuestionV1AdminWFController@fetch')->name('next.question'); - Route::get('/list', 'ListQuestionsAnswersQAController@list')->name('list.questions.answers'); + + Route::group(['prefix' => 'qa', 'as' => 'qa.'], function () { + Route::get('/list', 'ListQuestionsAnswersController@list')->name('list'); + Route::get('/export', '\\App\\Http\\Controllers\\Exports\\ExportQuestionsAnswersController@export')->name('export'); + }); }); diff --git a/routes/web.php b/routes/web.php index a7881fdc..cbb12f93 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1342,4 +1342,9 @@ Route::get('/site.webmanifest', function () { Route::get('/admin-workflow', function () { return view('pages.dashboards.admin_work_flow'); -})->name('admin-work-flow'); +})->name('admin-workflow'); + + +Route::get('/admin-workflow/list', function () { + return view('pages.admin-workflow.list-workflow'); +})->name('admin-workflow.list'); From db7d3dd3ae86252800fa21e4cfd0dfb439ad7d04 Mon Sep 17 00:00:00 2001 From: Dillon Ngo Date: Fri, 3 Jan 2025 03:06:55 +0800 Subject: [PATCH 13/54] Admin Workflow - New UI for Admin to go through questions and answers (update) --- .../General/Eloquent/Filters/IdAfter.php | 4 +- database/seeds/QAWorkFlowSeeder.php | 2 +- .../elements/QuestionAnswerComponent.vue | 5 ++- .../elements/QuestionAnswerInnerComponent.vue | 43 ++++++++++++++----- .../QuestionAnswerSectionComponent.vue | 4 +- 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/app/Classes/General/Eloquent/Filters/IdAfter.php b/app/Classes/General/Eloquent/Filters/IdAfter.php index 55959e75..e1c91a46 100644 --- a/app/Classes/General/Eloquent/Filters/IdAfter.php +++ b/app/Classes/General/Eloquent/Filters/IdAfter.php @@ -26,10 +26,10 @@ class IdAfter implements Filter if ($nextId) { return $builder->where('id', '>', $id) - ->where('id', '<', $nextId); + ->where('id', '<', $nextId)->withTrashed(); } - return $builder->where('id', '>', $id); + return $builder->where('id', '>', $id)->withTrashed(); } } diff --git a/database/seeds/QAWorkFlowSeeder.php b/database/seeds/QAWorkFlowSeeder.php index 47fadbed..3fce1e65 100644 --- a/database/seeds/QAWorkFlowSeeder.php +++ b/database/seeds/QAWorkFlowSeeder.php @@ -298,7 +298,7 @@ class QAWorkFlowSeeder extends Seeder 'is_start' => false, 'is_end' => false, 'answer_options' => [ - ['display_text' => 'Sensitive Goods', 'value' => 'Sensitive Goods'], + ['display_text' => 'Sensitive Goods', 'value' => 'Sensitive Goods', 'next_question_number' => 'approve_po_reject_others_complete'], ['display_text' => 'Others', 'value' => 'approve_po_reject_others', 'next_question_number' => 'approve_po_reject_others'], ['display_text' => 'Stop Working', 'value' => 'stop_working', 'next_question_number' => 'stop_working'], ['display_text' => 'Next Order', 'value' => 'approve_po', 'next_question_number' => 'approve_po'], diff --git a/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue b/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue index a5293588..e10b4223 100644 --- a/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue +++ b/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue @@ -50,10 +50,11 @@
Id
Question Text
+
Order/Booking/Transfer
Answer Text
Answer Value
+
Documents Uploaded
Time(seconds)
-
Order/Booking/Transfer
@@ -78,6 +79,7 @@ expanded: false, options: { 'order_by': {column: 'created_at', DESC: false}, + 'per_page': 20, }, showExpandedIcon: false, loading: true @@ -108,7 +110,6 @@ created(){ this.options['reference'] = this.item.reference; this.options['id_after'] = [this.item.id, this.item.question_groups]; - }, methods: { expand(){ diff --git a/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerInnerComponent.vue b/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerInnerComponent.vue index fc3ca949..155ca243 100644 --- a/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerInnerComponent.vue +++ b/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerInnerComponent.vue @@ -2,23 +2,44 @@
{{ item.id }}
{{ item.question_title }}
-
{{ item.answer }}
-
{{ parsedAnswer }}
-
{{ item.time }}
-
- - - +
{{ parsedAnswer === 'go_back' ? '' : item.answer }}
+
{{ parsedAnswer }}
+
+
+
+ + + +
+
+
{{ item.time }}
+ +
+ diff --git a/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue b/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue index e10b4223..f8a52d20 100644 --- a/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue +++ b/resources/assets/vue/components/admin-workflow/elements/QuestionAnswerComponent.vue @@ -1,7 +1,7 @@