Merge branch 'master' of gitlab.com:CIEFWorldwideSdnBhd/exchange-2.0 into development

This commit is contained in:
edmondlang
2023-10-04 13:02:34 +08:00
17 changed files with 354 additions and 39 deletions
@@ -16,6 +16,8 @@ class CreatedAfterOrEqual implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('created_at', '>=', Carbon::parse($value));
$table = $builder->getModel()->getTable();
$startDate = Carbon::createFromFormat('d-m-Y', $value)->startOfDay();
return $builder->where("{$table}.created_at", '>=', $startDate);
}
}
@@ -15,6 +15,8 @@ class CreatedBeforeOrEqual implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('created_at', '<=', Carbon::parse($value));
$table = $builder->getModel()->getTable();
$endDate = Carbon::createFromFormat('d-m-Y', $value)->endOfDay();
return $builder->where("{$table}.created_at", '<=', $endDate);
}
}
@@ -14,7 +14,8 @@ class OwnerId implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('owner_id', $value);
$table = $builder->getModel()->getTable();
return $builder->where("{$table}.owner_id", $value);
}
}
@@ -14,7 +14,8 @@ class OwnerType implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('owner_type', $value);
$table = $builder->getModel()->getTable();
return $builder->where("{$table}.owner_type", $value);
}
}
@@ -14,7 +14,8 @@ class StatusIn implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereIn('status', $value);
$table = $builder->getModel()->getTable();
return $builder->whereIn("{$table}.status", $value);
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Booking;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class WithBookingMarkingLike implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->join('transactions as t2', 't2.payment_reference', '=', 'transactions.bill_no')
->join('bookings', function ($join) use ($value) {
$join->on('bookings.id', '=', 't2.owner_id')
->where('bookings.marking', 'LIKE', '%'.$value.'%');
})
->addSelect(['transactions.*','t2.owner_id as bookingId', 'bookings.marking as bookingMarking']);
}
}
@@ -24,7 +24,7 @@ class GeneratesBookingMarking
*/
public function execute(): int {
$marking = mt_rand(20000, 99999);
$marking = mt_rand(100000, 999999);
return !$this->bookingMarkingExists->execute($marking) ? $marking : self::execute();
}
@@ -0,0 +1,67 @@
<?php
namespace App\Classes\Modules\Companies\ControllersLogic;
use App\Classes\ValueObjects\Constants\TransactionType;
use ZipArchive;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Models\Company;
class BulkDownloadCustomerInvoicesLogic
{
/**
* @param Request $request
* @return void
* @throws MalformedRequestException
*/
public function execute(Request $request)
{
$company = Company::where('reference', $request->input('marking'))->first();
$startDate = $request->input('startDate');
$endDate = $request->input('endDate');
if ($company) {
$invoicebookings = $company->bookings()->whereDate('created_at', '>=', $startDate)
->whereDate('created_at', '<=', $endDate)
->whereHas('transactions', function ($query) {
$query->where('transactions.type', TransactionType::INVOICE)
->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
})->orderBy('created_at')->get();
if (count($invoicebookings) > 0) {
$zip_file = "invoices_{$startDate}_to_{$endDate}_{$company->reference}.zip";
$zip = new ZipArchive();
if ($zip->open($zip_file, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE)) {
foreach($invoicebookings as $booking) {
$invoice_file = $booking->documents()->where('document_type', DocumentType::INVOICE)->whereNull('deleted_at')->first()->files()->first();
$zip->addFile(Storage::disk('documents')->path($invoice_file->file->file_info->original->file), 'invoice-' . $booking->created_at->format('d_m_Y') . '_' . $booking->marking . '.pdf');
}
$zip->close();
while (ob_get_level()) {
ob_end_clean();
}
return response()->download($zip_file);
}
} else {
return response()->json([
'status' => 'Failed',
'message' => 'No invoices found for this customer in the given date range.',
]);
}
} else {
return response()->json([
'status' => 'Failed',
'message' => 'Customer not found.',
]);
}
}
}
@@ -34,7 +34,8 @@ class GeneratesTransactionBillNumber
$attempt = 0;
while ($attempt < 10) { // Retry up to 10 times
$billNumber = $prefix . $date->format('Y') . $date->format('m') . '-' . microtime(true);
// $billNumber = $prefix . $date->format('Y') . $date->format('m') . '-' . intval(microtime(true));
$billNumber = $prefix . $date->format('Y') . $date->format('m') . '-' . mt_rand(1000000, 9999999);
if (!$this->checksIfTransactionBillNumberExists->execute($billNumber)) {
return $billNumber;
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Companies;
use App\Classes\Modules\Companies\ControllersLogic\BulkDownloadCustomerInvoicesLogic;
use Illuminate\Http\Request;
class BulkDownloadCustomerInvoicesController
{
/**
* @param Request $request
* @param BulkDownloadCustomerInvoicesLogic $logic
* @return void
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function download(Request $request, BulkDownloadCustomerInvoicesLogic $logic) {
return $logic->execute($request);
}
}
@@ -0,0 +1,103 @@
<template>
<div class="row">
<div class="col">
<div class="row m-b-5" @keyup.enter="submitSearch">
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.marking">
<label class="all-caps">Marking</label>
<!-- <input type="text" class="form-control" v-model="parameters.marking" :class="[{ 'not-allowed': hasMarkingParameter }]" :disabled="hasMarkingParameter"> -->
<input type="text" class="form-control" v-model="parameters.marking" :class="[{ 'not-allowed': hasMarking }]" :disabled="hasMarking">
</validation-wrapper-component>
</div>
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.startDate">
<label class="all-caps">Start Date</label>
<date-picker-component v-model.lazy="parameters.startDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.endDate">
<label class="all-caps">End Date</label>
<date-picker-component v-model.lazy="parameters.endDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col col-md-auto d-flex justify-content-center align-items-center">
<button type="button" class="btn btn-lg btn-primary fs-11 w-100"
@click="submitSearch()">Download</button>
</div>
</div>
<loading-component style="height: 100px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
<div class="row align-items-center justify-content-center bg-white p-t-50 p-b-50 p-l-15 p-r-15 margin-15"
v-if="!isLoading && returnData">
<div class="col-10">
<div class="row align-items-center justify-content-center hint-text">
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png"
class="w-100 hint-text" /></div>
</div>
<div class="row text-center">
<div class="col">
<div class="row m-t-20">
<div class="col">
<p class="all-caps no-margin fs-11" style="letter-spacing: 2px;">{{ returnData.message
}}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { required } from "vuelidate/lib/validators";
export default {
data() {
return {
key: 1,
returnData: null,
isLoading: false,
hasMarking: false,
parameters: {
marking: null,
startDate: null,
endDate: null,
},
}
},
validations: {
parameters: {
marking: { required },
startDate: { required },
endDate: { required },
}
},
created() {
const urlSearchParams = new URLSearchParams(window.location.search);
if (urlSearchParams.has('marking')) {
this.parameters.marking = urlSearchParams.get('marking');
}
this.hasMarkingParameter();
},
methods: {
submitSearch() {
this.isLoading = true;
this.submit(this.route('api.customers.invoices'), 'post', this.section, false, false);
},
errorHandler(error) {
this.isLoading = false;
console.log(error);
},
successHandler(response) {
this.isLoading = false;
this.returnData = response;
},
hasMarkingParameter() {
const urlSearchParams = new URLSearchParams(window.location.search);
this.hasMarking = urlSearchParams.has('marking');
},
},
}
</script>
@@ -1,6 +1,29 @@
<template>
<div class="row">
<div class="col">
<div class="row m-b-5" @keyup.enter="submitSearch">
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.reference_no">
<label class="all-caps">Booking Reference</label>
<input type="text" class="form-control" v-model="parameters.reference_no">
</validation-wrapper-component>
</div>
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.startDate">
<label class="all-caps">Start Date</label>
<date-picker-component v-model.lazy="parameters.startDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.endDate">
<label class="all-caps">End Date</label>
<date-picker-component v-model.lazy="parameters.endDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col col-md-auto d-flex justify-content-center align-items-center">
<button type="button" class="btn btn-lg btn-primary fs-11 w-100" @click="submitSearch()">Search</button>
</div>
</div>
<div class="row">
<div class="col">
<div class="row padding-10">
@@ -11,7 +34,7 @@
<div class="col-2 fs-10 text-right">Balance</div>
</div>
<list-component :key="key" section="topUp" :endpoint="route('api.transaction.wallet.list')" :options="{status_in: [2, 3], owner_type: 'App\\Models\\Wallet', owner_id: wallet_id, per_page: showingTransactionCount}">
<list-component :key="key" section="topUp" :endpoint="route('api.transaction.wallet.list')" :options="options">
<template slot="list" slot-scope="{data}">
<customer-wallet-transaction-component :data="data" :deciamls="deciamls"></customer-wallet-transaction-component>
</template>
@@ -39,12 +62,50 @@ export default {
data(){
return {
key: 1,
parameters: {
reference_no: null,
startDate: null,
endDate: null,
},
options: {
status_in: [2, 3],
owner_type: 'App\\Models\\Wallet',
owner_id: this.wallet_id,
per_page: this.showingTransactionCount
}
}
},
validations: {
parameters: {
reference_no: { },
startDate: { },
endDate: { },
}
},
watch: {
showingTransactionCount() {
this.options.per_page = this.showingTransactionCount;
this.key ++;
}
}
},
methods: {
submitSearch() {
delete this.options.with_booking_marking_like;
delete this.options.created_after_or_equal;
delete this.options.created_before_or_equal;
if (this.parameters.reference_no) {
this.options.with_booking_marking_like = this.parameters.reference_no
}
if (this.parameters.startDate) {
this.options.created_after_or_equal = this.parameters.startDate
}
if (this.parameters.endDate) {
this.options.created_before_or_equal = this.parameters.endDate
}
this.key ++;
},
},
}
</script>
@@ -1,36 +1,37 @@
<template>
<div class="row">
<div class="col">
<div class="row">
<div class="col-8">
<div class="row p-b-5 b-b b-grey m-b-10 m-l-0 m-r-0">
<div class="col no-padding">
<h6>Wallet Transaction History</h6>
</div>
</div>
<div class="row m-t-10 m-b-10">
<div class="col">
<div class="d-flex align-items-center h-100">
<span class="btn btn-md fs-11 bg-primary text-white fs-12 bold m-r-5" :class="[{'bg-primary-darker': showingPreciseAmount}]" @click="showingPreciseAmount=!showingPreciseAmount">{{ showingPreciseAmount ? 'Showing Precise Wallet Transaction' : 'Show Precise Wallet Transaction'}}</span>
<a v-if="company" :href="route('wallet.details-export', company.reference, showingPreciseAmount)" target="_blank" class="btn btn-md btn-primary fs-11"><i class="fa fa-download m-r-5"></i>{{ showingPreciseAmount ? 'Download Precise Transaction' : 'Download Transaction'}}</a>
<loading-component style="height: 200px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
<div v-if="!isLoading">
<div class="row">
<div class="col-8">
<div class="row p-b-5 b-b b-grey m-b-10 m-l-0 m-r-0">
<div class="col no-padding">
<h6>Wallet Transaction History</h6>
</div>
</div>
<div class="col-2">
<validation-wrapper-component selectable :validator="$v.showingTransactionCount">
<label>Showing Rows</label>
<select-component :options="[5, 10, 20, 30, 50]" v-model="showingTransactionCount"></select-component>
</validation-wrapper-component>
<div class="row m-t-10 m-b-10">
<div class="col">
<div class="d-flex align-items-center h-100">
<span class="btn btn-md fs-11 bg-primary text-white fs-12 m-r-5" :class="[{'bg-primary-darker': showingPreciseAmount}]" @click="showingPreciseAmount=!showingPreciseAmount">{{ showingPreciseAmount ? 'Showing Precise Wallet Transaction' : 'Show Precise Wallet Transaction'}}</span>
<a v-if="company" :href="route('wallet.details-export', company.reference, showingPreciseAmount)" target="_blank" class="btn btn-md btn-primary fs-11"><i class="fa fa-download m-r-5"></i>{{ showingPreciseAmount ? 'Download Precise Transaction' : 'Download Transaction'}}</a>
</div>
</div>
<div class="col-2">
<validation-wrapper-component selectable :validator="$v.showingTransactionCount">
<label>Showing Rows</label>
<select-component :options="[5, 10, 20, 30, 50]" v-model="showingTransactionCount"></select-component>
</validation-wrapper-component>
</div>
</div>
<div v-show="!showingPreciseAmount">
<customer-transaction-component :deciamls="2" :wallet_id="wallet_id" :showingTransactionCount="showingTransactionCount"></customer-transaction-component>
</div>
<div v-show="showingPreciseAmount">
<customer-transaction-component :deciamls="5" :wallet_id="wallet_id" :showingTransactionCount="showingTransactionCount"></customer-transaction-component>
</div>
</div>
<div v-show="!showingPreciseAmount">
<customer-transaction-component :deciamls="2" :wallet_id="wallet_id" :showingTransactionCount="showingTransactionCount"></customer-transaction-component>
</div>
<div v-show="showingPreciseAmount">
<customer-transaction-component :deciamls="5" :wallet_id="wallet_id" :showingTransactionCount="showingTransactionCount"></customer-transaction-component>
</div>
</div>
<div class="col-3 m-l-15">
<div v-if="!isLoading">
<div class="col-3 m-l-15">
<wallet-component :data="company" :creditable=true></wallet-component>
<div class="row m-t-20">
<div class="col">
@@ -68,7 +69,6 @@
</div>
</div>
</div>
<loading-component style="height: 200px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
</div>
</div>
</div>
@@ -35,7 +35,7 @@
<div class="col-auto">
<div class="btn btn-xs p-l-15 p-r-20 b-rad-none font-heading btn-rounded bg-white" @click="reload = true"><i class="fa fa-plus fs-8 m-r-5"></i> Reload</div>
</div>
<div class="col-auto p-l-0" v-if="!mini">
<div class="col-auto p-l-0" v-if="!mini && data.wallet">
<a class="text-white fs-10" :href="route('wallet.details', data.reference)" target="_blank">Transaction History<i class="fa fa-angle-right p-l-5"></i></a>
</div>
</div>
@@ -52,9 +52,18 @@
<wallet-top-up-form-component :data="data" :amount="(!data.wallet ? amount : (Math.round((((amount - data.wallet.amount) < '0.00' ? '0.00' : (amount - data.wallet.amount)) + Number.EPSILON) * 100) / 100).toFixed(2))" :creditable="creditable"></wallet-top-up-form-component>
</div>
</div>
<div class="row m-t-20" v-if="$store.getters.isAdmin">
<div class="row m-t-10" v-if="$store.getters.isAdmin">
<div class="col">
<a :href="route('account.statment', data.reference)">View Account Statement<i class="fa fa-angle-right p-l-5"></i></a>
<div class="row m-t-10" v-if="$store.getters.isSuperAdmin">
<div class="col">
<a :href="route('account.statment', data.reference)">View Account Statement<i class="fa fa-angle-right p-l-5"></i></a>
</div>
</div>
<div class="row m-t-10">
<div class="col">
<a :href="downloadInvoiceUrl()">Download All Invoices<i class="fa fa-angle-right p-l-5"></i></a>
</div>
</div>
</div>
</div>
</div>
@@ -79,6 +88,11 @@
default: false
}
},
methods: {
downloadInvoiceUrl(){
return this.route('customers.invoices') + '?marking=' + this.data.reference;
},
},
data(){
return {
reload: false,
@@ -0,0 +1,8 @@
@extends('layouts.base_portal')
@section('inner_content')
<div class="row" v-if="$store.getters.isAdmin">
<div class="col">
<bulk-download-invoice-component section="BulkDownloadInvoiceComponent"></bulk-download-invoice-component>
</div>
</div>
@endsection
+1
View File
@@ -32,6 +32,7 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
Route::post('/import/upload-honey-trap', 'Imports\ImportHoneyTrapController@import')->name('honey_trap.upload');
Route::post('/import/upload-import-invoices', 'Imports\ImportStatementInvoiceController@import')->name('import_invoices.upload');
Route::post('/import/upload-import-receipt', 'Imports\ImportStatementReceiptsController@import')->name('import_receipts.upload');
Route::post('/customers/invoices', 'Companies\BulkDownloadCustomerInvoicesController@download')->name('customers.invoices');
require __DIR__ . '/company.php';
+4
View File
@@ -145,6 +145,10 @@ Route::get('/purchase_orders', function () {
return view('pages.purchase_orders');
})->name('purchase_orders');
Route::get('/customers/invoices', function () {
return view('pages.customer_invoices_bulk_download');
})->name('customers.invoices');
Route::get('/support', function () {
return view('pages.customer_support', [
'marking' => null,