1688 admin work flow clean up and call api

This commit is contained in:
edmondlang
2024-07-22 01:12:51 +08:00
parent 31282492fe
commit 8d48f6f4eb
5 changed files with 470 additions and 306 deletions
+47
View File
@@ -0,0 +1,47 @@
<?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 = [
'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,35 @@
<?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('node');
$table->integer('seconds');
$table->unsignedBigInteger('userId');
$table->string('session_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('workflow_timestamps');
}
}
@@ -1,120 +1,119 @@
<template>
<div class="row justify-content-center align-items-center text-center" style="min-height: 80vh;">
<div class="col">
<div v-if="currentQuestion">
<h2>{{ currentQuestion.text }}</h2>
<div v-if="currentQuestionId === 'fill_po'">
<div class="box">
<div v-for="(order, index) in orderNumbers" :key="index" class="order">
<p>{{ order }}</p>
</div>
Total: CNY
</div>
<button @click="selectOrder(order, 'edit')" class="btn btn-primary">Edit PO</button>
</div>
<div v-else-if="currentQuestionId === '1688'">
<div>Login Information</div>
<login-form-component></login-form-component>
<button @click="selectOrder(order, 'issue_login')" class="btn btn-primary">Login Issue</button>
<button @click="selectOrder(order, 'dummy_login')" class="btn btn-primary">Login Successful</button>
</div>
<div v-else-if="currentQuestionId === 'login_successful'">
<div>Order Information</div>
<button @click="selectOrder(order, 'error_order')" class="btn btn-primary">Can't verify order?</button>
<button @click="selectOrder(order, 'verify_order')" class="btn btn-primary">Order Verified</button>
</div>
<div v-else-if="currentQuestionId === 'order_verify'">
<div>Order Amount</div>
<div class="box">
<div v-for="(order, index) in orderNumbers" :key="index" class="order">
<input v-model="orderNumbers[index]" class="form-control" />
</div>
CNY
</div>
<button @click="selectOrder(order, 'yes_order')" class="btn btn-primary">Yes</button>
<button @click="selectOrder(order, 'back_order')" class="btn btn-primary">Back</button>
</div>
<div v-else-if="currentQuestionId === 'approve_po'">
<div class="box">
<div v-for="(order, index) in orderNumbers" :key="index" class="order">
<p>{{ order }}</p>
</div>
Total: CNY
</div>
<button @click="selectOrder(order, 'approve')" class="btn btn-primary">Approve PO</button>
<button @click="selectOrder(order, 'edit')" class="btn btn-primary">Edit PO</button>
<button @click="selectOrder(order, 'reject')" class="btn btn-primary">Reject PO</button>
</div>
<div v-else-if="currentQuestionId === 'edit_po'">
<div class="box">
<div v-for="(order, index) in orderNumbers" :key="index" class="order">
<input v-model="orderNumbers[index]" class="form-control" />
</div>
<button @click="addOrder" class="btn btn-primary">Add Order</button>
<div>Total: CNY</div>
</div>
<button @click="issueOthers" class="btn btn-primary">Issue?</button>
<button @click="saveOrders" class="btn btn-primary">Done</button>
</div>
<div v-else-if="currentQuestionId === '1688_submit'">
<div class="box">
<h3>Upload English PO</h3>
<input type="file" id="image-upload" class="image-upload">
</div>
<div class="box">
<h3>Upload China PO</h3>
<input type="file" id="image-upload" class="image-upload">
</div>
<div class="box">
<h3>Upload bank slip</h3>
<input type="file" id="image-upload" class="image-upload">
</div>
<button @click="nextorder" class="btn btn-primary">Proceed to next order</button>
<button @click="submit_issue" class="btn btn-primary">Submit</button>
</div>
<div v-else-if="currentQuestionId === 'po_others'">
<div class="box">
<h3>What issue did you encounter?</h3>
<input type="text" id="issue" class="text-box" placeholder="Describe the issue...">
<div class="upload-text">Upload an image if needed</div>
<input type="file" id="image-upload" class="image-upload">
</div>
<button @click="issueOthers" class="btn btn-primary">Back</button>
<button @click="submit" class="btn btn-primary">Submit issue</button>
</div>
<div v-else-if="currentQuestionId === '1688_others'">
<div class="box">
<h3>What issue did you encounter?</h3>
<input type="text" id="issue" class="text-box" placeholder="Describe the issue...">
<div class="upload-text">Upload an image if needed</div>
<input type="file" id="image-upload" class="image-upload">
</div>
<button @click="issue1688" class="btn btn-primary">Back</button>
<button @click="submit" class="btn btn-primary">Submit issue</button>
</div>
<div v-else>
<div class="answer-container">
<div v-for="(answer, index) in currentQuestion.answers" :key="index" class="answer-item">
<button @click="selectAnswer(answer.next)" class="btn btn-primary">
{{ answer.text }}
</button>
</div>
</div>
<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>
<div v-if="readyToWork">
<h1>Are you ready to work today?</h1>
<div class="row">
<div class="col">
<button @click="startQuiz" class="btn btn-primary">Yes</button>
<button @click="readyToWork = !readyToWork" class="btn btn-primary">No</button>
<div v-else class="row">
<div class="col">
<div v-if="currentQuestion && Object.keys(currentQuestion).length">
<h2 v-if="currentQuestion.text">{{ currentQuestion.text }}</h2>
<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>
</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 class="row" v-if="currentQuestion.answers">
<div class="col">
<div class="w-100 d-block text-center">
<button v-for="(answer, index) in currentQuestion.answers" :key="index"
@click="selectAnswer(answer.next)"
class="btn btn-lg btn-primary d-inline-block rounded fs-20"
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 }}" 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 v-else>
<h1>Come back when you are ready to work</h1>
<button onClick="window.location.reload();" class="btn btn-primary">Refresh Page</button>
</div>
</div>
</div>
@@ -123,243 +122,234 @@
<script>
export default {
name: 'QuizApp',
data() {
return {
section: 'initial_section',
questions: {
1: {
text: 'What will you work on?',
answers: [
{ text: '1688', next: '1688' },
{ text: 'Approve PO', next: 'approve_po' },
{ text: 'Fill PO', next: 'fill_po' }
]
},
'1688': {},
'issue_login': {
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'},
"0": {
"text": "Are you ready to work today?",
"answers": [
{ "text": "Yes", "next": "start_work" },
{ "text": "No", "next": "no_work" }
]
},
'login_successful': {},
'issue_order': {
answers: [
{ text: 'Amount not found', next: 'issue_submit'},
{ text: 'Plus Member', next: 'issue_submit'},
{ text: 'Others', next: 'po_others'},
{ text: 'Customer Did not verify 1688 account', next: 'issue_submit'},
{ text: 'WorldFirst account linked another account', next: 'issue_submit'},
{ text: 'Back', next: 'login_successful'},
"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" }
]
},
'order_verify': {},
'order_verification': {
text: 'Are You Sure this is the correct amount?',
answers: [
{text: 'Yes', next: 'proceed_order'},
{text: 'No', next: 'insufficient_order'}
"no_work": {
"text": "Come back when you are ready to work",
"answers": [
{ "text": "Refresh Page", "next": "refresh_page" }
]
},
'proceed_order': {
text: 'Please proceed the order on 1688',
answers: [
{text: 'Got Issue', next: '1688_order_issue'},
{text: 'Done', next: '1688_submit'}
"1688": {
"load_api": this.route("api.admin_work_flow.fetch_oldest_order"),
"answers": [
{ "text": "Login Issue", "next": "login_issue" },
{ "text": "Login Successful", "next": "login_successful" }
]
},
'1688_order_issue': {
answers: [
{text: 'Wrong Pin Number', next: '1688_issue_submit'},
{text: 'Not Enough Stock', next: '1688_issue_submit'},
{text: 'Others', next: '1688_others'},
{text: 'No CrossBoarder', next: '1688_issue_submit'},
{text: 'AngPau', next: '1688_issue_submit'}
"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" }
]
},
'1688_others': {},
'1688_submit': {},
'1688_issue_submit': {
text: 'Issue has been submitted',
answers: [
{ text: 'Stop Working', next: 'stop_working'},
{ text: 'Next Order', next: '1688'}
"login_successful": {},
"issue_order": {
"answers": [
{ "text": "Amount not found", "next": "issue_submit" },
{ "text": "Plus Member", "next": "issue_submit" },
{ "text": "Others", "next": "po_others" },
{ "text": "Customer Did not verify 1688 account", "next": "issue_submit" },
{ "text": "WorldFirst account linked another account", "next": "issue_submit" },
{ "text": "Back", "next": "login_successful" }
]
},
'refund_request': {
text: 'Refund Request sent',
answers: [
{ text: 'Stop Working', next: 'stop_working' },
{ text: 'Next PO', next: 'approve_po' }
"order_verify": {},
"order_verification": {
"text": "Are You Sure this is the correct amount?",
"answers": [
{ "text": "Yes", "next": "proceed_order" },
{ "text": "No", "next": "insufficient_order" }
]
},
'approve_po': {},
'fill_po': {},
'po_filled': {
text: 'PO Filled',
answers: [
{ text: 'Stop Working', next: 'stop_working' },
{ text: 'Next PO', next: 'approve_po' }
"proceed_order": {
"text": "Please proceed the order on 1688",
"answers": [
{ "text": "Got Issue", "next": "1688_order_issue" },
{ "text": "Done", "next": "1688_submit" }
]
},
'submit_po' : {},
'po_approved': {
text: 'PO Approved',
answers: [
{ text: 'Stop Working', next: 'stop_working' },
{ text: 'Next PO', next: 'approve_po' }
"1688_order_issue": {
"answers": [
{ "text": "Wrong Pin Number", "next": "1688_issue_submit" },
{ "text": "Not Enough Stock", "next": "1688_issue_submit" },
{ "text": "Others", "next": "1688_others" },
{ "text": "No CrossBoarder", "next": "1688_issue_submit" },
{ "text": "AngPau", "next": "1688_issue_submit" }
]
},
'edit_po': {},
'po_others': {},
'reject_po': {
text: 'PO Rejected',
answers: [
{ text: 'Issue', next: 'issue_po' },
{ text: 'Back', next: 'approve_po' }
"1688_others": {},
"1688_submit": {},
"1688_issue_submit": {
"text": "Issue has been submitted",
"answers": [
{ "text": "Stop Working", "next": "stop_working" },
{ "text": "Next Order", "next": "1688" }
]
},
'issue_po': {
answers: [
{ text: 'Sensitive Goods', next: 'issue_submit'},
{ text: 'Others', next: 'po_others'},
{ text: 'Stop Working', next: 'stop_working'},
{ text: 'Next PO', next: 'approve_po'}
"refund_request": {
"text": "Refund Request sent",
"answers": [
{ "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'}
"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" }
]
},
'1688_issue_submitted': {
text: 'Issue has been submitted',
answers: [
{ text: 'Stop Working', next: 'stop_working'},
{ text: 'Next Order', next: '1688'}
"fill_po": {
// "text": "Order Number",
"load_api": this.route("api.admin_work_flow.fetch_pending_fill_po"),
"answers": [
{ "text": "Approve", "next": "po_approved" },
{ "text": "Edit PO", "next": "edit_po" },
{ "text": "Reject", "next": "reject_po" }
]
},
'stop_working': {
text: 'Thank you for your work!',
"po_filled": {
"text": "PO Filled",
"answers": [
{ "text": "Stop Working", "next": "stop_working" },
{ "text": "Next PO", "next": "approve_po" }
]
},
"submit_po": {},
"po_approved": {
"text": "PO Approved",
"answers": [
{ "text": "Stop Working", "next": "stop_working" },
{ "text": "Next PO", "next": "approve_po" }
]
},
"edit_po": {},
"po_others": {},
"reject_po": {
"text": "PO Rejected",
"answers": [
{ "text": "Issue", "next": "issue_po" },
{ "text": "Back", "next": "approve_po" }
]
},
"issue_po": {
"answers": [
{ "text": "Sensitive Goods", "next": "issue_submit" },
{ "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" }
]
},
"1688_issue_submitted": {
"text": "Issue has been submitted",
"answers": [
{ "text": "Stop Working", "next": "stop_working" },
{ "text": "Next Order", "next": "1688" }
]
},
"stop_working": {
"text": "Thank you for your work!",
"answers": [
{ "text": "Refresh Page", "next": "refresh_page" }
]
}
},
currentQuestionId: null,
readyToWork: true,
orderNumbers: ['Order 123', 'Order 456', 'Order 789']
isLoading: false,
externalApiResponse: { data: null },
timer: null,
elapsedTime: 0,
interval: 1000,
sessionId: null,
};
},
computed: {
currentQuestion() {
return this.questions[this.currentQuestionId];
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 = 0;
if (this.sessionId == null) {
this.sessionId = this.generateSessionId();
}
},
methods: {
startQuiz() {
this.currentQuestionId = 1;
},
startTimer() { this.timer = setInterval(() => this.elapsedTime++, this.interval); },
stopTimer() { clearInterval(this.timer); this.timer = null; },
startWork() { this.startTimer(); console.log("work is started"); },
endWork() { this.stopTimer(); console.log("work is ended"); },
selectAnswer(nextQuestionId) {
if (nextQuestionId === 'refresh_page') return window.location.reload();
if (!Object.keys(this.questions[nextQuestionId]).length) this.endWork();
if (nextQuestionId === 'start_work') this.startWork();
if (nextQuestionId === 'stop_work') this.stopTimer();
this.currentQuestionId = nextQuestionId;
},
selectOrder(order, action) {
if (action === 'approve') {
this.currentQuestionId = 'po_approved';
} else if (action === 'edit') {
this.currentQuestionId = 'edit_po';
} else if (action === 'reject') {
this.currentQuestionId = 'reject_po';
} else if (action == 'issue_login') {
this.currentQuestionId = 'issue_login'
} else if (action == 'dummy_login') {
this.currentQuestionId = 'login_successful'
} else if (action == 'error_order') {
this.currentQuestionId = 'issue_order'
} else if (action == 'verify_order') {
this.currentQuestionId = 'order_verify'
} else if (action == 'back_order') {
this.currentQuestionId = 'login_successful'
} else if (action == 'yes_order') {
this.currentQuestionId = 'order_verification'
}
},
addOrder() {
this.orderNumbers.push('');
},
saveOrders() {
alert('Orders have been saved: ' + this.orderNumbers.join(', '));
this.currentQuestionId = 'po_filled';
},
nextorder() {
this.currentQuestionId = '1688';
},
issueOthers(){
this.currentQuestionId = 'issue_po';
},
issue1688() {
this.currentQuestionId = '1688_order_issue';
},
submit_issue() {
this.currentQuestionId = '1688_issue_submitted';
},
submit() {
}
// call api to recorrd timestamp
var logBody = {
'node': nextQuestionId,
'seconds': this.elapsedTime,
'userId': this.$store.getters.getUserId,
'session_id': this.sessionId,
};
this.callApi(this.route('api.admin_work_flow.add_workflow_timestamp'), 'post', 'section', logBody, false, false);
if (this.questions[nextQuestionId]?.load_api) this.callApi(this.questions[nextQuestionId].load_api, 'get', 'section');
},
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 (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;
});
},
}
};
</script>
<style scoped>
.container {
margin: 20px;
}
.box {
border: 1px solid #ddd;
padding: 20px;
margin-top: 20px;
border-radius: 8px;
}
.order {
margin-bottom: 15px;
}
.image-upload {
width: 80%;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
}
.text-box {
width: 80%;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 20px;
}
.btn {
margin: 5px;
font-size: 18px; /* Adjust the font size */
padding: 12px 24px; /* Adjust the padding */
border-radius: 4px; /* Optional: adjust border radius */
}
.answer-container {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.answer-item {
margin: 10px;
}
</style>
+90
View File
@@ -0,0 +1,90 @@
<?php
use App\Classes\Exceptions\RequestValidationException;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Http\Resources\BookingResource;
use App\Models\Booking;
use App\Models\WorkflowTimestamp;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\Request;
Route::group(['prefix' => 'admin-work-flow', 'as' => 'admin_work_flow.', 'namespace' => 'AdminWorkFlow'], function () {
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,
'booking_marking' => $booking->marking ?? null,
]);
})->name('fetch_oldest_order');
Route::post('/add-workflow-timestamp', function (Request $request) {
$validator = Validator::make($request->all(), [
'node' => 'required|string',
'seconds' => 'required|integer',
'userId' => 'required|integer|exists:users,id',
'session_id' => 'required|string',
]);
if ($validator->fails()) throw new RequestValidationException($validator->messages()->first());
$workflowTimestamp = WorkflowTimestamp::create($request->only('node', 'seconds', 'userId', 'session_id'));
return response()->json([
'message' => 'Workflow timestamp created successfully',
'data' => $workflowTimestamp
], 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));
})->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));
})->name('fetch_pending_fill_po');
});
+2
View File
@@ -76,5 +76,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
// require __DIR__ . '/rate.php';
// require __DIR__ . '/receipt.php';
require __DIR__ . '/admin_work_flow.php';
});
});