Merge branch 'vapor/tax-rebate-qr' of https://gitlab.com/CIEFWorldwideSdnBhd/shipping-portal into vapor/production

This commit is contained in:
edmondlang
2024-10-31 18:05:35 +08:00
15 changed files with 551 additions and 4 deletions
@@ -0,0 +1,89 @@
<?php
namespace App\Classes\Modules\Orders\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Orders\Services\ChecksIfOrderNumberExists;
use App\Classes\Modules\Orders\Services\RestoresOrder;
use App\Classes\Modules\Orders\Services\FetchesOrder;
use App\Http\Resources\OrderResource;
use App\Models\Order;
use App\Models\SupplierTaxRebate;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class CreateSupplierTaxRebateLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification(): array
{
return [
'title' => 'Create Supplier Tax Rebate',
'message' => 'You have successfully created a Supplier Tax Rebate'
];
}
/** @var RestoresOrder */
private $restoresOrder;
/** @var FetchesOrder */
private $fetchesOrder;
/** @var ChecksIfOrderNumberExists */
private $checkIfOrderNumberExists;
/**
* CancelOrderLogic constructor.
* @param RestoresOrder $restoresOrder
* @param FetchesOrder $fetchesOrder
*/
public function __construct(RestoresOrder $restoresOrder, FetchesOrder $fetchesOrder, ChecksIfOrderNumberExists $checkIfOrderNumberExists)
{
$this->restoresOrder = $restoresOrder;
$this->fetchesOrder = $fetchesOrder;
$this->checkIfOrderNumberExists = $checkIfOrderNumberExists;
}
/**
* @param Request $request
* @return JsonResponse
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function logic(Request $request): JsonResponse
{
Log::info('finding order');
/** @var Order $order */
$order = $this->fetchesOrder->execute(['id' => $request->route('id')]);
Log::info('taxRebate');
$taxRebate = SupplierTaxRebate::create([
'order_id' => $order->id,
'supplier_name' => $request->input('supplier_name'),
'supplier_contact_number' => $request->input('supplier_contact_number'),
'reference_number' => $this->generateUniqueReferenceNumber(),
]);
return $this->resourceResponse(new OrderResource($order));
}
public function generateUniqueReferenceNumber(?string $prefix = '', ?int $length = 9): string
{
Log::info('generating unique reference number');
// regenerate new reference number until a new unique reference is found
do {
$reference = $prefix . rand((int)('1' . str_repeat('0', $length - 2) . '1'), (int) str_repeat('9', $length));
} while (
SupplierTaxRebate::where('reference_number', $reference)->exists() || $this->checkIfOrderNumberExists->execute($reference)
);
return $reference;
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Orders;
use App\Classes\Modules\Orders\ControllersLogic\CreateSupplierTaxRebateLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CreateSupplierTaxRebateController
{
/**
* @param Request $request
* @param RestoreOrderLogic $logic
* @return JsonResponse
*/
public function create(Request $request, CreateSupplierTaxRebateLogic $logic): JsonResponse
{
return $logic->execute($request);
}
}
@@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers\Orders;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\OrderRoleTypes;
use App\Classes\ValueObjects\Constants\WarehouseReferences;
use App\Models\Order;
use Illuminate\Http\Request;
use Mccarlosen\LaravelMpdf\Facades\LaravelMpdf;
use App\Classes\General\AWSS3Helper;
use App\Models\SupplierTaxRebate;
use Illuminate\Support\Facades\Storage;
class DownloadTaxRebateQrPdfController
{
public function download(Request $request){
$order = Order::find($request->route('id'));
$deliveryAddress = $order->addresses()->where('status', ApprovalStatus::APPROVED)->first();
$warehouse= $order->orderRoles()->where('role_id', OrderRoleTypes::ORIGIN_WAREHOUSE)->first()->appointee;
$warehouseAddress = $warehouse->addresses()->first();
$warehouseContacts = $warehouse->contacts;
$warehousePrefix = '';
$deliveryPrefix = '';
$customerMarking = '';
$remark = $warehouse->remarks()->first();
if($warehouse->reference === WarehouseReferences::VT_GUANG_ZHOU || $warehouse->reference === WarehouseReferences::VT_YIWU) {
$customerMarking = $order->companyModule->connections()->first()->invitee_reference.'/';
if(strtolower($deliveryAddress->state->name) === 'sabah' || strtolower($deliveryAddress->state->name) === 'labuan') {
$deliveryPrefix = 'SB/';
}
if(strtolower($deliveryAddress->state->name) === 'sarawak') {
$deliveryPrefix = 'SRW/';
}
}
if($warehouse->reference === WarehouseReferences::YD_YIWU){
$warehousePrefix = 'YW/';
}
if(in_array($warehouse->reference,[WarehouseReferences::YD_GUANG_ZHOU, WarehouseReferences::YD_GUANG_ZHOU_2, WarehouseReferences::YD_FS_EAST_MALAYSIA, WarehouseReferences::YD_FS_WEST_MALAYSIA])){
$warehousePrefix = 'YD/';
if(strtolower($deliveryAddress->state->name) === 'sabah' || strtolower($deliveryAddress->state->name) === 'labuan' || in_array(strtolower($deliveryAddress->district->name), ['limbang', 'lawas'])) {
$warehousePrefix = 'KK/';
}
if(strtolower($deliveryAddress->state->name) === 'sarawak' && !in_array(strtolower($deliveryAddress->district->name), ['limbang', 'lawas'])) {
$warehousePrefix = 'KU/';
}
if(strtolower($deliveryAddress->state->name) === 'johor') {
$warehousePrefix = 'NX/';
}
}
// tax rebate data
$tax_rebate_refeerence = $request->query('tax_rebate_refeerence');
$supplierTaxRebate = SupplierTaxRebate::where('reference_number', $tax_rebate_refeerence)->first();
if (!$supplierTaxRebate) {
// If not found, throw an error
abort(404, 'Supplier Tax Rebate not found');
}
$data = [
'order' => $order,
'marking' => $warehousePrefix.$deliveryPrefix.'CIEF/'.$customerMarking,
'delivery_address' => $deliveryAddress,
'warehouse_address' => $warehouseAddress,
'warehouse_contacts' => $warehouseContacts,
'remark' => $remark ? $remark->content : '',
'supplierTaxRebate' => $supplierTaxRebate
];
$pdf = LaravelMpdf::loadView('pdfs.tax_rebate_qr', $data, [], [
'title' => 'CIEF_ORDER_NO_'.$order->reference,
'format' => 'A4-L',
'orientation' => 'L',
'margin_top' => '40px',
]);
$exportFileName = 'CIEF_ORDER_NO_'.$order->reference.'.pdf';
$filesystemDriver = Storage::getDefaultDriver();
if($filesystemDriver === 's3'){
$pdfContent = $pdf->output();
return response([ 'src' => AWSS3Helper::S3PDF($exportFileName, $pdfContent) ]);
}
else{
return $pdf->stream($exportFileName);
}
}
}
+1
View File
@@ -35,6 +35,7 @@ class OrderV2Resource extends JsonResource
}),
'storages' => $this->storages ? $this->storages : null, //from middleware
'remarks' => RemarkResource::collection($this->remarks),
'supplier_tax_rebate' => SupplierTaxRebateResource::collection($this->supplierTaxRebate),
'created_at' => $this->created_at->format('d-m-Y')
];
@@ -0,0 +1,28 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class SupplierTaxRebateResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
$orderId = $this->id;
return [
'id' => $this->id,
'supplier_name' => $this->supplier_name,
'supplier_contact_number' => $this->supplier_contact_number,
'reference_number' => $this->reference_number,
'created_at' => $this->created_at->format('d-m-Y'),
'updated_at' => $this->updated_at->format('d-m-Y'),
];
}
}
+9
View File
@@ -169,4 +169,13 @@ class Order extends AbstractModel implements Addressable, Packable, Remarkable,
{
return $this->hasManyDeep(Transaction::class, [PackingList::class], ['owner_id', 'owner_id'], ['id', 'id']);
}
/**
* @return HasMany
*/
public function supplierTaxRebate(): HasMany
{
return $this->HasMany(SupplierTaxRebate::class, 'order_id');
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class SupplierTaxRebate extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'order_id',
'supplier_name',
'supplier_contact_number',
'reference_number'
];
public function order()
{
return $this->belongsTo(Order::class);
}
}
+3 -2
View File
@@ -1,8 +1,9 @@
<?php
return [
// please update resources/assets/vue/app.js if value is changed
// IMPORTANT: please update resources/assets/vue/app.js if value is changed
// 'qr_code_img_url' => 'https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=', // not working
'qr_code_img_url' => 'https://qrcode.tec-it.com/API/QRCode?data=',
// 'qr_code_img_url' => 'https://qrcode.tec-it.com/API/QRCode?data=',
'qr_code_img_url' => 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=', // backup
];
@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateSupplierQrCodesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('supplier_tax_rebates', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('order_id')->references('id')->on('orders')->onDelete('cascade');
$table->string('supplier_name');
$table->string('supplier_contact_number');
$table->string('reference_number');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('supplier_tax_rebates');
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ import Vapor from 'laravel-vapor';
window.Vapor = Vapor;
// define global variable
Vue.prototype.$qr_code_img_url = 'https://qrcode.tec-it.com/API/QRCode?data=';
Vue.prototype.$qr_code_img_url = 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=';
/** Application Injections */
Vue.use(vuelidate);
@@ -0,0 +1,167 @@
<template>
<div class="row">
<div class="col bg-white padding-40 b-rad-lg">
<!-- List QR Code -->
<loading-component v-if="isLoading" style="height: 200px; top: 0;" key="1" color="success"></loading-component>
<div v-else>
<div v-if="step === 'list_qr'">
<div class="row">
<div class="col">
<div class="justify-content-between d-flex">
<h3>List Of Tax Rebate QR Code</h3>
<i class="fa fa-times pointer fs-16 text-black" @click="resetSteps()"></i>
</div>
<div class="row">
<div v-for="i in data.supplier_tax_rebate" :key="i.id" class="col-12 col-md-4 p-t-10 p-b-10">
<div class="padding-20 bg-master-lightest rounded-lg b-a b-grey d-flex flex-column h-100">
<a @click="download(i)" target="_blank">
<div class="peelable-corner overflow-hidden relative text-center">
<img :src="$qr_code_img_url + i.reference_number" class="w-100" style="width: 150px; height: 150px; max-width: 150px; max-height: 150px;">
</div>
</a>
<div class="m-t-15">
<template v-for="(label, field) in fields">
<div class="block m-b-5">
<small class="fs-10 all-caps muted">{{ label }}</small>
<h6 class="no-margin small text-break overflow-wrap m-b-15">{{ i[field] }}</h6>
</div>
</template>
</div>
</div>
</div>
</div>
<div class="row m-t-30">
<div class="col text-center">
<button class="btn btn-large btn-primary" @click="updateStep('1')">Create New QR Code</button>
</div>
</div>
</div>
</div>
</div>
<!-- Confirm Creation -->
<div v-if="step === '1'">
<div class="text-center m-b-30">
<h3>Are you sure you want to create a Tax Rebate QR Code?</h3>
<p>A Tax Rebate QR Code allows suppliers to claim tax rebates. <br>We need some additional information for this
service.
</p>
</div>
<div class="row m-t-15">
<div class="col p-r-5">
<button class="btn btn-lg btn-default w-100 b-rad-none" @click="resetSteps()">Cancel</button>
</div>
<div class="col p-l-5">
<button class="btn btn-lg btn-primary w-100" @click="updateStep('create_qr')">Continue</button>
</div>
</div>
</div>
<!-- Create QR Code Form -->
<div v-if="step === 'create_qr'">
<div class="text-center m-b-30">
<h3>Create Tax Rebate QR Code?</h3>
</div>
<div class="row">
<div class="col">
<validation-wrapper-component class="m-b-15" :validator="$v.parameters.supplier_name">
<label class="text-primary">Supplier Name</label>
<input class="form-control" v-model="parameters.supplier_name">
</validation-wrapper-component>
</div>
</div>
<div class="row">
<div class="col">
<validation-wrapper-component class="m-b-15" :validator="$v.parameters.supplier_contact_number">
<label class="text-primary">Supplier Contact Number</label>
<input class="form-control" v-model="parameters.supplier_contact_number">
</validation-wrapper-component>
</div>
</div>
<div class="row m-t-15">
<div class="col p-r-5">
<button class="btn btn-lg btn-default w-100 b-rad-none" @click="resetSteps()">Cancel</button>
</div>
<div class="col p-l-5">
<button class="btn btn-lg btn-primary w-100" @click="create">Create</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import modalFormHandler from '../../../general/mixins/modalFormHandler';
import { required } from "vuelidate/lib/validators";
export default {
data() {
return {
parameters: {
supplier_name: '',
supplier_contact_number: '',
},
step: this.data.supplier_tax_rebate.length ? 'list_qr' : '1',
qr_code_img_url: this.$qr_code_img_url,
fields: {
reference_number: 'Reference Number',
supplier_name: 'Supplier Name',
// supplier_contact_number: 'Supplier Contact Number'
supplier_contact_number: 'Contact Number'
},
isLoading: false,
};
},
validations: {
parameters: {
supplier_name: { required },
supplier_contact_number: { required },
}
},
methods: {
updateStep(step) {
if (this.step === 'create_qr') {
this.$v.$touch();
if (this.$v.$invalid) return;
}
this.step = step;
},
create() {
this.submit(this.route('api.order.tax_rebate.create', this.data.id), 'post', this.section, true, true);
},
download(param) {
this.isLoading = true;
let url = route('order.taxt_rebate_qr.download', this.data.id) + '?tax_rebate_refeerence=' + param.reference_number;
console.log(url);
if(window.LARAVEL_VAPOR_ENABLED){
this.$store.dispatch('crudRequest', {endpoint: url, method: 'get'})
.then(response =>
{
let success = response.ok;
response.json().then(response => {
if(!success){return;}
if (response.src) {
// window.location.href = response.src;
window.open(response.src, '_blank');
this.isLoading = false;
}
});
}
);
}
else{
window.open(url, '_blank');
this.isLoading = false;
}
},
resetSteps(order) {
this.closeModal();
this.step = this.data.supplier_tax_rebate.length ? 'list_qr' : '1';
},
},
mixins: [modalFormHandler]
}
</script>
@@ -13,7 +13,7 @@
<a @click="download(order)" target="_blank">
<div class="peelable-corner overflow-hidden relative">
<!-- <img :src="'https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl={id:'+order.id+'}'" class="w-100"> -->
<img :src="this.$qr_code_img_url + order.id + '}'" class="w-100">
<img :src="this.$qr_code_img_url + order.id" class="w-100">
</div>
</a>
</div>
@@ -138,6 +138,10 @@
<a @click="download(order)" target="_blank">
<button class="btn btn-sm btn-success bg-master b-rad-none all-caps">Download QR Code Pdf</button>
</a>
<div class="btn btn-sm btn-primary b-rad-none all-caps requestModal" data-type="taxRebateQrCode">Tax Rebate QR Code</div>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="taxRebateQrCode" size="large">
<tax-rebate-qr-code-form-component :section="section" :data="order"></tax-rebate-qr-code-form-component>
</modal-component>
</div>
</div>
</div>
@@ -0,0 +1,65 @@
@extends('layouts.base_pdf')
@section('inner_content')
<style>
.tax-rebate {
background-color: black;
color: white;
padding: 15px;
text-align: center;
font-size: 24px;
margin-bottom: 15px;
font-weight: bold;
}
</style>
<div class="tax-rebate">
<h2 style="font-size: 60px;">Tax Rebate Order</h2>
<p style="font-size: 25px; color: white;">供应商: {{$supplierTaxRebate['supplier_name']}}</p>
<p style="font-size: 25px; color: white;">联络号码: {{$supplierTaxRebate['supplier_contact_number']}}</p>
<!-- <p style="font-size: 25px;">Tax Rebate No: {{$supplierTaxRebate['reference_number']}}</p> -->
</div>
<table style="width: 100%; border-spacing: 0;">
<tbody>
<tr style="border-spacing: 2em;">
<td align="center" width="25%" style="border: 2px solid #000000; border-right: none;">
<table width="100%">
<tbody>
<tr align="center">
<td>
<img src="{{ url(config('qr.qr_code_img_url') . '{id:'.$order->id.'}') }}" style="width: 230px; height: 230px;" />
</td>
</tr>
<tr align="center">
<td>
<h2 style="margin: 0 !important;"><strong>#{{$order->reference}}</strong></h2>
</td>
</tr>
</tbody>
</table>
</td>
<td width="75%" style="border: 2px solid #000000; border-left: none;">
<table style="border-spacing:10px 10px;">
<tbody>
<tr>
<td style="border-bottom: solid 2px #000000;">
<h3 style="font-size: 35px; line-height: 70px">{{$marking}}{{$order->reference}}</h3>
</td>
</tr>
<tr>
<td>
<p style="font-size: 25px; margin-bottom: 0 !important; margin-top: 10px !important;">仓库地址:</p>
<p style="margin-top: 5px !important; margin-bottom: 5px !important; word-wrap: break-word; font-size: 25px;">{{$warehouse_address->state->name.' '.$warehouse_address->district->name.' '.$warehouse_address->street_one.' '.$warehouse_address->street_two}}</p>
<p style="font-size: 25px;">邮编:{{$warehouse_address->postcode}}</p>
<p style="margin-top: 5px !important; margin-bottom: 0px !important; font-size: 25px;">联系:@foreach($warehouse_contacts as $contact){{ $loop->first ? '' : ' / ' }}{{ $contact->phone.' '.$contact->reference }}@endforeach</p>
<p style="font-style: italic; font-weight: bold; margin-top: 5px !important; margin-bottom: 0px !important; font-size: 25px;">{!! $remark !!}</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
@endsection
+3
View File
@@ -22,4 +22,7 @@ Route::group(['prefix' => 'order', 'as' => 'order.', 'namespace' => 'Orders'], f
Route::put('/assign-remark/{id}', 'AssignOrderRemarkController@create')->name('assign.remark');
Route::get('/{id}/shipping-cost', 'ShippingCostController@calculate')->name('shipping.cost');
Route::get('/packages/{id}', 'FetchOrderPackagesController@fetch')->name('packages');
Route::post('/{id}/supplier-tax-rebate/create', 'CreateSupplierTaxRebateController@create')->name('tax_rebate.create');
});
+1
View File
@@ -299,6 +299,7 @@ Route::get('/deliveries/refresh', function(){
})->name('deliveries.refresh');
Route::get('/order/{id}/download', 'Orders\DownloadOrderQrPdfController@download')->name('order.qr.download');
Route::get('/tax-rebate-order/{id}/download', 'Orders\DownloadTaxRebateQrPdfController@download')->name('order.taxt_rebate_qr.download');
Route::get('/report/customclearance/{orderid}', 'Reports\CustomcClearanceReportController@download')->name('report.customclearance');