Merge branch 'development' into vapor/development

This commit is contained in:
Dillon Ngo
2024-10-15 00:52:46 +08:00
19 changed files with 1184 additions and 227 deletions
@@ -1,44 +0,0 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use App\Classes\ValueObjects\Constants\RoleTypes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
class HasVouchersAll implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return mixed
*/
public static function apply(Builder $builder, $value)
{
if(in_array(Auth::user()->type, RoleTypes::ADMIN_ROLES)){
// $userId = $value !== 1 ? $value : Auth::user()->id;
$userId = $value;
return $builder->where('user_id', $userId)
->where(function ($query) {
$query->whereHas('reward', function ($subquery) {
$subquery->where('is_active', true);
})
->orWhereDoesntHave('reward');
})
->whereDoesntHave('voucher.redemptions.transaction.booking.company.employees', function ($query) use ($userId) {
$query->where('user_id', $userId);
});
}
else{
return $builder->where('user_id', Auth::user()->id)
->where(function ($query) {
$query->whereHas('reward', function ($subquery) {
$subquery->where('is_active', true);
})
->orWhereDoesntHave('reward');
})
->whereDoesntHave('voucher.redemptions.transaction.owner');
}
}
}
@@ -24,10 +24,11 @@ class ImportAmbankStatementLogic extends AbstractControllerLogic
/**
* @return array
*/
protected function notification():array {
protected function notification(): array
{
return [
'title' => 'Import Ambank Statement Transactions Details',
'message' => 'You have successfully updated the Ambank Statement Transactions Details'
'message' => 'You have successfully imported the Ambank Statement Transactions Details'
];
}
@@ -37,178 +38,147 @@ class ImportAmbankStatementLogic extends AbstractControllerLogic
* @return JsonResponse
* @throws MalformedRequestException
*/
public function logic(Request $request) : JsonResponse
public function logic(Request $request): JsonResponse
{
$requiredHeaders = ['Date', 'Time', 'Description', 'Recipient Reference', 'Other Payment Details', 'Transfer Type', 'Inward Amount', 'Outward Amount', 'Balance'];
$object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
foreach ($object->getFiles() as $file){
foreach ($object->getFiles() as $file) {
$collection = Excel::toCollection(null, json_decode($file)->file_info->original->file, null, null, true);
$statementDetailRows = $collection->first()->slice(0,13);
// check whether the csv is format we expect for
foreach ($collection as $sheet_no => $sheet) {
$header_mapping_result = [];
foreach ($sheet as $row_no => $row) {
if ($row[0] !== $requiredHeaders[0]) {
continue;
}
$dateInfoKey = $this->findInfoIndexFromExcelCollection($statementDetailRows, "STATEMENT DATE");
$statementDateRange = trim(explode(':', $this->extractInfoFromExcelCollection($statementDetailRows[$dateInfoKey])[0])[1]);
$dateFrom = trim(explode('-', $statementDateRange)[0]);
$dateTo = trim(explode('-', $statementDateRange)[1]);
$dateFrom = carbon::createFromFormat('d/m/Y', $dateFrom);
$dateTo = carbon::createFromFormat('d/m/Y', $dateTo);
$dateTo = carbon::parse($dateTo);
$totalDebitKey = $this->findInfoIndexFromExcelCollection($statementDetailRows, "TOTAL DEBIT");
$totalDebit = $this->extractInfoFromExcelCollection($statementDetailRows[$totalDebitKey])[1];
$totalCreditKey = $this->findInfoIndexFromExcelCollection($statementDetailRows, "TOTAL CREDIT");
$totalCredit = $this->extractInfoFromExcelCollection($statementDetailRows[$totalCreditKey])[1];
$totalTransactions = $totalDebit + $totalCredit;
$beginBalanceKey = $this->findInfoIndexFromExcelCollection($statementDetailRows, "OPENING BALANCE");
$beginBalance = ((float) str_replace(',', '', $this->extractInfoFromExcelCollection($statementDetailRows[$beginBalanceKey])[1]));
$endBalanceKey = $this->findInfoIndexFromExcelCollection($statementDetailRows, "CLOSING BALANCE");
$endBalance = ((float) str_replace(',', '', $this->extractInfoFromExcelCollection($statementDetailRows[$endBalanceKey])[1]));
foreach ($requiredHeaders as $col_no => $header) {
if ($row[$col_no] !== $header) {
$header_mapping_result[$col_no] = false;
} else {
$header_mapping_result[$col_no] = true;
}
}
if (count($header_mapping_result) === 9) {
break;
}
}
if (count($header_mapping_result) === 0 || in_array(false, $header_mapping_result)) {
throw new MalformedRequestException("Sheet {$sheet_no} format is not correct");
}
}
$data_start_from = $sheet->search(function ($row, $key) {
return $row[0] === 'Date';
}) + 1;
$data_end_at = $sheet->keys()->last();
$descending = true;
for ($i = $data_end_at; $i > 0; $i--) {
$parse_date = Carbon::createFromFormat('d/m/Y', $sheet[$i][0]);
if ($parse_date && $parse_date->format('d/m/Y') === $sheet[$i][0]) {
$data_end_at = $i;
break;
}
}
if (Carbon::createFromFormat('d/m/Y', $sheet[$data_start_from][0]) > Carbon::createFromFormat('d/m/Y', $sheet[$data_end_at][0])) {
$descending = true;
} else {
$descending = false;
}
$date_from = Carbon::createFromFormat('d/m/Y', $sheet[$descending ? $data_end_at : $data_start_from][0])->format('Y-m-d');
$date_to = Carbon::createFromFormat('d/m/Y', $sheet[$descending ? $data_start_from : $data_end_at][0])->format('Y-m-d');
$total_rows = abs($data_end_at - $data_start_from) + 1;
$begin_balance = floatval($sheet[$descending ? $data_end_at : $data_start_from][8]);
$end_balance = floatval($sheet[$descending ? $data_start_from : $data_end_at][8]);
$total_debit = 0;
$total_credit = 0;
$account = StatementAccount::where('number', 8881040198515)->first();
if (!$account) {
throw new Exception("Statement Account for CIEF LITE not found.");
throw new Exception("Statement Account for AMBANK not found.");
}
$statement = AccountStatement::whereDate('date_from', $dateFrom)
->whereDate('date_to', $dateTo)
->where('total_amount', $totalTransactions)
->where('begin_balance', $beginBalance)
->where('end_balance', $endBalance)
$statement = AccountStatement::whereDate('date_from', $date_from)
->whereDate('date_to', $date_to)
->where('total_rows', $total_rows)
->where('begin_balance', $begin_balance)
->where('end_balance', $end_balance)
->first();
if(!$statement){
if (!$statement) {
$statement = new AccountStatement([
'date_from' => $dateFrom,
'date_to' => $dateTo,
'total_amount' => $totalTransactions,
'begin_balance' => $beginBalance,
'end_balance' => $endBalance,
'statement_account_id' => $account->id,
'date_from' => $date_from,
'date_to' => $date_to,
'total_rows' => $total_rows,
'begin_balance' => $begin_balance,
'end_balance' => $end_balance,
]);
$statement->save();
}
$account->statements()->save($statement);
$collection->map(function ($sheet, $key) use ($statement) {
if ($key === 0) {
$headerColumnKey = $this->findInfoIndexFromExcelCollection($sheet->slice(11), "DATE");
$dateColumnKey = $this->findInfoIndexFromExcelCollection($sheet->slice(11)[$headerColumnKey], "DATE");
$descriptionColumnKey = $this->findInfoIndexFromExcelCollection($sheet->slice(11)[$headerColumnKey], "TRANSACTION");
$debitColumnKey = $this->findInfoIndexFromExcelCollection($sheet->slice(11)[$headerColumnKey], "DEBIT");
$creditColumnKey = $this->findInfoIndexFromExcelCollection($sheet->slice(11)[$headerColumnKey], "CREDIT");
$balanceColumnKey = $this->findInfoIndexFromExcelCollection($sheet->slice(11)[$headerColumnKey], "BALANCE");
} else {
if (strpos($sheet->first()->first(), "DATE") === false) {
return;
}
$headerColumnKey = $this->findInfoIndexFromExcelCollection($sheet, "DATE");
$dateColumnKey = $this->findInfoIndexFromExcelCollection($sheet[$headerColumnKey], "DATE");
$descriptionColumnKey = $this->findInfoIndexFromExcelCollection($sheet[$headerColumnKey], "TRANSACTION");
$debitColumnKey = $this->findInfoIndexFromExcelCollection($sheet[$headerColumnKey], "DEBIT");
$creditColumnKey = $this->findInfoIndexFromExcelCollection($sheet[$headerColumnKey], "CREDIT");
$balanceColumnKey = $this->findInfoIndexFromExcelCollection($sheet[$headerColumnKey], "BALANCE");
}
$sheet->slice($headerColumnKey + 1)->map(function ($row, $index) use (
$statement,
$sheet,
$dateColumnKey,
$descriptionColumnKey,
$debitColumnKey,
$creditColumnKey,
$balanceColumnKey,
$key)
{
// if date or balance is null or empty, skip the row
if (!$row[$dateColumnKey] || !$row[$balanceColumnKey]) {
return;
foreach ($collection as $sheet_no => $sheet) {
foreach ($sheet as $row_no => $row) {
if ($row_no < $data_start_from || $row_no > $data_end_at) {
continue;
}
$postingDate = carbon::createFromFormat('dM', trim($row[$dateColumnKey]));
$posting_date = Carbon::createFromFormat('d/m/Y', $row[0])->format('Y-m-d');
$posting_time = Carbon::parse($row[1])->format('H:i:s');
$transaction_description = trim($row[2]);
$transaction_description_2 = trim($row[3]);
$transaction_description_3 = trim($row[4]);
$transaction_description_4 = trim($row[5]);
$inward_amount = floatval(trim($row[6]));
$outward_amount = floatval(trim($row[7]));
$amount = $inward_amount == 0 ? $outward_amount : $inward_amount;
$description = trim($row[$descriptionColumnKey]);
$nextRow = $sheet[$index + 1];
if (!$nextRow[$dateColumnKey] && $nextRow[$descriptionColumnKey]) {
$nextRowInfoArr = $this->extractInfoFromExcelCollection($nextRow);
foreach ($nextRowInfoArr as $col) {
if ($col) {
$description .= ' '. $col;
}
}
if ($inward_amount == 0) {
$total_debit += 1;
} else {
$total_credit += 1;
}
$descriptionArr = explode(',', $description);
$balance = floatval(trim($row[8]));
$transactionDescription = array_key_exists(0, $descriptionArr) ? trim($descriptionArr[0]) : "";
$description2 = array_key_exists(1, $descriptionArr) ? trim($descriptionArr[1]) : "";
$description3 = array_key_exists(2, $descriptionArr) ? trim($descriptionArr[2]) : "";
$description4 = array_key_exists(3, $descriptionArr) ? trim($descriptionArr[3]) : "";
$description5 = array_key_exists(4, $descriptionArr) ? trim($descriptionArr[4]) : "";
if (count($descriptionArr) > 5) {
for ($i = 5; $i < count($descriptionArr); $i++) {
$description5 = array_key_exists($i, $descriptionArr) ? $description5 . " " . trim($descriptionArr[$i]) : $description5;
}
}
$amount = $row[$creditColumnKey] ? ((float) str_replace(',', '', trim($row[$creditColumnKey]))) : (-((float) str_replace(',', '', $row[$debitColumnKey])));
$endBalance = ((float) str_replace(',', '', $row[$balanceColumnKey]));
$transaction = new StatementTransaction([
'posting_date' => $postingDate,
'transaction_description' => $transactionDescription,
'transaction_description_2' => $description2,
'transaction_description_3' => $description3,
'transaction_description_4' => $description4,
'transaction_description_5' => $description5,
'amount' => $amount,
'end_balance' => $endBalance,
]);
// Check if the transaction already exists for this statement
$existingTransaction = StatementTransaction::whereDate('posting_date', $postingDate)
// Check if the transaction already exists for any statement
$existingTransaction = StatementTransaction::where('posting_date', ($posting_date . ' ' . $posting_time))
->where('amount', $amount)
->where('transaction_description', $transactionDescription)
->whereRaw("CAST(REPLACE(end_balance,',','') AS DECIMAL(15,2)) = ?",[$endBalance])
->where('transaction_description', $transaction_description)
->whereRaw("CAST(REPLACE(end_balance,',','') AS DECIMAL(15,2)) = ?", [$balance])
->first();
if (!$existingTransaction) {
$statement->transactions()->save($transaction);
}
return $transaction;
});
});
if (!$existingTransaction) {
$transaction = new StatementTransaction([
'account_statement_id' => $statement->id,
'posting_date' => $posting_date . ' ' . $posting_time,
'transaction_description' => $transaction_description,
'transaction_description_2' => $transaction_description_2,
'transaction_description_3' => $transaction_description_3,
'transaction_description_4' => $transaction_description_4,
'amount' => $amount,
'end_balance' => $balance,
]);
$transaction->save();
}
}
$statement->total_amount = $total_debit ?: $total_credit;
$statement->save();
}
}
return $this->response([]);
}
private function findInfoIndexFromExcelCollection($collection, $keyword)
{
foreach ($collection as $key => $row) {
if ($row instanceof Collection) {
$infoArr = $this->extractInfoFromExcelCollection($row);
foreach ($infoArr as $info) {
if (str_contains(strtolower($info), strtolower($keyword))) {
return $key;
}
}
} else {
if (str_contains(strtolower($row), strtolower($keyword))) {
return $key;
}
}
}
}
// return an array with information of the row start from 0 index
private function extractInfoFromExcelCollection($collection)
{
return array_values(array_filter($collection->toArray()));
}
}
@@ -183,16 +183,6 @@ class CreateCustomerLogic extends AbstractControllerLogic
$this->generateEmailVerificationAttemptProcessor->execute($user);
}
$this->newCustomerToVoucherifyProcessor->execute($company->id, $user, true);
$voucher = $this->createVoucherProcessor->execute($user, Vouchers::WELCOME_50_PERCENT_OFF);
if($voucher){
$voucherCount = $user->rewards->where('voucher_id', $voucher->id)->count();
if($voucherCount === 0){
$this->createsUserReward->execute(null, $user, $voucher->id);
}
}
$shippingCompanyModuleId = null;
if ($request->input('shipping_company_module_id')) {
$shippingCompanyModuleId = $request->input('shipping_company_module_id');
@@ -206,6 +196,16 @@ class CreateCustomerLogic extends AbstractControllerLogic
$this->connectCompanyToShippingCompanyModule->execute($company->id, $shippingCompanyModuleId);
}
$this->newCustomerToVoucherifyProcessor->execute($company->id, $user, true);
$voucher = $this->createVoucherProcessor->execute($user, Vouchers::WELCOME_50_PERCENT_OFF);
if($voucher){
$voucherCount = $user->rewards->where('voucher_id', $voucher->id)->count();
if($voucherCount === 0){
$this->createsUserReward->execute(null, $user, $voucher->id);
}
}
return $this->response($this->authenticationProcessor->execute($request, false));
}
@@ -113,32 +113,33 @@ class CreateBookingRefundLogic extends AbstractControllerLogic
$service_charges_to_refund = $transaction->service_charge;
} else {
$refundAmount = bcdiv($request->input('amount'), $transaction->currency_rate, 7);
$bookingAmountBeforeCurrentRefund = $booking->fix_amount - $refundInPending;
$bookingAmountAfterRefunded = $booking->fix_amount - $refundInPending - $request->input('amount');
$isFullyRefund = ($refund + $request->input('amount')) == $transaction->original_amount;
$voucherCode = null;
$redemptionId = null;
if ($transaction->voucherRedemption) {
// $voucherCode = $transaction->voucherRedemption->voucher->code;
$redemptionId = $transaction->voucherRedemption->redemption_id;
}
$conversionObjectBeforeCurrentRefund = new CurrencyConversionObject($bookingAmountBeforeCurrentRefund, $booking->convertible_currency_id, $booking->service_id, $booking->fix_currency_id === 1 ? 0:1, $transaction->payment_method);
$conversionObjectAfterRefund = new CurrencyConversionObject($isFullyRefund ? $request->input('amount') : $bookingAmountAfterRefunded, $booking->convertible_currency_id, $booking->service_id, $booking->fix_currency_id === 1 ? 0:1, $transaction->payment_method);
$quotationBeforeCurrentRefund = $this->fetchBookingQuotation->execute($booking->company, $conversionObjectBeforeCurrentRefund, $voucherCode, $redemptionId);
$quotationAfterRefund = $this->fetchBookingQuotation->execute($booking->company, $conversionObjectAfterRefund, $voucherCode, $redemptionId);
$service_charges_to_refund = $isFullyRefund ? $quotationBeforeCurrentRefund->getServiceCharge() : $quotationBeforeCurrentRefund->getServiceCharge() - $quotationAfterRefund->getServiceCharge();
}
$bookingAmountBeforeCurrentRefund = $booking->fix_amount - $refundInPending;
$bookingAmountAfterRefunded = $booking->fix_amount - $refundInPending - $request->input('amount');
$isFullyRefund = ($refund + $request->input('amount')) == $transaction->original_amount;
$voucherCode = null;
$redemptionId = null;
if ($transaction->voucherRedemption) {
// $voucherCode = $transaction->voucherRedemption->voucher->code;
$redemptionId = $transaction->voucherRedemption->redemption_id;
}
$conversionObjectBeforeCurrentRefund = new CurrencyConversionObject($bookingAmountBeforeCurrentRefund, $booking->convertible_currency_id, $booking->service_id, $booking->fix_currency_id === 1 ? 0:1, $transaction->payment_method);
$conversionObjectAfterRefund = new CurrencyConversionObject($isFullyRefund ? $request->input('amount') : $bookingAmountAfterRefunded, $booking->convertible_currency_id, $booking->service_id, $booking->fix_currency_id === 1 ? 0:1, $transaction->payment_method);
$quotationBeforeCurrentRefund = $this->fetchBookingQuotation->execute($booking->company, $conversionObjectBeforeCurrentRefund, $voucherCode, $redemptionId);
$quotationAfterRefund = $this->fetchBookingQuotation->execute($booking->company, $conversionObjectAfterRefund, $voucherCode, $redemptionId);
$service_charges_to_refund = $isFullyRefund ? $quotationBeforeCurrentRefund->getServiceCharge() : $quotationBeforeCurrentRefund->getServiceCharge() - $quotationAfterRefund->getServiceCharge();
// refund service charges if booking is not E2E
$refundTotal = $refundAmount;
@@ -186,4 +187,4 @@ class CreateBookingRefundLogic extends AbstractControllerLogic
}
}
}
@@ -20,6 +20,7 @@ class PaymentTransactionResource extends JsonResource
public function toArray($request)
{
$current_running_balance = $request['running_balance'];
$booking = null; //cief todo: 66
$bank = null;
//Check if Transaction of type PAYMENT has an override for recipient bank - starts
+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();
+49
View File
@@ -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');
}
}
@@ -0,0 +1,59 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
class UpdateVoucherCampaignsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (env('APP_ENV') !== 'production') {
DB::table('voucher_campaigns')->where('id', 1)->update(['campaign_id' => 'camp_snxv2JQlh5v9LEDBbIBOnibD']);
DB::table('voucher_campaigns')->where('id', 2)->update(['campaign_id' => 'camp_lEM7WhLHFlhcRKRR0C4bXH0F']);
DB::table('voucher_campaigns')->where('id', 3)->update(['campaign_id' => 'camp_JP9qBGBYEinVjzAE1vjynMe0']);
$voucherCampaignIds = [1, 2, 3];
foreach ($voucherCampaignIds as $id) {
$campaignId = DB::table('voucher_campaigns')->where('id', $id)->value('campaign_id');
$ownerId = DB::table('voucher_campaigns')->where('id', $id)->value('id');
$upperCampaignId = strtoupper($campaignId);
$keysToUpdate = DB::table('key_value_pairs')
->where('key', 'like', '%_TOTAL%')
->where('owner_type', 'App\Models\VoucherCampaign')
->where('owner_id', (int) $ownerId)
->get();
foreach ($keysToUpdate as $entry) {
$parts = explode('_', $entry->key);
$lastTwoParts = array_slice($parts, -2); // Get the last two elements
$newKey = "{$upperCampaignId}_" . implode('_', $lastTwoParts);
DB::table('key_value_pairs')
->where('id', $entry->id)
->update(['key' => $newKey]);
}
}
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
if (env('APP_ENV') !== 'production') {
DB::table('voucher_campaigns')->where('id', 1)->update(['campaign_id' => 'camp_0pXJ11fNxzLjNNVIJ1DCeTW1']);
DB::table('voucher_campaigns')->where('id', 2)->update(['campaign_id' => 'camp_pkLMhccKn0r74L61Fc36wYum']);
DB::table('voucher_campaigns')->where('id', 3)->update(['campaign_id' => 'camp_ZK6VwLHI9Ij8lBz5kAxcVy15']);
}
}
}
@@ -70,7 +70,10 @@ export default {
} else if (this.statementType.includes('LITE')) {
this.submit(this.route('api.accounting.statement.import.cief.lite'), 'post', this.section, true, false)
}
}
},
errorHandler(error){
this.formHandler(error.message);
},
},
mixins: [formHandler]
}
@@ -16,7 +16,7 @@
}
},
created(){
this.submit(this.route('api.voucher.user.list') + '?filters=' + JSON.stringify( { 'has_vouchers_all': true, order_by:{ column:'id', DESC:true }} ), 'get', 'voucherNavigationSection', false, false);
this.submit(this.route('api.voucher.user.list') + '?filters=' + JSON.stringify( { 'has_vouchers_all_with_user': true, order_by:{ column:'id', DESC:true }} ), 'get', 'voucherNavigationSection', false, false);
},
methods: {
successHandler(response){
@@ -30,7 +30,7 @@
}
},
created(){
this.submit(this.route('api.voucher.user.list') + '?filters=' + JSON.stringify( { 'has_vouchers_all': this.company.employee.id, order_by:{ column:'id', DESC:true }} ), 'get', this.section, false, false);
this.submit(this.route('api.voucher.user.list') + '?filters=' + JSON.stringify( { 'has_vouchers_all_with_user': this.company.employee.id, order_by:{ column:'id', DESC:true }} ), 'get', this.section, false, false);
},
methods: {
successHandler(response){
@@ -81,7 +81,7 @@
url: '#',
autoQueue: false,
processQueue: false,
acceptedFiles: 'image/*, application/*',
acceptedFiles: 'image/*, application/*, text/csv',
uploadMultiple: true,
clickable: '.select-btn',
previewTemplate: '<div class="row m-l-0 m-r-0 align-items-center m-t-5 m-b-5 bg-master-lightest text-left p-t-10 p-b-10 "> <div class="col-auto p-r-0"> <img data-dz-thumbnail style="width: 35px; height: 35px;" /> </div> <div class="col"> <div class="row m-b-5"> <div class="col"> <div class="dz-filename fs-8 bold"><span data-dz-name></span></div> </div> </div> <div class="row"> <div class="col"> <div class="dz-size muted light fs-10" data-dz-size></div> </div> </div> </div> <div class="col-auto"><i class="fs-16 fa fa-times-circle pointer hint-text" data-dz-remove></i></div> </div>'
@@ -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
+198
View File
@@ -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');
});
+2
View File
@@ -89,5 +89,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
// require __DIR__ . '/rate.php';
// require __DIR__ . '/receipt.php';
require __DIR__ . '/admin_work_flow.php';
});
});
+5 -1
View File
@@ -419,7 +419,7 @@ Route::get('/segments', function (Request $request) {
Route::get('/pending_orders', function(){
$payments = Transaction::where('type', TransactionType::PAYMENT)->where('owner_type', Booking::class)->whereIn('status', [ApprovalStatus::APPROVED])
->whereDoesntHave('transactions', function ($query) {
return $query->where('type', TransactionType::REFUND)->whereIn('status', [ApprovalStatus::PENDING_SUBMISSION, ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED]);
return $query->where('type', TransactionType::REFUND)->whereIn('status', [ApprovalStatus::PENDING_SUBMISSION, ApprovalStatus::PENDING_VERIFICATION]);
})
->orderBy('updated_at', 'desc')
->get();
@@ -1240,6 +1240,10 @@ 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');
//Laravel Vapor - Starts
// Route::get('/aws-image-upload', 'AWS\AWSImageUploadController@imageUpload')->name('aws.image.upload');
// Route::post('/aws-image-upload', 'AWS\AWSImageUploadController@imageUploadPost')->name('aws.image.upload.post');