1688 work flow

This commit is contained in:
edmondlang
2024-07-23 22:58:36 +08:00
parent 56fddcea36
commit c340c9d4d8
4 changed files with 216 additions and 104 deletions
+8
View File
@@ -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();
@@ -18,7 +18,7 @@ class CreateWorkflowTimestampsTable extends Migration
$table->string('current_node');
$table->string('next_node');
$table->integer('seconds');
$table->unsignedBigInteger('userId');
$table->unsignedBigInteger('user_id');
$table->string('session_id');
$table->timestamps();
});
@@ -9,31 +9,56 @@
</div>
<div v-else class="row">
<div class="col">
<div v-if="currentQuestion && Object.keys(currentQuestion).length">
<div v-if="currentQuestion && Object.keys(currentQuestion).length && !apiFailed">
<h2 v-if="currentQuestion.text">{{ currentQuestion.text }}</h2>
<!-- <p>currentQuestionId - {{ currentQuestionId }}</p> -->
<!-- <p>currentOrder - {{ currentOrder }}</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">
<div class="col">
<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>
<h3>
<span class="bold d-inline-block m-r-15">1688 LOGIN ID/EMAIL/PHONE: </span>
{{ externalApiResponse.data.account_no }}
</h3>
<h3>
<span class="bold d-inline-block m-r-15">1688 LOGIN PASSWORD: </span>
{{ externalApiResponse.data.holder_name }}
</h3>
<h3>
<span class="bold d-inline-block m-r-15">ALIPAY 6-DIGIT PAYMENT PIN: </span>
{{ externalApiResponse.data.pin }}
</h3>
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">
@@ -43,25 +68,27 @@
</div>
</div>
<div v-if="currentQuestionId === 'login_successful'">
<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 :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">
<div class="col">
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">
<div v-for="(attribute, index) in externalApiResponse.data.booking_attributes"
:key="index">
<h2>
#{{ attribute.value }}
{{ index + 1 + '. #' + attribute.value }}
</h2>
</div>
</div>
</div>
</div>
<div v-else class="row bg-danger-light m-t-15 m-b-15 rounded padding-30">
<div class="col">
@@ -119,14 +146,16 @@
<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"
<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)"
@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>
@@ -138,8 +167,9 @@
<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 }}" is empty. Please contact Tech Support
</h1>
<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"
@@ -182,24 +212,24 @@ export default {
"1688": {
"load_api": this.route("api.admin_work_flow.fetch_oldest_order"),
"answers": [
{ "text": "Login Issue", "next": "login_issue" },
{ "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": "issue_submit" },
{ "text": "Wrong login details", "next": "issue_submit" },
{ "text": "Others", "next": "po_others" },
{ "text": "Order Cancelled", "next": "refund_request" }
{ "text": "Need TAC", "next": "Need TAC" },
{ "text": "Wrong login details", "next": "Wrong login details" },
{ "text": "Others", "next": "Others" },
{ "text": "Order Cancelled", "next": "Order Cancelled" }
]
},
"login_successful": {
"load_api": this.route("api.admin_work_flow.fetch_model_attributes"),
"load_api": 'fetch_model_attributes',
"answers": [
{ "text": "Can't verify order?", "next": "issue_order"},
{ "text": "Order Verified", "next": "order_verify"},
{ "text": "Can't verify order?", "next": "issue_order" },
{ "text": "Order Verified", "next": "order_verify" },
]
},
"issue_order": {
@@ -212,9 +242,10 @@ export default {
]
},
"order_verify": {
// fetch order amount
"load_api": this.route("api.admin_work_flow.fetch_model_attributes"),
"answers": [
{ "text": "Yes", "next": "order_verification"}
{ "text": "Yes", "next": "order_verification" }
]
},
"order_verification": {
@@ -336,6 +367,9 @@ export default {
stepTime: 0,
interval: 1000,
sessionId: null,
currentOrder: null,
apiFailed: false,
};
},
computed: {
@@ -367,18 +401,32 @@ export default {
stopTimer() { clearInterval(this.timer); this.timer = null; },
startWork() { this.startTimer(); },
endWork() { this.stopTimer(); console.log("work is ended"); },
selectAnswer(nextQuestionId) {
selectAnswer(nextQuestionId, goBack = false) {
if (nextQuestionId === 'refresh_page') return window.location.reload();
if (this.currentQuestionId === 'login_issue') {
this.callApi(this.route('api.admin_work_flow.create_1688_login_issue_remark'), 'post', 'section', {
'booking_id': this.currentOrder.booking_id,
'remark': nextQuestionId,
'user_id': this.$store.getters.getUserId,
}, false, false);
nextQuestionId = '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)) {
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;
},
@@ -388,14 +436,15 @@ export default {
'current_node': currentNode,
'next_node': nextNode,
'seconds': this.stepTime,
'userId': this.$store.getters.getUserId,
'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() {
this.currentQuestionId = this.questionIds.pop();
var prevId = this.questionIds.pop();
this.selectAnswer(prevId, true);
},
generateSessionId() {
return 'xxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
@@ -404,15 +453,44 @@ export default {
});
},
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)
}
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.data = apiResponse.data;
if (apiResponse.ok) this.successHandler(apiResponse.data);
else this.errorHandler(apiResponse.data, apiResponse.status);
if (showLoadingAnimation) this.isLoading = false;
});
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 (url === this.route("api.admin_work_flow.fetch_oldest_order")) {
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;
},
}
};
+75 -49
View File
@@ -6,33 +6,42 @@ 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 () {
// currenly, using service id to hardcode fetch
// todo-new: pls confirm need to include 1688 vip orders
$booking = Booking::where('service_id', 4)
->where('status', ApprovalStatus::APPROVED)
// ->latest()
->first();
if (!$booking) return response()->json(['error' => 'No booking found'], 404);
$bank = $booking->bank;
return response()->json([
'passwords' => $bank->holder_name ?? null,
'account_no' => $bank->account_no ?? null,
'pin' => $bank->bank_branch ?? null,
'holder_name' => $bank->holder_name ?? null,
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');
@@ -42,74 +51,91 @@ Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namesp
'current_node' => 'required|string',
'next_node' => 'required|string',
'seconds' => 'required|integer',
'userId' => 'required|integer|exists:users,id',
'user_id' => 'required|integer|exists:users,id',
'session_id' => 'required|string',
]);
if ($validator->fails()) throw new RequestValidationException($validator->messages()->first());
if ($validator->fails()) {
throw new RequestValidationException($validator->messages()->first());
}
$workflowTimestamp = WorkflowTimestamp::create($request->only('current_node', 'next_node', 'seconds', 'userId', 'session_id'));
$workflowTimestamp = WorkflowTimestamp::create($validator->validated());
return response()->json([
'message' => 'Workflow timestamp created successfully',
'data' => $workflowTimestamp
], 201);
return jsonResponse($workflowTimestamp, 'Workflow timestamp created successfully', 201);
})->name('add_workflow_timestamp');
Route::get('/fetch-pending-approved-po', function () {
// currenly, using service id to hardcode fetch
// todo-new: pls confirm need to include 1688 vip orders
$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))
// ->latest()
->first();
if (!$booking) return response()->json(['error' => 'No booking found'], 404);
return response()->json(new BookingResource($booking));
return $booking ? jsonResponse(new BookingResource($booking)) : jsonResponse(null, 'No booking found', 404);
})->name('fetch_pending_approve_po');
Route::get('/fetch-pending-fill-po', function () {
// currenly, using service id to hardcode fetch
// todo-new: pls confirm need to include 1688 vip orders
$booking = Booking::with('transactions')
->where('service_id', 4)
->where('status', ApprovalStatus::APPROVED)
->whereDoesntHave('transactions', fn ($query) => $query->where('type', TransactionType::PURCHASE_ORDER))
// ->latest()
->first();
if (!$booking) return response()->json(['error' => 'No booking found'], 404);
return response()->json(new BookingResource($booking));
return $booking ? jsonResponse(new BookingResource($booking)) : jsonResponse(null, 'No booking found', 404);
})->name('fetch_pending_fill_po');
Route::get('/fetch-model-attributes', function () {
$booking = Booking::where('service_id', 4)
->where('status', ApprovalStatus::APPROVED)
// ->latest()
->first();
Route::get('{booking_id}/fetch-model-attributes', function ($bookingId) {
$booking = Booking::find($bookingId);
if ($booking) {
$attributes = $booking->modelAttributes()
->where('name', BookingAttributeNames::ORDER_REFERENCE_NO)
->get(['id', 'value'])
->map(fn ($attr) => $attr->only(['id', 'value']));
return response()->json([
'booking' => $booking ?? null,
'booking_attributes' => $attributes ?? null,
]);
if (!$booking) {
return jsonResponse(null, 'No approved booking found', 404);
}
return response()->json(['message' => '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');
});