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');