mirror of
https://gitlab.com/CIEFWorldwideSdnBhd/exchange-2.0.git
synced 2026-09-01 10:54:00 +00:00
Merge branch 'admin-1688-work-flow-aqqill' of gitlab.com:CIEFWorldwideSdnBhd/exchange-2.0 into development
This commit is contained in:
@@ -12,6 +12,14 @@ class Remark extends AbstractModel
|
||||
|
||||
protected $table = 'remarks';
|
||||
|
||||
protected $fillable = [
|
||||
'owner_id',
|
||||
'owner_type',
|
||||
'commenter_id',
|
||||
'content',
|
||||
'type'
|
||||
];
|
||||
|
||||
public function owner(): morphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class WorkflowTimestamp extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'workflow_timestamps';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'current_node',
|
||||
'next_node',
|
||||
'node',
|
||||
'seconds',
|
||||
'userId',
|
||||
'session_id'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'timestamp' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the user that owns the workflow timestamp.
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'userId');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateWorkflowTimestampsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('workflow_timestamps', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('current_node');
|
||||
$table->string('next_node');
|
||||
$table->integer('seconds');
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->string('session_id');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('workflow_timestamps');
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@
|
||||
},
|
||||
methods: {
|
||||
submitForm() {
|
||||
this.$emit('remark-submitted', this.parameters.content);
|
||||
this.submit(this.data ? this.route('api.remark.update', this.data.id) : this.route('api.remark.create', this.id), this.data ? 'put' : 'post', this.section, true, true);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,665 @@
|
||||
<template>
|
||||
<div class="row justify-content-center align-items-center text-center" style="min-height: 80vh;">
|
||||
<div class="col">
|
||||
<h1 class="m-b-50" :class="{ 'text-success': timer }">{{ formattedTime }}</h1>
|
||||
<div v-if="isLoading" class="row">
|
||||
<div class="col">
|
||||
<loading-component></loading-component>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row">
|
||||
<div class="col">
|
||||
<div v-if="currentQuestion && Object.keys(currentQuestion).length && !apiFailed">
|
||||
<h2 v-if="currentQuestion.text">{{ currentQuestion.text }}</h2>
|
||||
|
||||
<!-- <p>currentQuestionId - {{ currentQuestionId }}</p> -->
|
||||
<!-- <p>currentOrder - {{ currentOrder? currentOrder.id : 'null' }}</p> -->
|
||||
|
||||
<div v-if="currentQuestionId === '1688'">
|
||||
<h1>Login Information</h1>
|
||||
<div v-if="externalApiResponse.data"
|
||||
class="row bg-master-light m-t-15 m-b-15 rounded padding-30 justify-content-center">
|
||||
<div class="col-auto text-left">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<h3><span class="bold d-inline-block m-r-15">ORDER Marking: </span></h3>
|
||||
</td>
|
||||
<td>
|
||||
<h3><a :href="route('booking.details', externalApiResponse.data.booking_marking)"
|
||||
target="_blank">{{ externalApiResponse.data.booking_marking
|
||||
}}</a></h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h3><span class="bold d-inline-block m-r-15">1688 LOGIN ID/EMAIL/PHONE:
|
||||
</span></h3>
|
||||
</td>
|
||||
<td>
|
||||
<h3>{{ externalApiResponse.data.account_no }}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h3><span class="bold d-inline-block m-r-15">1688 LOGIN PASSWORD:
|
||||
</span></h3>
|
||||
</td>
|
||||
<td>
|
||||
<h3>{{ externalApiResponse.data.holder_name }}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h3><span class="bold d-inline-block m-r-15">ALIPAY 6-DIGIT PAYMENT PIN:
|
||||
</span></h3>
|
||||
</td>
|
||||
<td>
|
||||
<h3>{{ externalApiResponse.data.pin }}</h3>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row bg-danger-light m-t-15 m-b-15 rounded padding-30">
|
||||
<div class="col">
|
||||
<h1 class="text-white">Api Failed. Please contact Tech Support</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentQuestionId === 'login_successful'">
|
||||
<h3>
|
||||
<span class="bold d-inline-block m-r-15">ORDER Marking: </span>
|
||||
<a :href="route('booking.details', externalApiResponse.data.booking.marking)"
|
||||
target="_blank">
|
||||
{{ externalApiResponse.data.booking.marking }}
|
||||
</a>
|
||||
</h3>
|
||||
<div v-if="externalApiResponse.data" class="row bg-master-light m-t-15 m-b-15 rounded padding-30 justify-content-center">
|
||||
<div class="col-auto text-left">
|
||||
<h3>
|
||||
<span class="bold d-inline-block m-r-15">ORDER Reference(s): </span>
|
||||
</h3>
|
||||
<div v-for="(attribute, index) in externalApiResponse.data.booking_attributes"
|
||||
:key="index">
|
||||
<h2>
|
||||
{{ index + 1 + '. #' + attribute.value }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row bg-danger-light m-t-15 m-b-15 rounded padding-30">
|
||||
<div class="col">
|
||||
<h1 class="text-white">Api Failed. Please contact Tech Support</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentQuestionId === 'order_verify'">
|
||||
<h3>
|
||||
<span class="bold d-inline-block m-r-15">ORDER Marking: </span>
|
||||
<a :href="route('booking.details', externalApiResponse.data.booking.marking)"
|
||||
target="_blank">
|
||||
{{ externalApiResponse.data.booking.marking }}
|
||||
</a>
|
||||
</h3>
|
||||
<div v-if="externalApiResponse.data" class="row bg-master-light m-t-15 m-b-15 rounded padding-30 justify-content-center">
|
||||
<div class="col-auto text-left">
|
||||
<h3>
|
||||
<span class="bold d-inline-block m-r-15">ORDER Reference(s): </span>
|
||||
</h3>
|
||||
<div v-for="(attribute, index) in externalApiResponse.data.booking_attributes"
|
||||
:key="index">
|
||||
<h2>
|
||||
{{ index + 1 + '. #' + attribute.value }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-auto text-center">
|
||||
<h4>
|
||||
Total CNY: <!-- {{ attribute.cost }} -->
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row bg-danger-light m-t-15 m-b-15 rounded padding-30">
|
||||
<div class="col">
|
||||
<h1 class="text-white">Api Failed. Please contact Tech Support</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentQuestionId === '1688_submit'">
|
||||
<div v-if="externalApiResponse.data" class="row">
|
||||
<div class="col">
|
||||
<div class="row b-a b-primary padding-30 bg-white m-t-25">
|
||||
<div class="col">
|
||||
<h2>
|
||||
Upload the English PO
|
||||
</h2>
|
||||
<file-upload-component :data="externalApiResponse.data" section="section"></file-upload-component>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h2>
|
||||
Upload the China PO
|
||||
</h2>
|
||||
<file-upload-component :data="externalApiResponse.data" section="section"></file-upload-component>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h2>
|
||||
Upload Bank Slip
|
||||
</h2>
|
||||
<file-upload-component :data="externalApiResponse.data" section="section"></file-upload-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row bg-danger-light m-t-15 m-b-15 rounded padding-30">
|
||||
<div class="col">
|
||||
<h1 class="text-white">Api Failed. Please contact Tech Support</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentQuestionId === 'approve_po'">
|
||||
<div v-if="externalApiResponse.data" class="row">
|
||||
<div class="col">
|
||||
<h3>
|
||||
<span class="bold d-inline-block m-r-15">ORDER Marking: </span>
|
||||
<a :href="route('booking.details', externalApiResponse.data.marking)"
|
||||
target="_blank">{{ externalApiResponse.data.marking }}</a>
|
||||
</h3>
|
||||
<div class="row b-a b-primary padding-30 bg-white m-t-25">
|
||||
<div class="col">
|
||||
<purchase-order-form-component :data="externalApiResponse.data"
|
||||
:section="section"></purchase-order-form-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row bg-danger-light m-t-15 m-b-15 rounded padding-30">
|
||||
<div class="col">
|
||||
<h1 class="text-white">Api Failed. Please contact Tech Support</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentQuestionId === 'fill_po'">
|
||||
<div v-if="externalApiResponse.data" class="row">
|
||||
<div class="col">
|
||||
<h3>
|
||||
<span class="bold d-inline-block m-r-15">ORDER Marking: </span>
|
||||
<a :href="route('booking.details', externalApiResponse.data.marking)"
|
||||
target="_blank">{{ externalApiResponse.data.marking }}</a>
|
||||
</h3>
|
||||
<div class="row b-a b-primary padding-30 bg-white m-t-25">
|
||||
<div class="col">
|
||||
<purchase-order-form-component :data="externalApiResponse.data"
|
||||
section="section"></purchase-order-form-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row bg-danger-light m-t-15 m-b-15 rounded padding-30">
|
||||
<div class="col">
|
||||
<h1 class="text-white">Api Failed. Please contact Tech Support</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentQuestionId === 'edit_po'">
|
||||
<div v-if="externalApiResponse.data" class="row">
|
||||
<div class="col">
|
||||
<h3>
|
||||
<span class="bold d-inline-block m-r-15">ORDER Marking: </span>
|
||||
<a :href="route('booking.details', externalApiResponse.data.marking)"
|
||||
target="_blank">{{ externalApiResponse.data.marking }}</a>
|
||||
</h3>
|
||||
<div class="row b-a b-primary padding-30 bg-white m-t-25">
|
||||
<div class="col">
|
||||
<purchase-order-form-component :data="externalApiResponse.data"
|
||||
section="section"></purchase-order-form-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row bg-danger-light m-t-15 m-b-15 rounded padding-30">
|
||||
<div class="col">
|
||||
<h1 class="text-white">Api Failed. Please contact Tech Support</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentQuestionId === '1688_issue_others' || currentQuestionId === 'po_others'">
|
||||
<div v-if="externalApiResponse.data" class="row">
|
||||
<div class="col">
|
||||
<div class="row b-a b-primary padding-30 bg-white m-t-25">
|
||||
<div class="col">
|
||||
<h2>
|
||||
What issues did you encounter?
|
||||
</h2>
|
||||
<div class="m-t-25">
|
||||
<remark-comment-form-component @remark-submitted="handleRemarkSubmitted" :data="externalApiResponse.data" :id="externalApiResponse.data.booking_id" :section="section" module_type="Booking"></remark-comment-form-component>
|
||||
</div>
|
||||
<div class="m-t-25">
|
||||
<file-upload-component v-model="files" :value="value" v-on:input="$emit('input', $event)"></file-upload-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="row bg-danger-light m-t-15 m-b-15 rounded padding-30">
|
||||
<div class="col">
|
||||
<h1 class="text-white">Api Failed. Please contact Tech Support</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" v-if="currentQuestion.answers">
|
||||
<div class="col">
|
||||
<div class="w-100 d-block text-center">
|
||||
<button @click="goBack()"
|
||||
v-if="questionIds.length && currentQuestion.show_back_btn != false"
|
||||
class="btn btn-lg btn-default b-a b-grey d-inline-block rounded fs-20"
|
||||
style="margin: 15px; padding: 15px 40px; min-width: 210px;">
|
||||
Go Back
|
||||
</button>
|
||||
<button v-for="(answer, index) in currentQuestion.answers" :key="index"
|
||||
@click="selectAnswer(answer.next)" :data-attr-next-step="answer.next"
|
||||
class="btn btn-lg btn-primary d-inline-block rounded fs-20"
|
||||
:class="getbuttonClass(answer.btn_color)"
|
||||
style="margin: 15px; padding: 15px 40px; min-width: 210px;">
|
||||
{{ answer.text }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="row bg-danger-light m-t-15 m-b-15 rounded padding-30">
|
||||
<div class="col">
|
||||
<h1 class="text-white">
|
||||
"{{ currentQuestionId }}" {{ apiFailed ? 'api Failed' : "is empty" }}
|
||||
. Please contact Tech Support</h1>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-primary d-inline-block rounded fs-20"
|
||||
style="margin: 15px; padding: 15px 40px; min-width: 210px;"
|
||||
@click="selectAnswer('refresh_page')">Refresh Page</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
section: 'initial_section',
|
||||
questions: {
|
||||
"node_0": {
|
||||
"text": "Are you ready to work today?",
|
||||
"answers": [
|
||||
{ "text": "Yes", "next": "start_work" },
|
||||
{ "text": "No", "next": "no_work" }
|
||||
]
|
||||
},
|
||||
"start_work": {
|
||||
"text": "What will you work on?",
|
||||
"answers": [
|
||||
{ "text": "1688", "next": "1688" },
|
||||
{ "text": "Approve PO", "next": "approve_po" },
|
||||
{ "text": "Fill PO", "next": "fill_po" }
|
||||
]
|
||||
},
|
||||
"no_work": {
|
||||
"text": "Come back when you are ready to work",
|
||||
"answers": [
|
||||
{ "text": "Refresh Page", "next": "refresh_page" }
|
||||
]
|
||||
},
|
||||
"1688": {
|
||||
"load_api": this.route("api.admin_work_flow.fetch_oldest_order"),
|
||||
"answers": [
|
||||
{ "text": "Login Issue", "next": "login_issue", 'btn_color': "warning" },
|
||||
{ "text": "Login Successful", "next": "login_successful" }
|
||||
],
|
||||
},
|
||||
"login_issue": {
|
||||
"text": "Login Issues",
|
||||
"answers": [
|
||||
{ "text": "Need TAC", "next": "Need Tac" },
|
||||
{ "text": "Wrong login details", "next": "Wrong Login Details" },
|
||||
{ "text": "Others", "next": "1688_issue_others" },
|
||||
{ "text": "Order Cancelled", "next": "refund_request" }
|
||||
]
|
||||
},
|
||||
"login_successful": {
|
||||
"load_api": 'fetch_model_attributes',
|
||||
"answers": [
|
||||
{ "text": "Can't verify order?", "next": "1688_issue_order" },
|
||||
{ "text": "Order Verified", "next": "order_verify" },
|
||||
]
|
||||
},
|
||||
"1688_issue_order": {
|
||||
"answers": [
|
||||
{ "text": "Amount not found", "next": "Amount not found" },
|
||||
{ "text": "Plus Member", "next": "Plus Member" },
|
||||
{ "text": "Others", "next": "Others" },
|
||||
{ "text": "Customer did not verify 1688 account", "next": "Customer did not verify 1688 account" },
|
||||
{ "text": "WorldFirst account linked another account", "next": "WorldFirst account linked another account" },
|
||||
]
|
||||
},
|
||||
"order_verify": {
|
||||
// fetch order amount
|
||||
"load_api": 'fetch_model_attributes',
|
||||
"answers": [
|
||||
{ "text": "Yes", "next": "order_verification" }
|
||||
]
|
||||
},
|
||||
"order_verification": {
|
||||
"text": "Are You Sure this is the correct amount?",
|
||||
"answers": [
|
||||
{ "text": "No", "next": "insufficient_order" },
|
||||
{ "text": "Yes", "next": "proceed_order" }
|
||||
]
|
||||
},
|
||||
"proceed_order": {
|
||||
"text": "Please proceed the order on 1688",
|
||||
"answers": [
|
||||
{ "text": "Got Issue", "next": "1688_order_issue" },
|
||||
{ "text": "Done", "next": "1688_submit" }
|
||||
]
|
||||
},
|
||||
"1688_order_issue": {
|
||||
"answers": [
|
||||
{ "text": "Wrong Pin Number", "next": "Wrong Pin Number" },
|
||||
{ "text": "Not Enough Stock", "next": "Not Enough Stock" },
|
||||
{ "text": "Others", "next": "Others" },
|
||||
{ "text": "No CrossBoarder", "next": "No CrossBoarder" },
|
||||
{ "text": "AngPau", "next": "AngPau" }
|
||||
]
|
||||
},
|
||||
"1688_issue_others": {
|
||||
"text": "Others",
|
||||
"answers": [
|
||||
{ "text": "Submit Issue", "next": "1688_issue_submit"}
|
||||
]
|
||||
},
|
||||
"1688_submit": {
|
||||
"answers": [
|
||||
{ "text": "Stop Working", "next": "stop_working"},
|
||||
{ "text": "Proceed to next order", "next": "1688"}
|
||||
]
|
||||
},
|
||||
"1688_issue_submit": {
|
||||
"text": "Issue has been submitted",
|
||||
"answers": [
|
||||
{ "text": "Stop Working", "next": "stop_working" },
|
||||
{ "text": "Next Order", "next": "1688" }
|
||||
]
|
||||
},
|
||||
"refund_request": {
|
||||
"text": "Refund Request sent",
|
||||
"answers": [
|
||||
{ "text": "Stop Working", "next": "stop_working" },
|
||||
{ "text": "Next PO", "next": "1688" }
|
||||
]
|
||||
},
|
||||
"approve_po": {
|
||||
// "text": "Order Number",
|
||||
"load_api": this.route("api.admin_work_flow.fetch_pending_approve_po"),
|
||||
"answers": [
|
||||
{ "text": "Approve", "next": "po_approved" },
|
||||
{ "text": "Edit PO", "next": "edit_po" },
|
||||
{ "text": "Reject", "next": "reject_po" }
|
||||
]
|
||||
},
|
||||
"fill_po": {
|
||||
// "text": "Order Number",
|
||||
"load_api": this.route("api.admin_work_flow.fetch_pending_fill_po"),
|
||||
"answers": [
|
||||
{ "text": "Edit PO", "next": "edit_po" },
|
||||
]
|
||||
},
|
||||
"po_filled": {
|
||||
"text": "PO Filled",
|
||||
"answers": [
|
||||
{ "text": "Stop Working", "next": "stop_working" },
|
||||
{ "text": "Next PO", "next": "approve_po" }
|
||||
]
|
||||
},
|
||||
"po_approved": {
|
||||
"text": "PO Approved",
|
||||
"answers": [
|
||||
{ "text": "Stop Working", "next": "stop_working" },
|
||||
{ "text": "Next PO", "next": "approve_po" }
|
||||
]
|
||||
},
|
||||
"edit_po": {
|
||||
"answers": [
|
||||
{ "text": "Issue?", "next": "issue_po"},
|
||||
{ "text": "Done", "next": "po_filled" },
|
||||
]
|
||||
},
|
||||
"po_others": {
|
||||
"text": "Others",
|
||||
"answers": [
|
||||
{ "text": "Submit Issue", "next": "issue_submit"}
|
||||
]
|
||||
},
|
||||
"reject_po": {
|
||||
"text": "PO Rejected",
|
||||
"answers": [
|
||||
{ "text": "Issue", "next": "issue_po" },
|
||||
]
|
||||
},
|
||||
"issue_po": {
|
||||
"answers": [
|
||||
{ "text": "Sensitive Goods", "next": "Sensitive Goods" },
|
||||
{ "text": "Others", "next": "PO Others" },
|
||||
// { "text": "Stop Working", "next": "stop_working" },
|
||||
// { "text": "Next PO", "next": "approve_po" }
|
||||
]
|
||||
},
|
||||
"issue_submit": {
|
||||
"text": "Issue has been submitted",
|
||||
"answers": [
|
||||
{ "text": "Stop Working", "next": "stop_working" },
|
||||
{ "text": "Next Order", "next": "approve_po" }
|
||||
]
|
||||
},
|
||||
"stop_working": {
|
||||
"text": "Thank you for your work!",
|
||||
"answers": [
|
||||
{ "text": "Refresh Page", "next": "refresh_page" }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
currentQuestionId: null,
|
||||
questionIds: [],
|
||||
isLoading: false,
|
||||
externalApiResponse: { data: null },
|
||||
timer: null,
|
||||
elapsedTime: 0,
|
||||
stepTime: 0,
|
||||
interval: 1000,
|
||||
sessionId: null,
|
||||
|
||||
currentOrder: null,
|
||||
apiFailed: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentQuestion() { return this.questions[this.currentQuestionId] || {}; },
|
||||
formattedTime() {
|
||||
const hours = String(Math.floor(this.elapsedTime / 3600)).padStart(2, '0');
|
||||
const minutes = String(Math.floor((this.elapsedTime % 3600) / 60)).padStart(2, '0');
|
||||
const seconds = String(this.elapsedTime % 60).padStart(2, '0');
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.currentQuestionId = 'node_0';
|
||||
if (this.sessionId == null) {
|
||||
this.sessionId = this.generateSessionId();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
startTimer() {
|
||||
if (!this.timer) {
|
||||
this.timer = setInterval(() => {
|
||||
this.elapsedTime++;
|
||||
this.stepTime++;
|
||||
}, this.interval);
|
||||
} else {
|
||||
console.log("time has already started");
|
||||
}
|
||||
},
|
||||
stopTimer() { clearInterval(this.timer); this.timer = null; },
|
||||
startWork() { this.startTimer(); },
|
||||
endWork() { this.stopTimer(); console.log("work is ended"); },
|
||||
handleRemarkSubmitted(remarkContent) {
|
||||
this.callApi(this.route('api.admin_work_flow.create_issue_remark'), 'post', 'section', {
|
||||
'booking_id': this.currentOrder.booking_id,
|
||||
'remark': remarkContent,
|
||||
'user_id': this.$store.getters.getUserId,
|
||||
}, false, false);
|
||||
|
||||
alert('Remark has been submitted');
|
||||
},
|
||||
|
||||
selectAnswer(nextQuestionId, goBack = false) {
|
||||
if (nextQuestionId === 'refresh_page') return window.location.reload();
|
||||
if (this.currentQuestionId === 'issue_po') {
|
||||
if (nextQuestionId === 'po_others') {
|
||||
this.callApi(this.route('api.admin_work_flow.create_po_issue_remark'), 'post', 'section', {
|
||||
'booking_id': this.currentOrder.id,
|
||||
'remark': 'PO Others',
|
||||
'user_id': this.$store.getters.getUserId,
|
||||
}, false, false);
|
||||
|
||||
} else {
|
||||
this.callApi(this.route('api.admin_work_flow.create_po_issue_remark'), 'post', 'section', {
|
||||
'booking_id': this.currentOrder.id,
|
||||
'remark': nextQuestionId,
|
||||
'user_id': this.$store.getters.getUserId,
|
||||
}, false, false);
|
||||
nextQuestionId = 'issue_submit';
|
||||
}
|
||||
}
|
||||
|
||||
if (['login_issue', '1688_issue_order', '1688_order_issue'].includes(this.currentQuestionId)) {
|
||||
if (nextQuestionId === 'refund_request') {
|
||||
this.callApi(this.route('api.admin_work_flow.create_issue_remark'), 'post', 'section', {
|
||||
'booking_id': this.currentOrder.booking_id,
|
||||
'remark': nextQuestionId,
|
||||
'user_id': this.$store.getters.getUserId,
|
||||
}, false, false);
|
||||
|
||||
} else if(nextQuestionId === '1688_issue_others') {
|
||||
// this.callApi(this.route('api.admin_work_flow.create_issue_remark'), 'post', 'section', {
|
||||
// 'booking_id': this.currentOrder.booking_id,
|
||||
// 'remark': nextQuestionId,
|
||||
// 'user_id': this.$store.getters.getUserId,
|
||||
// }, false, false);
|
||||
|
||||
} else {
|
||||
this.callApi(this.route('api.admin_work_flow.create_issue_remark'), 'post', 'section', {
|
||||
'booking_id': this.currentOrder.booking_id,
|
||||
'remark': nextQuestionId,
|
||||
'user_id': this.$store.getters.getUserId,
|
||||
}, false, false);
|
||||
nextQuestionId = '1688_issue_submit';
|
||||
}
|
||||
}
|
||||
if (!Object.keys(this.questions[nextQuestionId]).length) this.endWork();
|
||||
if (nextQuestionId === 'start_work') this.startWork();
|
||||
if (nextQuestionId === 'stop_working') this.stopTimer();
|
||||
|
||||
// exclude some node to click back
|
||||
if (!['node_0'].includes(this.currentQuestionId) && !goBack) {
|
||||
this.questionIds.push(this.currentQuestionId);
|
||||
this.callLogApi(this.currentQuestionId, nextQuestionId);
|
||||
}
|
||||
|
||||
if (!['node_0'].includes(this.currentQuestionId) && goBack) {
|
||||
this.callLogApi(this.currentQuestionId, 'goBack');
|
||||
}
|
||||
|
||||
if (this.questions[nextQuestionId]?.load_api) this.callApi(this.questions[nextQuestionId].load_api, 'get', 'section');
|
||||
this.currentQuestionId = nextQuestionId;
|
||||
},
|
||||
callLogApi(currentNode, nextNode) {
|
||||
// call api to recorrd timestamp
|
||||
var logBody = {
|
||||
'current_node': currentNode,
|
||||
'next_node': nextNode,
|
||||
'seconds': this.stepTime,
|
||||
'user_id': this.$store.getters.getUserId,
|
||||
'session_id': this.sessionId,
|
||||
};
|
||||
// this.callApi(this.route('api.admin_work_flow.add_workflow_timestamp'), 'post', 'section', logBody, false, false);
|
||||
this.stepTime = 0;
|
||||
},
|
||||
goBack() {
|
||||
var prevId = this.questionIds.pop();
|
||||
this.selectAnswer(prevId, true);
|
||||
},
|
||||
generateSessionId() {
|
||||
return 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
},
|
||||
callApi(url, method, section, parameters = null, showLoadingAnimation = true, storeResponseBody = true) {
|
||||
if (url == 'fetch_model_attributes') {
|
||||
url = this.route("api.admin_work_flow.fetch_model_attributes", this.currentOrder.booking_id)
|
||||
} else if (url == 'fetch_pending_approve_po') {
|
||||
url = this.route("api.admin_work_flow.fetch_pending_approve_po", this.currentOrder)
|
||||
}
|
||||
|
||||
if (showLoadingAnimation) this.isLoading = true;
|
||||
this.$store.dispatch('crudRequest', { endpoint: url, method, parameters: parameters })
|
||||
.then(response => response.json().then(data => ({ data, ok: response.ok, status: response.status })))
|
||||
.then(apiResponse => {
|
||||
if (storeResponseBody) {
|
||||
this.externalApiResponse = apiResponse.data;
|
||||
}
|
||||
|
||||
if (apiResponse.ok) {
|
||||
this.successHandler(apiResponse.data);
|
||||
} else {
|
||||
// Improved error handling
|
||||
this.handleError(apiResponse.data, apiResponse.status);
|
||||
}
|
||||
|
||||
if (showLoadingAnimation) {
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
if ([this.route("api.admin_work_flow.fetch_oldest_order"), this.route("api.admin_work_flow.fetch_pending_approve_po"), this.route("api.admin_work_flow.fetch_pending_fill_po")].includes(url)) {
|
||||
this.currentOrder = apiResponse.data.data
|
||||
}
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle network or other unexpected errors
|
||||
console.error('Unexpected error:', error);
|
||||
this.errorHandler({ message: 'An unexpected error occurred. Please try again later.' }, 500);
|
||||
this.apiFailed = true;
|
||||
if (showLoadingAnimation) {
|
||||
this.isLoading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
getbuttonClass(className) {
|
||||
return 'btn-' + className;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
@extends('layouts.base_portal')
|
||||
@section('inner_content')
|
||||
<admin-work-flow-section-component></admin-work-flow-section-component>
|
||||
@endsection
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
use App\Classes\Exceptions\RequestValidationException;
|
||||
use App\Classes\ValueObjects\Constants\ApprovalStatus;
|
||||
use App\Classes\ValueObjects\Constants\TransactionType;
|
||||
use App\Classes\ValueObjects\Constants\BookingAttributeNames;
|
||||
use App\Http\Resources\BookingResource;
|
||||
use App\Models\Booking;
|
||||
use App\Models\Remark;
|
||||
use App\Models\WorkflowTimestamp;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
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)
|
||||
{
|
||||
$response = ['message' => $message];
|
||||
|
||||
if ($data !== null) {
|
||||
$response['data'] = $data;
|
||||
}
|
||||
|
||||
return response()->json($response, $status);
|
||||
}
|
||||
|
||||
|
||||
Route::get('/fetch-oldest-order', function () {
|
||||
$booking = Booking::where('service_id', 4)
|
||||
->where('status', ApprovalStatus::APPROVED)
|
||||
->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_marking' => $booking->marking ?? null,
|
||||
'booking_id' => $booking->id ?? null,
|
||||
]) : jsonResponse(null, 'No booking found', 404);
|
||||
})->name('fetch_oldest_order');
|
||||
|
||||
|
||||
|
||||
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::get('/fetch-pending-approved-po', function () {
|
||||
$booking = Booking::with('transactions')
|
||||
->where('service_id', 4)
|
||||
->where('status', ApprovalStatus::APPROVED)
|
||||
->whereHas('transactions', fn ($query) => $query->where('type', TransactionType::PURCHASE_ORDER)->where('status', '<', ApprovalStatus::APPROVED))
|
||||
->first();
|
||||
|
||||
return $booking ? jsonResponse(new BookingResource($booking)) : jsonResponse(null, 'No booking found', 404);
|
||||
})->name('fetch_pending_approve_po');
|
||||
|
||||
|
||||
|
||||
Route::get('/fetch-pending-fill-po', function () {
|
||||
$booking = Booking::with('transactions')
|
||||
->where('service_id', 4)
|
||||
->where('status', ApprovalStatus::APPROVED)
|
||||
->whereDoesntHave('transactions', fn ($query) => $query->where('type', TransactionType::PURCHASE_ORDER))
|
||||
->first();
|
||||
|
||||
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('/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');
|
||||
});
|
||||
@@ -87,5 +87,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
|
||||
// require __DIR__ . '/rate.php';
|
||||
// require __DIR__ . '/receipt.php';
|
||||
|
||||
require __DIR__ . '/admin_work_flow.php';
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1217,3 +1217,6 @@ Route::get('check-duplicate-refunds', function () {
|
||||
echo '</table>';
|
||||
});
|
||||
|
||||
Route::get('/admin-work-flow', function () {
|
||||
return view('pages.dashboards.admin_work_flow');
|
||||
})->name('admin-work-flow');
|
||||
|
||||
Reference in New Issue
Block a user