Merge branch 'revert-304379b1' into 'master'

REVERT THE REVERT REINSTATING THE COMMIT WITH MESSAGE: "Proof of concept - Vue Polling a workaround for AWS API Gateway limitation"

See merge request CIEFWorldwideSdnBhd/exchange-2.0!158
This commit is contained in:
Dillon Ngo
2024-02-11 10:30:08 +00:00
60 changed files with 2380 additions and 152 deletions
@@ -0,0 +1,11 @@
<?php
namespace App\Classes\Exceptions;
use App\Classes\ValueObjects\Constants\HttpStatus;
final class JobResourceNotFoundException extends ServiceApiException {
public function __construct(?string $message = null) {
parent::__construct($message ?? 'Unable to find the requested resource', HttpStatus::RESOURCE_NOT_FOUND);
}
}
@@ -17,6 +17,7 @@ use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Classes\Exceptions\JobResourceNotFoundException;
abstract class AbstractControllerLogic
{
@@ -67,7 +68,19 @@ abstract class AbstractControllerLogic
return $response;
} catch (ErrorException|GeneralExceptions $exception){
log::error($exception);
if ($exception instanceof JobResourceNotFoundException) {
Log::error(sprintf(
"Uncaught exception '%s' with message '%s' in %s:%d",
get_class($exception),
$exception->getMessage(),
$exception->getTrace()[0]['file'],
$exception->getTrace()[0]['line']
));
}
else{
log::error($exception);
}
return (new ApiResponseObject($this->getNotificationTitle().' failed', $exception->getMessage(),
$exception->getCode() ? $exception->getCode() : HttpStatus::SERVER_ERROR))->handler();
@@ -4,9 +4,11 @@ namespace App\Classes\General\Eloquent;
use App\Classes\Exceptions\ResourceNotFoundException;
use App\Classes\Exceptions\JobResourceNotFoundException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Psy\Exception\ErrorException;
use Illuminate\Support\Facades\Log;
abstract class AbstractFetchRecord extends AbstractGetRecord
{
@@ -27,12 +29,18 @@ abstract class AbstractFetchRecord extends AbstractGetRecord
* @return Model
* @throws ResourceNotFoundException
*/
public function getResults(Builder $query): Model {
public function getResults(Builder $query, array $param = []): Model {
if(!$query->exists()){
throw new ResourceNotFoundException('Unable to find any record based on the criteria provided');
$table = $query->getModel()->getTable();
if($table ==='job_results'){
throw new JobResourceNotFoundException('Unable to find any job based on the criteria provided');
}
else{
throw new ResourceNotFoundException('Unable to find any record based on the criteria provided');
}
}
return $query->first();
}
}
}
@@ -30,11 +30,25 @@ abstract class AbstractGetRecord
return $this->filters->only(self::DECORATION_FILTERS);
}
// /**
// * @param null|string $json
// * @return array
// */
// public function deserializeFilters(?string $json): array {
// return $json !== null ? collect(json_decode($json))->toArray() : [];
// }
/**
* @param null|string $json
* @param null|string $param
* @return array
*/
public function deserializeFilters(?string $json): array {
public function deserializeFilters($param): array {
if(gettype($param) == "array"){
$json = implode(',', $param);
}
else{
$json = $param;
}
return $json !== null ? collect(json_decode($json))->toArray() : [];
}
@@ -50,9 +64,9 @@ abstract class AbstractGetRecord
* @param array $filters
* @return mixed
*/
public function handler(array $filters){
public function handler(array $filters, array $params = []){
$this->filters = collect($filters);
return $this->getResults($this->applyFiltersToQuery());
return $this->getResults($this->applyFiltersToQuery(), $params);
}
@@ -65,6 +79,6 @@ abstract class AbstractGetRecord
* @param Builder $query
* @return mixed
*/
abstract function getResults(Builder $query);
abstract function getResults(Builder $query, array $params = []);
}
}
@@ -17,11 +17,11 @@ abstract class AbstractListRecord extends AbstractGetRecord
* @return mixed
* @throws MalformedRequestException
*/
public function execute(array $filters = []){
public function execute(array $filters = [], array $param = []){
try{
return $this->handler($filters);
return $this->handler($filters, $param);
} catch (QueryException $exception){
log::error($exception);
@@ -30,18 +30,24 @@ abstract class AbstractListRecord extends AbstractGetRecord
}
/**
* @param Builder $query
* @return mixed
*/
public function getResults(Builder $query) {
public function getResults(Builder $query, array $param = []) {
$filters = $this->getDecorationFilters();
if($filters->has('order_by')){
$query = $query->orderBy($filters->get('order_by')->column, $filters->get('order_by')->DESC ? 'DESC': 'ASC');
}
return $filters->has('per_page') ? $query->paginate($filters->get('per_page')) : $query->get();
if(!empty($param)){
return $filters->has('per_page') ? $query->paginate($filters->get('per_page'), ['*'], 'page', $param['page']) : $query->get(); //page data from query parameters e.g ?page=1
}
else{
return $filters->has('per_page') ? $query->paginate($filters->get('per_page')) : $query->get();
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class JobId implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('job_id', $value);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class OrderByIdDesc implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->orderBy('id', 'desc');
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class RequestSignature implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('request_signature', $value);
}
}
@@ -0,0 +1,18 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class ResultNotNull implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereNotNull('result');
}
}
+24
View File
@@ -2,6 +2,7 @@
namespace App\Classes\General;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
@@ -42,4 +43,27 @@ class Helper
}
}
}
/**
* @param null|string $param
* @return array
*/
static function deserializeFilters($param): array {
if(gettype($param) == "array"){
$json = implode(',', $param);
}
else{
$json = $param;
}
return $json !== null ? collect(json_decode($json))->toArray() : [];
}
/**
* @param ResourceCollection $collection
* @return array
*/
static function collectionResponse(ResourceCollection $collection){
return json_decode($collection->response()->getContent(), true);
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace App\Classes\Jobs;
use App\Classes\Modules\Bookings\Processors\ListBookingsJobProcessor;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\JobResult;
use Illuminate\Support\Facades\Log;
class ListBookingsJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 900;
/** @var ListGenericJobObject */
private $listGenericJobObject;
private $jobId;
/**
* ListBookingsJob constructor.
* @param ListGenericJobObject $listGenericJobObject
*/
public function __construct(ListGenericJobObject $listGenericJobObject)
{
$this->listGenericJobObject = $listGenericJobObject;
}
public function handle()
{
$rawPayload = $this->job->payload();
if(isset($rawPayload['data']['commandName'])){
$this->listGenericJobObject->setJobCommandName($rawPayload['data']['commandName']);
}
if(isset($rawPayload['data']['command'])){
$this->listGenericJobObject->setJobCommand($rawPayload['data']['command']);
}
$result = (App()->make(ListBookingsJobProcessor::class))->execute($this->listGenericJobObject);
}
public function getJobId(){
return $this->job->getJobId();
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace App\Classes\Jobs;
use App\Classes\Modules\Documents\Processors\ListDocumentsJobProcessor;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\JobResult;
use Illuminate\Support\Facades\Log;
class ListDocumentsJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 900;
/** @var ListGenericJobObject */
private $listGenericJobObject;
private $jobId;
/**
* ListDocumentsJob constructor.
* @param ListGenericJobObject $listGenericJobObject
*/
public function __construct(ListGenericJobObject $listGenericJobObject)
{
$this->listGenericJobObject = $listGenericJobObject;
}
public function handle()
{
$rawPayload = $this->job->payload();
if(isset($rawPayload['data']['commandName'])){
$this->listGenericJobObject->setJobCommandName($rawPayload['data']['commandName']);
}
if(isset($rawPayload['data']['command'])){
$this->listGenericJobObject->setJobCommand($rawPayload['data']['command']);
}
$result = (App()->make(ListDocumentsJobProcessor::class))->execute($this->listGenericJobObject);
//cief todo: Insert into DB: job id, query result, timestamp
// Store the result in the job_results table
//cief todo: why cannot save data in table like this
// $model = new JobResult();
// $model->job_id = $this->job->getJobId();
// $model->result = json_encode($result);
// $model->save();
// Log::error(json_encode($model->id));
}
public function getJobId(){
return $this->job->getJobId();
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace App\Classes\Jobs;
use App\Classes\Modules\Transactions\Processors\ListTransactionsJobProcessor;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\JobResult;
use Illuminate\Support\Facades\Log;
class ListTransactionsJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 900;
/** @var ListGenericJobObject */
private $listGenericJobObject;
private $jobId;
/**
* ListTransactionsJob constructor.
* @param ListGenericJobObject $listGenericJobObject
*/
public function __construct(ListGenericJobObject $listGenericJobObject)
{
$this->listGenericJobObject = $listGenericJobObject;
}
public function handle()
{
$rawPayload = $this->job->payload();
if(isset($rawPayload['data']['commandName'])){
$this->listGenericJobObject->setJobCommandName($rawPayload['data']['commandName']);
}
if(isset($rawPayload['data']['command'])){
$this->listGenericJobObject->setJobCommand($rawPayload['data']['command']);
}
$result = (App()->make(ListTransactionsJobProcessor::class))->execute($this->listGenericJobObject);
}
public function getJobId(){
return $this->job->getJobId();
}
}
@@ -0,0 +1,75 @@
<?php
namespace App\Classes\Modules\Bookings\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Jobs\ListBookingsJob;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Classes\Modules\Jobs\Services\CreatesJobResult;
class ListBookingJobLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'List Booking Job',
'message' => 'You have successfully submit a job to list bookings'
];
}
/** @var CreatesJobResult */
private $createsJobResult;
/**
* ListPackingListsJobLogic constructor.
* @param CreatesJobResult $createsJobResult
*/
public function __construct(CreatesJobResult $createsJobResult)
{
$this->createsJobResult = $createsJobResult;
}
/**
* @param Request $request
* @return JsonResponse
*/
public function logic(Request $request) : JsonResponse
{
$jobId = uniqid();
$user = Auth::user();
$userInfo = (object) [
'type' => $user->type,
];
$userInfoJson = json_encode($userInfo);
$requestSignature = md5($userInfoJson . $request->fullUrl());
$listGenericJobObject = new ListGenericJobObject(
$request->fullUrl(),
$request->all(),
$requestSignature,
null,
$jobId,
$userInfo
);
ListBookingsJob::dispatch($listGenericJobObject);
$result = [];
$result['job_id'] = $jobId;
$this->createsJobResult->execute($listGenericJobObject);
return $this->response(['data' => $result]);
}
}
@@ -0,0 +1,46 @@
<?php
namespace App\Classes\Modules\Bookings\Processors;
use App\Classes\Modules\Bookings\Services\ListsBookings;
use App\Classes\Modules\Jobs\Processors\UpdateJobResultProcessor;
use App\Classes\General\Helper;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use App\Http\Resources\ListBookingJobResource;
class ListBookingsJobProcessor
{
/** @var ListsBookings */
private $listsBookings;
/** @var UpdateJobResultProcessor */
private $updateJobResultProcessor;
/**
* ListBookingsJobProcessor constructor.
* @param ListsBookings $listsBookings
* @param UpdateJobResultProcessor $updateJobResultProcessor
*/
public function __construct(ListsBookings $listsBookings, UpdateJobResultProcessor $updateJobResultProcessor)
{
$this->listsBookings = $listsBookings;
$this->updateJobResultProcessor = $updateJobResultProcessor;
}
/**
* @param ListGenericJobObject $listGenericJobObject
* @return void
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\JobResourceNotFoundException
*/
public function execute(ListGenericJobObject $listGenericJobObject) {
$query = $this->listsBookings->execute($this->listsBookings->deserializeFilters($listGenericJobObject->getPayload()['filters']), ['page' => $listGenericJobObject->getPayload()['page']]);
foreach ($query->items() as &$item) {
$item['userInfo'] = $listGenericJobObject->getUserInfo();
}
$resultCurrent = Helper::collectionResponse(ListBookingJobResource::collection($query));
$this->updateJobResultProcessor->execute($listGenericJobObject, $resultCurrent);
}
}
@@ -0,0 +1,75 @@
<?php
namespace App\Classes\Modules\Documents\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Jobs\Services\CreatesJobResult;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use App\Classes\Jobs\ListDocumentsJob;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ListDocumentJobLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'List Document Job',
'message' => 'You have successfully submit a job to list documents'
];
}
/** @var CreatesJobResult */
private $createsJobResult;
/**
* ListDocumentJobLogic constructor.
* @param CreatesJobResult $createsJobResult
*/
public function __construct(CreatesJobResult $createsJobResult)
{
$this->createsJobResult = $createsJobResult;
}
/**
* @param Request $request
* @return JsonResponse
*/
public function logic(Request $request) : JsonResponse
{
$jobId = uniqid();
$user = Auth::user();
$userInfo = (object) [
'email' => $user->email,
'type' => $user->type,
];
$userInfoJson = json_encode($userInfo);
$requestSignature = md5($userInfoJson . $request->fullUrl());
$listGenericJobObject = new ListGenericJobObject(
$request->fullUrl(),
$request->all(),
$requestSignature,
null,
$jobId,
$userInfo
);
ListDocumentsJob::dispatch($listGenericJobObject);
$result = [];
$result['job_id'] = $jobId;
$this->createsJobResult->execute($listGenericJobObject);
return $this->response(['data' => $result]);
}
}
@@ -0,0 +1,47 @@
<?php
namespace App\Classes\Modules\Documents\Processors;
use App\Classes\Modules\Documents\Services\ListsDocuments;
use App\Classes\Modules\Jobs\Processors\UpdateJobResultProcessor;
use App\Classes\General\Helper;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use App\Http\Resources\ListDocumentJobResource;
class ListDocumentsJobProcessor
{
/** @var ListsDocuments */
private $listsDocuments;
/** @var UpdateJobResultProcessor */
private $updateJobResultProcessor;
/**
* ListDocumentsJobProcessor constructor.
* @param ListsDocuments $listsDocuments
* @param UpdateJobResultProcessor $updateJobResultProcessor
*/
public function __construct(ListsDocuments $listsDocuments, UpdateJobResultProcessor $updateJobResultProcessor)
{
$this->listsDocuments = $listsDocuments;
$this->updateJobResultProcessor = $updateJobResultProcessor;
}
/**
* @param ListGenericJobObject $listGenericJobObject
* @return void
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\JobResourceNotFoundException
*/
public function execute(ListGenericJobObject $listGenericJobObject) {
$query = $this->listsDocuments->execute($this->listsDocuments->deserializeFilters($listGenericJobObject->getPayload()['filters']), ['page' => $listGenericJobObject->getPayload()['page']]);
foreach ($query->items() as &$item) {
$item['userInfo'] = $listGenericJobObject->getUserInfo();
}
$resultCurrent = Helper::collectionResponse(ListDocumentJobResource::collection($query));
$this->updateJobResultProcessor->execute($listGenericJobObject, $resultCurrent);
}
}
@@ -0,0 +1,51 @@
<?php
namespace App\Classes\Modules\Jobs\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Jobs\Processors\FetchesJobResultProcessor;
use App\Http\Resources\JobResultResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class FetchJobResultLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Retrieved Data',
'message' => 'You have successfully retrieved data'
];
}
/** @var FetchesJobResultProcessor */
private $fetchesJobResultProcessor;
/**
* FetchJobResultLogic constructor.
* @param FetchesJobResultProcessor $fetchesJobResultProcessor
*/
public function __construct(FetchesJobResultProcessor $fetchesJobResultProcessor)
{
$this->fetchesJobResultProcessor = $fetchesJobResultProcessor;
}
/**
* @param Request $request
* @return JsonResponse
* @throws \App\Classes\Exceptions\AccessForbiddenException
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\RequestValidationException
*/
public function logic(Request $request) : JsonResponse
{
$query = $this->fetchesJobResultProcessor->execute($request);
return $this->resourceResponse(new JobResultResource($query));
}
}
@@ -0,0 +1,118 @@
<?php
namespace App\Classes\Modules\Jobs\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class ListGenericJobObject implements DataTransferObject
{
/** @var string */
private $name;
/** @var array */
private $payload;
/** @var string */
private $jobId;
/** @var string */
private $requestSignature;
/** @var string */
private $resultSignature;
/** @var object */
private $userInfo;
/** @var string */
private $jobCommandName;
/** @var string */
private $jobCommand;
public function __construct(string $name, array $payload, string $requestSignature, ?string $resultSignature, string $jobId, object $userInfo = null)
{
$this->name = $name;
$this->payload = $payload;
$this->jobId = $jobId;
$this->requestSignature = $requestSignature;
$this->resultSignature = $resultSignature;
$this->userInfo = $userInfo;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return array
*/
public function getPayload(): array
{
return $this->payload;
}
/**
* @return string
*/
public function getJobId(): string
{
return $this->jobId;
}
/**
* @return string
*/
public function getRequestSignature(): string
{
return $this->requestSignature;
}
/**
* @return string
*/
public function getResultSignature(): ?string
{
return $this->resultSignature;
}
/**
* @return object
*/
public function getUserInfo(): object
{
return $this->userInfo;
}
/**
* @return string
*/
public function getJobCommandName(): string
{
return $this->jobCommandName;
}
/**
* @return string
*/
public function getJobCommand(): string
{
return $this->jobCommand;
}
public function setJobCommandName(string $jobCommandName)
{
$this->jobCommandName = $jobCommandName;
}
public function setJobCommand(string $jobCommand)
{
$this->jobCommand = $jobCommand;
}
}
@@ -0,0 +1,60 @@
<?php
namespace App\Classes\Modules\Jobs\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class UpdateJobResultObject implements DataTransferObject
{
/** @var string */
private $result;
/** @var string */
private $resultSignature;
/** @var string */
private $jobCommandName;
/** @var string */
private $jobCommand;
public function __construct(string $result, string $resultSignature, string $jobCommandName, string $jobCommand)
{
$this->result = $result;
$this->resultSignature = $resultSignature;
$this->jobCommandName = $jobCommandName;
$this->jobCommand = $jobCommand;
}
/**
* @return string
*/
public function getResult(): string
{
return $this->result;
}
/**
* @return array
*/
public function getResultSignature(): string
{
return $this->resultSignature;
}
/**
* @return string
*/
public function getJobCommandName(): string
{
return $this->jobCommandName;
}
/**
* @return string
*/
public function getJobCommand(): string
{
return $this->jobCommand;
}
}
@@ -0,0 +1,47 @@
<?php
namespace App\Classes\Modules\Jobs\Processors;
use App\Classes\Exceptions\JobResourceNotFoundException;
use App\Classes\Modules\Jobs\Services\FetchesJobResult;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class FetchesJobResultProcessor
{
/** @var FetchesJobResult */
private $fetchesJobResult;
/**
* FetchesJobResultProcessor constructor.
* @param FetchesJobResult $fetchesJobResult
*/
public function __construct(FetchesJobResult $fetchesJobResult)
{
$this->fetchesJobResult = $fetchesJobResult;
}
/**
* @param Request $request
* @return Model
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\JobResourceNotFoundException
* @throws \App\Classes\Exceptions\ResourceNotFoundException
*/
public function execute(Request $request){
$res1 = $this->fetchesJobResult->execute(['job_id' => $request->route('job_id')]);
if($request->route('is_last')){
$res2 = $this->fetchesJobResult->execute(['request_signature' => $res1->request_signature, 'result_not_null' => true, 'order_by_id_desc' => true]);
return $res2;
}
if(!$res1->result){
throw new JobResourceNotFoundException('Unable to find any job based on the criteria provided');
}
return $res1;
}
}
@@ -0,0 +1,64 @@
<?php
namespace App\Classes\Modules\Jobs\Processors;
use App\Classes\Modules\Jobs\Services\UpdatesJobResult;
use App\Classes\Modules\Jobs\Services\FetchesJobResult;
use App\Classes\Exceptions\JobResourceNotFoundException;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use App\Classes\Modules\Jobs\DataTransferObjects\UpdateJobResultObject;
class UpdateJobResultProcessor
{
/** @var FetchesJobResult */
private $fetchesJobResult;
/** @var UpdatesJobResult */
private $updatesJobResult;
/**
* UpdateJobResultProcessor constructor.
* @param FetchesJobResult $fetchesJobResult
* @param UpdatesJobResult $updatesJobResult
*/
public function __construct(FetchesJobResult $fetchesJobResult, UpdatesJobResult $updatesJobResult)
{
$this->fetchesJobResult = $fetchesJobResult;
$this->updatesJobResult = $updatesJobResult;
}
/**
* @param ListGenericJobObject $listGenericJobObject
* @param array $resultCurrent
* @return void
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\JobResourceNotFoundException
*/
public function execute(ListGenericJobObject $listGenericJobObject, $resultCurrent) {
$jobResultCurrent = $this->fetchesJobResult->execute(['job_id' => $listGenericJobObject->getJobId()]);
$resultCurrentJson = json_encode($resultCurrent);
$resultSignatureCurrent = md5($resultCurrentJson);
try{
$jobResultExisting = $this->fetchesJobResult->execute(['request_signature' => $jobResultCurrent->request_signature, 'result_not_null' => true, 'order_by_id_desc' => true]);
$resultSignatureExisting = $jobResultExisting->result_signature;
//if($resultSignatureExisting != $resultSignatureCurrent){
$this->updateJobResult($jobResultCurrent, $resultCurrentJson, $resultSignatureCurrent, $listGenericJobObject->getJobCommandName(), $listGenericJobObject->getJobCommand());
//}
} catch (JobResourceNotFoundException $exception){
$this->updateJobResult($jobResultCurrent, $resultCurrentJson, $resultSignatureCurrent, $listGenericJobObject->getJobCommandName(), $listGenericJobObject->getJobCommand());
}
}
private function updateJobResult($jobResultCurrent, $resultCurrentJson, $resultSignatureCurrent, $jobCommandName, $jobCommand){
$updateJobResultObject = new UpdateJobResultObject(
$resultCurrentJson,
$resultSignatureCurrent,
$jobCommandName,
$jobCommand
);
$create = $this->updatesJobResult->execute($jobResultCurrent, $updateJobResultObject);
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Classes\Modules\Jobs\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Models\JobResult;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
class CreatesJobResult extends AbstractUpdateRecord
{
/**
* @param ListGenericJobObject $listGenericJobObject
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(ListGenericJobObject $listGenericJobObject)
{
$model = new JobResult();
$model->job_id = $listGenericJobObject->getJobId();
$model->request_signature = $listGenericJobObject->getRequestSignature();
$model->result_signature = $listGenericJobObject->getResultSignature();
$model->url = $listGenericJobObject->getName();
return $this->handler($model);
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Classes\Modules\Jobs\Services;
use App\Classes\General\Eloquent\AbstractFetchRecord;
use Illuminate\Database\Eloquent\Builder;
use App\Models\JobResult;
class FetchesJobResult extends AbstractFetchRecord
{
/** @var JobResult */
private $repository;
/**
* FetchesJobResult constructor.
* @param JobResult $repository
*/
public function __construct(JobResult $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
public function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Classes\Modules\Jobs\Services;
use App\Classes\General\Eloquent\AbstractListRecord;
use Illuminate\Database\Eloquent\Builder;
use App\Models\JobResult;
class ListsJobResult extends AbstractListRecord
{
/** @var JobResult */
private $repository;
/**
* ListsJobResult constructor.
* @param JobResult $repository
*/
public function __construct(JobResult $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Classes\Modules\Jobs\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Classes\Modules\Jobs\DataTransferObjects\UpdateJobResultObject;
use App\Models\JobResult;
class UpdatesJobResult extends AbstractUpdateRecord
{
/**
* @param JobResult $model
* @param UpdateJobResultObject $updateJobResultObject
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(JobResult $model, UpdateJobResultObject $updateJobResultObject) {
$model->result = $updateJobResultObject->getResult();
$model->result_signature = $updateJobResultObject->getResultSignature();
$model->job_command_name = $updateJobResultObject->getJobCommandName();
$model->job_command = $updateJobResultObject->getJobCommand();
return $this->handler($model);
}
}
@@ -0,0 +1,74 @@
<?php
namespace App\Classes\Modules\Transactions\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Jobs\ListTransactionsJob;
use App\Classes\Modules\Jobs\Services\CreatesJobResult;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ListTransactionsJobLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'List Transaction Job',
'message' => 'You have successfully submit a job to list transactions'
];
}
/** @var CreatesJobResult */
private $createsJobResult;
/**
* ListTransactionsJobLogic constructor.
* @param CreatesJobResult $createsJobResult
*/
public function __construct(CreatesJobResult $createsJobResult)
{
$this->createsJobResult = $createsJobResult;
}
/**
* @param Request $request
* @return JsonResponse
*/
public function logic(Request $request) : JsonResponse
{
$jobId = uniqid();
$user = Auth::user();
$userInfo = (object) [
'type' => $user->type,
];
$userInfoJson = json_encode($userInfo);
$requestSignature = md5($userInfoJson . $request->fullUrl());
$listGenericJobObject = new ListGenericJobObject(
$request->fullUrl(),
$request->all(),
$requestSignature,
null,
$jobId,
$userInfo
);
ListTransactionsJob::dispatch($listGenericJobObject);
$result = [];
$result['job_id'] = $jobId;
$this->createsJobResult->execute($listGenericJobObject);
return $this->response(['data' => $result]);
}
}
@@ -0,0 +1,45 @@
<?php
namespace App\Classes\Modules\Transactions\Processors;
use App\Classes\Modules\Transactions\Services\ListsTransactions;
use App\Classes\Modules\Jobs\Processors\UpdateJobResultProcessor;
use App\Classes\General\Helper;
use App\Classes\Modules\Jobs\DataTransferObjects\ListGenericJobObject;
use App\Http\Resources\ListTransactionJobResource;
class ListTransactionsJobProcessor
{
/** @var ListsTransactions */
private $listsTransactions;
/** @var UpdateJobResultProcessor */
private $updateJobResultProcessor;
/**
* ListTransactionsJobProcessor constructor.
* @param ListsTransactions $listsTransactions
* @param UpdateJobResultProcessor $updateJobResultProcessor
*/
public function __construct(ListsTransactions $listsTransactions, UpdateJobResultProcessor $updateJobResultProcessor)
{
$this->listsTransactions = $listsTransactions;
$this->updateJobResultProcessor = $updateJobResultProcessor;
}
/**
* @param ListGenericJobObject $listGenericJobObject
* @return void
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\JobResourceNotFoundException
*/
public function execute(ListGenericJobObject $listGenericJobObject) {
$query = $this->listsTransactions->execute($this->listsTransactions->deserializeFilters($listGenericJobObject->getPayload()['filters']), ['page' => $listGenericJobObject->getPayload()['page']]);
$resultCurrent = Helper::collectionResponse(ListTransactionJobResource::collection($query));
$this->updateJobResultProcessor->execute($listGenericJobObject, $resultCurrent);
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Bookings;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Classes\Modules\Bookings\ControllersLogic\ListBookingJobLogic;
class ListBookingsJobController
{
/**
* @param Request $request
* @param ListBookingJobLogic $logic
* @return JsonResponse
*/
public function list(Request $request, ListBookingJobLogic $logic) : JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Documents;
use App\Classes\Modules\Documents\ControllersLogic\ListDocumentJobLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListDocumentsJobController
{
/**
* @param Request $request
* @param ListDocumentJobLogic $logic
* @return JsonResponse
*/
public function list(Request $request, ListDocumentJobLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Jobs;
use App\Classes\Modules\Jobs\ControllersLogic\FetchJobResultLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class FetchJobResultController
{
/**
* @param Request $request
* @param FetchJobResultLogic $logic
* @return JsonResponse
*/
public function fetch(Request $request, FetchJobResultLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Transactions;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Classes\Modules\Transactions\ControllersLogic\ListTransactionsJobLogic;
class ListTransactionsJobController
{
/**
* @param Request $request
* @param ListTransactionsJobLogic $logic
* @return JsonResponse
*/
public function list(Request $request, ListTransactionsJobLogic $logic) : JsonResponse {
return $logic->execute($request);
}
}
-2
View File
@@ -11,11 +11,9 @@ use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\DocumentType;
use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Log;
class BookingResource extends JsonResource
{
/**
* Transform the resource into an array.
*
-1
View File
@@ -15,7 +15,6 @@ use App\Models\SegmentConstant;
use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class CompanyResource extends JsonResource
{
-3
View File
@@ -3,10 +3,7 @@
namespace App\Http\Resources;
use App\Models\Booking;
use App\Models\Company;
use App\Models\Document;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Resources\Json\JsonResource;
class DocumentResource extends JsonResource
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class JobResultResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'job_id' => $this->job_id,
'result' => $this->result,
];
}
}
@@ -0,0 +1,81 @@
<?php
namespace App\Http\Resources;
use App\Classes\Modules\Bookings\Services\CalculatesBookingFloatingAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingOutstanding;
use App\Classes\Modules\Bookings\Services\CalculatesBookingPayableAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\DocumentType;
use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;
class ListBookingJobResource extends JsonResource
{
private $userInfo;
public function __construct($resource, $userInfo = null)
{
parent::__construct($resource);
$this->userInfo = $userInfo ?? ($resource->userInfo ?? null);
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function toArray($request)
{
return [
'id' => $this->id,
'company' => new CompanyResource($this->company, $this->userInfo),
'bank' => new BankResource($this->bank),
'service' => new ServiceTypeResource($this->service),
'marking' => $this->marking,
'amount' => $this->fix_amount,
'floating_amount' => floatval((App()->make(CalculatesBookingFloatingAmount::class))->execute($this->resource, $this->fix_currency_id)),
'paid_amount' => floatval((App()->make(CalculatesBookingPayableAmount::class))->execute($this->resource, $this->fix_currency_id)) - floatval((App()->make(CalculatesBookingRefundAmount::class))->execute($this->resource, $this->fix_currency_id)),
'outstanding_amount' => floatval((App()->make(CalculatesBookingOutstanding::class))->execute($this->resource)) - floatval((App()->make(CalculatesBookingRefundAmount::class))->execute($this->resource, $this->fix_currency_id)),
'fixed_currency' => new CurrencyResource($this->fixedCurrency),
'convertible_currency' => new CurrencyResource($this->convertibleCurrency),
'conversion_currency' => new CurrencyResource($this->conversionCurrency),
'documents' => [
'purchase_order' => new DocumentResource($this->documents()->where('document_type', DocumentType::PURCHASE_ORDER)->first()),
'delivery_order' => new DocumentResource($this->documents()->where('document_type', DocumentType::DELIVER_ORDER)->first()),
'invoice' => new DocumentResource($this->documents()->where('document_type', DocumentType::INVOICE)->first()),
'supplier_delivery_order' => new DocumentResource($this->documents()->where('document_type', DocumentType::SUPPLIER_DELIVER_ORDER)->first()),
'proforma_invoice' => new DocumentResource($this->documents()->where('document_type', DocumentType::PROFORMA_INVOICE)->whereNotIn('status', [ApprovalStatus::REJECTED, ApprovalStatus::EXPIRED])->orderByDesc('id')->first()),
'ecommerce_purchase_order' => new DocumentResource($this->documents()->where('document_type', DocumentType::ECOMMERCE_PURCHASE_ORDER)->first()),
],
'status' => $this->status,
'created_at' => Carbon::parse($this->created_at)->format('d-m-Y'),
'created_at_with_time' => Carbon::parse($this->created_at)->format('d-m-Y h:i:s A'),
$this->mergeWhen($this->relationLoaded('transactions'), [
'purchase_order' => new TransactionResource($this->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first()),
'payment_attempts' => TransactionResource::collection(
$this->transactions()
->payments()->where('status', ApprovalStatus::PENDING_SUBMISSION)
->whereDate('expires_on', '>=', Carbon::now())
->get()
),
'expired_payment_attempts' => TransactionResource::collection($this->transactions()->payments()->where('status', ApprovalStatus::PENDING_SUBMISSION)->whereDate('expires_on', '>=', Carbon::now())->where('expires_on', '>', Carbon::now()->toTimeString())->get()),
'payment_history' => TransactionResource::collection($this->transactions()->where(function($query){
$query->where(function($query){
$query->payments()->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::COMPLETED, ApprovalStatus::REJECTED]);
})->orWhere(function($query){
$query->where(function($query){
$query->where('type', TransactionType::REFUND)->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::REJECTED, ApprovalStatus::COMPLETED]);
})->orWhere(function($query){
$query->where('type', TransactionType::CREDIT_NOTE)->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
});
});
})->latest()->get())
])
];
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Resources;
use App\Models\Booking;
use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\V2\BookingV2Resource;
use App\Http\Resources\V2\CompanyV2Resource;
class ListDocumentJobResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'reference' => $this->reference,
'status' => (int) $this->status,
'document_type' => $this->document_type,
'owner' => $this->relationLoaded('owner') ? ($this->owner instanceof Booking ? new BookingV2Resource($this->owner, $this->userInfo) : new CompanyV2Resource($this->owner, $this->userInfo)) : null,
'files' => FileResource::collection($this->files),
'created_at' => Carbon::parse($this->created_at)->format('d-m-Y h:i:s A')
];
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Http\Resources;
use App\Classes\ValueObjects\Constants\TransactionType;
use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;
class ListTransactionJobResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
$booking = in_array((int)$this->type, [TransactionType::BILL, TransactionType::REFUND])? $this->owner->owner : $this->owner;
$days = $this->created_at->endOfDay()->addWeekdays($booking->service_id === 3 ? 3 : 1);
return [
'id' => $this->id,
'booking' => new BookingResource($booking),
'type' => (int) $this->type,
'bill_no' => $this->bill_no,
'payment_reference' => $this->payment_reference,
'payment_method' => (float) $this->payment_method,
'recipient_bank_account' => new BankResource($booking->bank),
'issuer_name' => $this->issuerCompany->name,
'issuer_id' => $this->issuerCompany->id,
'amount' => (double) $this->amount,
'original_amount' => (double) $this->original_amount,
'currency' => new CurrencyResource($this->currency),
'original_currency' => new CurrencyResource($this->original_currency),
'service_charge' => (double) $this->service_charge,
'tax' => (double) $this->tax,
'currency_rate' => (double) $this->currency_rate,
'status' => (int) $this->status,
'details' => TransactionDetailResource::collection($this->transactionDetails),
'documents' => new DocumentResource($this->documents()->first()),
'transaction_bill' => new TransactionResource($this->when((int) $this->type === TransactionType::PAYMENT, $this->transactions()->bills()->first())),
'transaction_refunds' => TransactionResource::collection($this->when((int) $this->type === TransactionType::PAYMENT, $this->transactions()->refunds()->get())),
'expires_on' => Carbon::parse($this->expires_on)->format('d-m-Y h:i:s A'),
'updated_at' => Carbon::parse($this->updated_at)->format('d-m-Y h:i:s A'),
'interval' => [
'value' => $days->gt(Carbon::now()) ? '+' : '-',
'duration' => $days->diff(Carbon::now())->format('%d'),
],
'redemption' => new VoucherRedemptionResource($this->voucherRedemption)
];
}
}
@@ -0,0 +1,82 @@
<?php
namespace App\Http\Resources\V2;
use App\Classes\Modules\Bookings\Services\CalculatesBookingFloatingAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingOutstanding;
use App\Classes\Modules\Bookings\Services\CalculatesBookingPayableAmount;
use App\Classes\Modules\Bookings\Services\CalculatesBookingRefundAmount;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\DocumentType;
use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources as V1;
class BookingV2Resource extends JsonResource
{
private $userInfo;
public function __construct($resource, $userInfo = null)
{
parent::__construct($resource);
$this->userInfo = $userInfo ?? ($resource->userInfo ?? null);
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function toArray($request)
{
return [
'id' => $this->id,
'company' => new CompanyV2Resource($this->company, $this->userInfo),
'bank' => new V1\BankResource($this->bank),
'service' => new V1\ServiceTypeResource($this->service),
'marking' => $this->marking,
'amount' => $this->fix_amount,
'floating_amount' => floatval((App()->make(CalculatesBookingFloatingAmount::class))->execute($this->resource, $this->fix_currency_id)),
'paid_amount' => floatval((App()->make(CalculatesBookingPayableAmount::class))->execute($this->resource, $this->fix_currency_id)) - floatval((App()->make(CalculatesBookingRefundAmount::class))->execute($this->resource, $this->fix_currency_id)),
'outstanding_amount' => floatval((App()->make(CalculatesBookingOutstanding::class))->execute($this->resource)) - floatval((App()->make(CalculatesBookingRefundAmount::class))->execute($this->resource, $this->fix_currency_id)),
'fixed_currency' => new V1\CurrencyResource($this->fixedCurrency),
'convertible_currency' => new V1\CurrencyResource($this->convertibleCurrency),
'conversion_currency' => new V1\CurrencyResource($this->conversionCurrency),
'documents' => [
'purchase_order' => new V1\DocumentResource($this->documents()->where('document_type', DocumentType::PURCHASE_ORDER)->first()),
'delivery_order' => new V1\DocumentResource($this->documents()->where('document_type', DocumentType::DELIVER_ORDER)->first()),
'invoice' => new V1\DocumentResource($this->documents()->where('document_type', DocumentType::INVOICE)->first()),
'supplier_delivery_order' => new V1\DocumentResource($this->documents()->where('document_type', DocumentType::SUPPLIER_DELIVER_ORDER)->first()),
'proforma_invoice' => new V1\DocumentResource($this->documents()->where('document_type', DocumentType::PROFORMA_INVOICE)->whereNotIn('status', [ApprovalStatus::REJECTED, ApprovalStatus::EXPIRED])->orderByDesc('id')->first()),
'ecommerce_purchase_order' => new V1\DocumentResource($this->documents()->where('document_type', DocumentType::ECOMMERCE_PURCHASE_ORDER)->first()),
],
'status' => $this->status,
'created_at' => Carbon::parse($this->created_at)->format('d-m-Y'),
'created_at_with_time' => Carbon::parse($this->created_at)->format('d-m-Y h:i:s A'),
$this->mergeWhen($this->relationLoaded('transactions'), [
'purchase_order' => new V1\TransactionResource($this->transactions()->where('type', TransactionType::PURCHASE_ORDER)->first()),
'payment_attempts' => V1\TransactionResource::collection(
$this->transactions()
->payments()->where('status', ApprovalStatus::PENDING_SUBMISSION)
->whereDate('expires_on', '>=', Carbon::now())
->get()
),
'expired_payment_attempts' => V1\TransactionResource::collection($this->transactions()->payments()->where('status', ApprovalStatus::PENDING_SUBMISSION)->whereDate('expires_on', '>=', Carbon::now())->where('expires_on', '>', Carbon::now()->toTimeString())->get()),
'payment_history' => V1\TransactionResource::collection($this->transactions()->where(function($query){
$query->where(function($query){
$query->payments()->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::COMPLETED, ApprovalStatus::REJECTED]);
})->orWhere(function($query){
$query->where(function($query){
$query->where('type', TransactionType::REFUND)->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::REJECTED, ApprovalStatus::COMPLETED]);
})->orWhere(function($query){
$query->where('type', TransactionType::CREDIT_NOTE)->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
});
});
})->latest()->get())
])
];
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
namespace App\Http\Resources\V2;
use App\Classes\Modules\Companies\Services\FetchesCompanyServices;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\BankAccountType;
use App\Classes\ValueObjects\Constants\BusinessType;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Classes\ValueObjects\Constants\RoleTypes;
use App\Classes\ValueObjects\Constants\SegmentConstants;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Currency;
use App\Models\SegmentConstant;
use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
use App\Http\Resources as V1;
class CompanyV2Resource extends JsonResource
{
private $userInfo;
public function __construct($resource, $userInfo = null)
{
parent::__construct($resource);
$this->userInfo = $userInfo;
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
$lastPayment = $this->transactions()->where('transactions.type', TransactionType::PAYMENT)->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->orderBy('id', 'DESC')->first();
$totalPayments = $this->transactions()->where('transactions.type', TransactionType::PAYMENT)->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->sum('amount');
$segment = SegmentConstant::where('reference', SegmentConstants::SUPPLIER_CURRENCIES)->where('detail->id', $this->id)->first();
$serviceCharge = SegmentConstant::where('reference', SegmentConstants::SERVICE_CHARGE)->where('detail->id', $this->id)->first();
$userResource = null;
$userInfoEmail = $this->userInfo && isset($this->userInfo->email) ? $this->userInfo->email : null;
$userInfoType = $this->userInfo && isset($this->userInfo->type) ? $this->userInfo->type : null;
if(!$userInfoEmail && Auth::user()){
$userInfoEmail = Auth::user()->email;
}
if(!$userInfoType && Auth::user()){
$userInfoType = Auth::user()->type;
}
if(!is_null($userInfoEmail) && !is_null($userInfoType)){
$userResource = new V1\UserResource($userInfoType === RoleTypes::USER ? $this->employees()->where('email', '=', $userInfoEmail)->first() : $this->employees()->orderBy('id', 'DESC')->first());
}
return [
'id' => $this->id,
'name' => $this->name,
'reference' => $this->reference,
'debtor' => $this->debtor,
'type' => (int) $this->type,
'business_type' => (int) $this->business_type,
'status' => (int) $this->status,
'contact' => new V1\ContactResource ($this->when($this->has('contacts'), $this->contacts->first())),
'address' => new V1\AddressResource($this->when($this->has('addresses'), $this->addresses->where('billing', true)->first())),
'employee' => $userResource,
'identification' => new V1\DocumentResource($this->documents->whereIn('document_type', DocumentType::IDENTIFICATION_DOCUMENTS)->first()),
'bookings' => $this->whenLoaded('bookings', $this->bookings()->orderBy('id', 'DESC')->get(), []),
'confirmed_bookings' => $this->bookings()->whereHas('transactions', function ($query){
$query->where('type', TransactionType::PAYMENT)->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
})->count(),
'total_payments' => (float) $totalPayments,
'average_spending_per_day' => (float) $totalPayments / ($this->created_at->diff(Carbon::now())->days === 0 ? 1 : $this->created_at->diff(Carbon::now())->days),
'average_spending_per_booking' => (float) $totalPayments > 0 ? $totalPayments / $this->bookings()->whereHas('transactions', function ($query){
$query->where('type', TransactionType::PAYMENT)->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
})->count() : $totalPayments,
'last_payment' => $lastPayment ? $lastPayment->created_at->diffForHumans() : 'No Payments',
'personal_banks' => V1\BankResource::collection($this->banks->where('type', BankAccountType::PERSONAL)),
'recipient_banks' => [
'accounts' => V1\BankResource::collection($this->banks->where('type', BankAccountType::EXTERNAL)),
'default' => new V1\BankResource($this->banks->where('type', BankAccountType::EXTERNAL)->where('default', true)->first())
],
'segments' => V1\SegmentResource::collection($this->segments),
'seasonalSegment' => $this->whenLoaded('seasonalSegments', V1\SeasonalSegmentResource::collection($this->seasonalSegments)),
'services' => (new FetchesCompanyServices())->getServices($this->servicesConfigurations()),
'wallet' => $this->whenLoaded('wallets', new V1\WalletResource($this->wallets()->with('transactions')->first()), new V1\WalletResource($this->wallets()->first())),
'created_at' => $this->created_at->format('d-m-Y'),
$this->mergeWhen($this->business_type === BusinessType::CURRENCY_VENDOR, [
'currencies' => $segment ? V1\CurrencyResource::collection(Currency::whereIn('id', $segment->detail->currencies)->get()) : [],
'service_charge' => $serviceCharge
])
];
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace App\Models;
class JobResult extends AbstractModel
{
protected $table = 'job_results';
public $fillable = [
'job_id',
'result'
];
}
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateJobResultsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('job_results', function (Blueprint $table) {
$table->id();
$table->string('job_id', 50);
$table->longText('result')->nullable();
$table->timestamps();
// $table->foreign('job_id')->references('id')->on('jobs')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('job_results');
}
}
@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNewColumnToJobResultsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('job_results', function (Blueprint $table) {
$table->longText('url')->after('result')->nullable();
$table->string('job_command_name')->after('url')->nullable();
$table->longText('job_command')->after('job_command_name')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('job_results', function (Blueprint $table) {
$table->dropColumn('url');
$table->dropColumn('job_command_name');
$table->dropColumn('job_command');
});
}
}
@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNewColumn2ToJobResultsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('job_results', function (Blueprint $table) {
$table->string('request_signature')->after('job_id')->nullable();
$table->string('result_signature')->after('request_signature')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('job_results', function (Blueprint $table) {
$table->dropColumn('request_signature');
$table->dropColumn('result_signature');
});
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -117,11 +117,17 @@
</div>
<div class="row">
<div class="col">
<!-- CIEF TODO: For easy revert to old code -->
<list-component ref="pendingOrdersList" section="pendingOrdersSection" :endpoint="route('api.transaction.list')" :options="{per_page: 5, status: 2, owner_type: 'App\\Models\\Booking', type: 1, original_currency_id_in: [selectedCurrency.id], transaction_service_id: selectedService.id}">
<template slot="list" slot-scope="{data}">
<supplier-pending-order-component :data="data" v-on:input="updateOrder($event)"></supplier-pending-order-component>
</template>
</list-component>
<!-- <list-polling-component ref="pendingOrdersList" section="pendingOrdersSection" :endpoint="route('api.transaction.list.job')" :options="{per_page: 5, status: 2, owner_type: 'App\\Models\\Booking', type: 1, original_currency_id_in: [selectedCurrency.id], transaction_service_id: selectedService.id}">
<template slot="list" slot-scope="{data}">
<supplier-pending-order-component :data="data" v-on:input="updateOrder($event)"></supplier-pending-order-component>
</template>
</list-polling-component> -->
</div>
</div>
</div>
@@ -165,7 +171,7 @@
}
},
created(){
this.submit(route('api.company.list') + '?filters=' + JSON.stringify({'business_type': 3, 'status_in': [1, 2, 0]}), 'get', 'pendingOrdersSection', false, false)
this.submit(route('api.company.list') + '?filters=' + JSON.stringify({'business_type': 3, 'status_in': [1, 2, 0]}), 'get', 'pendingOrdersSection', false, false); //cief todo: Uncaught (in promise) null
},
methods: {
successHandler(response){
@@ -210,4 +216,4 @@
}
</script>
</script>
@@ -0,0 +1,213 @@
<template>
<transition-component group enter-class="animate__animated animate__fadeInUp animate__delay-1 animate__faster p-r-30" leave-class="animate__animated animate__fadeOutDown animate__faster p-r-30" style="min-height: 300px;width:100%">
<loading-component style="height: 200px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
<div class="row" key="2" v-show="!$store.getters.isLoading(section)">
<div class="col">
<div class="row">
<div class="col">
<div class="row align-items-center justify-content-center p-t-50 p-b-50" v-show="!$store.getters.getListData(section).length && !isLoading && emptyListSection">
<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;">Nothing To Show Here</p>
</div>
</div>
<div class="row m-t-5 align-items-center justify-content-center">
<div class="col">
<small class="fs-9 muted all-caps font-lato" style="letter-spacing: 2px">There is no results found, Try adjusting your filters to find what you are looking for.</small>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" v-show="!isLoading">
<div class="col">
<div class="list disable-text-selection" data-check-all="checkAll">
<div class="row" ref="list" v-for="item in $store.getters.getListData(section)" v-bind:key="item.id" :data="item">
<div class="col">
<slot name="list" :data="item"></slot>
</div>
</div>
</div>
</div>
</div>
<div class="row" v-show="!isLoading">
<div class="col">
<pagination-component :section="section" class="mb-5" ref="pagination"></pagination-component>
</div>
</div>
</div>
</div>
</div>
</div>
</transition-component>
</template>
<script>
import requestV2 from '../../../general/mixins/aws/requestV2'
export default {
props: {
section:{
type: String,
required: true
},
endpoint: {
type: String,
required: true
},
options: {
default () {
return {}
}
},
emptyListSection: {
type: Boolean,
default: true,
}
},
data(){
return {
filters: this.options,
pollingInterval: null,
isPolling: false,
isFetchingResult: false,
isLoading: false,
}
},
created(){
this.setDecoratorDefault();
this.$store.dispatch('updateListQueue', {'name': this.section, 'page': 1, 'filters': this.filters}); //cief todo: Uncaught (in promise) null
},
computed: {
pendingList () {
return this.$store.getters.isInCompleteQueue(this.section);
}
},
watch: {
pendingList(inComplete){
if(inComplete){
this.fetchList();
}
}
},
methods: {
fetchList(){
let listDecorators = this.$store.getters.getListDetails(this.section);
let url = this.endpoint + '?page=' + listDecorators.page + '&filters=' + JSON.stringify(listDecorators.filters);
this.isLoading = true;
this.submitJob(url);
},
//cief todo: remove?
// updateFilters(filters){
// this.filters = filters;
// this.setDecoratorDefault();
// console.log('updateFilters');
// this.submit(this.endpoint + '?page=1&filters=' + JSON.stringify(this.filters), 'get', this.section, false, false); //cief todo: Uncaught (in promise) null
// },
successHandler(response){
let result = JSON.parse(response.payload.data.result);
result.meta = {
current_page: result.meta.current_page,
first_page_url: result.meta.first_page_url,
from: result.meta.from,
last_page: result.meta.last_page,
last_page_url: result.meta.last_page_url,
next_page_url: result.meta.next_page_url,
path: result.meta.path,
per_page: result.meta.per_page,
prev_page_url: result.meta.prev_page_url,
to: result.meta.to,
total: result.meta.total
};
this.stopPolling();
this.$store.dispatch('completeList', {'name': this.section, 'data': result.data});
this.$refs.pagination.makePagination(result.meta, result.links);
this.isLoading = false;
},
errorHandler(error){
this.isFetchingResult = false;
this.isPolling = false;
},
startPolling(jobId, maxAttempts = 8) {
let attempts = 0;
let interval = 10000; // Initial interval
const resetPollingInterval = (customInterval) => {
this.pollingInterval = setInterval(pollJobResult, customInterval);
};
const pollJobResult = () => {
if (this.isPolling || this.isFetchingResult) {
return;
}
this.isPolling = true;
attempts++;
if(attempts === 1){
this.stopPolling();
resetPollingInterval(5000);
}
if (attempts > maxAttempts) {
this.stopPolling();
this.isLoading = false;
console.log(`Reached maximum attempts (${maxAttempts}). Polling stopped.`);
return;
}
if(attempts === maxAttempts){
this.fetchJobResult(jobId, true);
}
else{
this.fetchJobResult(jobId);
}
};
// pollJobResult(); // Initial call
this.pollingInterval = setInterval(pollJobResult, interval);
},
stopPolling() {
clearInterval(this.pollingInterval);
this.pollingInterval = null;
this.isPolling = false;
this.isFetchingResult = false;
},
submitJob(url){
try {
this.$store.dispatch('crudRequest', {endpoint: url, method: 'get'}).then(response => {
let success = response.ok;
response.json().then(response => {
if(!success){return;}
let jobId = response.payload.data.job_id;
if(jobId){
this.startPolling(jobId);
}
});
})
} catch (error) {
console.error('Error submitJob', error);
}
},
fetchJobResult(jobId, isLastAttempt = false) {
this.isFetchingResult = true;
try {
let anotherEndpoint = route('api.job.fetch', jobId);
if(isLastAttempt){
anotherEndpoint = route('api.job.fetch.last.attempt', jobId, isLastAttempt);
}
this.poll(anotherEndpoint, 'get', this.section, false, false); //cief todo: Uncaught (in promise) null
} catch (error) {
console.error('Error fetchJobResult', error);
}
}
},
mixins: [requestV2]
}
</script>
+49
View File
@@ -0,0 +1,49 @@
export default {
methods: {
poll(url, method, section, successNotification = true, errorNotification = true){
if(!this.validate()){ return; }
if (section) {
this.$store.dispatch('toggleLoading', {name: section, status: true})
}
this.$store.dispatch('crudRequestV2', {
endpoint: url,
method: method,
parameters: this.parameters
}).then(response => {
let statusCode = response.status,
success = response.ok;
response.json().then(response => {
if(!success){
this.openModal();
errorNotification ? this.$store.dispatch('createNotification', {title: response.title, message: response.message, type: 'error'}): null;
this.errorHandler(response, statusCode); return;
}
successNotification ? this.$store.dispatch('createNotification', {title: response.title, message: response.message, type: 'success'}): null;
this.successHandler(response)
});
}).catch((error) => {
this.$store.dispatch('createNotification', {title: 'Unexpected Error', message: 'An unexpected error has occurred. Try again!', type: 'error'});
}).then(() => {
if (section) {
this.$store.dispatch('toggleLoading', {name: section, status: false})
}
})
},
validate() {
if(this.$v){
this.$v.$touch();
return !this.$v.$invalid;
}
return true;
},
successHandler(response){},
errorHandler(response){}
}
}
+24
View File
@@ -0,0 +1,24 @@
export default {
data() {
return {
activeTab: null,
displayedTabs: [],
};
},
methods: {
setActiveTab(event) {
const tabName = event.currentTarget.getAttribute('tab-name');
// console.log(`Tab "${tabName}" clicked`);
this.activeTab = tabName;
if (!this.displayedTabs.includes(tabName)) {
this.displayedTabs.push(tabName);
}
},
isActiveTab(tabName) {
return this.activeTab === tabName;
},
showTabContent(tabName) {
return this.displayedTabs.includes(tabName);
},
},
}
+51
View File
@@ -0,0 +1,51 @@
export default {
actions: {
crudRequestV2({getters, dispatch}, {endpoint, method, parameters}){
return dispatch('ensureReCaptchaIsSet').then(function () {
const queryDomain = endpoint.split('?')[0];
let encodedParams = endpoint.split('?')[1];
let decodedParams = fullyDecodeURI(encodedParams);
const queryParams = encodeURIComponent(decodedParams);
encodedParams = queryParams.toString();
let filteredEncodedParams = encodedParams.replace(/%3D/g,'=');
filteredEncodedParams = filteredEncodedParams.replace(/%26/g,'&');
let combinedAbsoluteUrl = queryDomain;
if(filteredEncodedParams !== undefined && filteredEncodedParams !== 'undefined'){
combinedAbsoluteUrl = queryDomain + '?' + filteredEncodedParams;
}
// return fetch(endpoint, {
return fetch(combinedAbsoluteUrl, {
method: method,
responseType: 'json',
body: parameters ? JSON.stringify(parameters):null,
headers: {
'content-type': 'application/json',
'Authorization': 'Bearer '+getters.getAccessToken,
'captcha-token': getters.getReCaptcha
}
}).then(response => {
if(response.status === 401 && window.location.href !== route('login')){
dispatch('userAuthentication', {access_token: '', redirect_url: '/'});
}
return response;
})
});
}
}
}
function isEncoded(uri) {
uri = uri || '';
return uri !== decodeURIComponent(uri);
}
function fullyDecodeURI(uri){
while (isEncoded(uri)){
uri = decodeURIComponent(uri);
}
return uri;
}
+3 -1
View File
@@ -4,6 +4,7 @@ import toggleSection from './modules/toggleSection'
import toggleLoading from './modules/toggleLoading'
import createNotification from './modules/createNotification'
import crudRequest from './modules/crudRequest'
import crudRequestV2 from './modules/crudRequestV2'
import authentication from './modules/authentication'
import loadRequestQueue from './modules/loadRequestQueue'
@@ -16,6 +17,7 @@ export default new Vuex.Store({
loadRequestQueue,
createNotification,
crudRequest,
crudRequestV2,
authentication
}
})
})
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
@extends('layouts.base_portal')
@section('inner_content')
<div class="row" v-if="$store.getters.isAdmin">
<div class="col p-t-15 p-b-15">
<billing-component></billing-component>
<admin-payments-billing-section-polling-component></admin-payments-billing-section-polling-component>
</div>
</div>
@endsection
+4
View File
@@ -67,6 +67,10 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
require __DIR__ . '/milestone.php';
// require __DIR__ . '/accounting.php'; //cief todo: To check if this is needed
require __DIR__ . '/job.php';
// require __DIR__ . '/rate.php';
// require __DIR__ . '/receipt.php';
+1 -1
View File
@@ -1,4 +1,4 @@
<?php
<?php
use Illuminate\Support\Facades\Route;
+2 -1
View File
@@ -4,9 +4,10 @@ use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'document', 'as' => 'document.', 'namespace' => 'Documents'], function () {
Route::get('/list', 'ListDocumentsController@list')->name('list');
Route::get('/list/job', 'ListDocumentsJobController@list')->name('list.job');
Route::delete('/{id}/delete', 'DeleteDocumentController@delete')->name('delete');
Route::put('/{id}/approve', 'ApproveDocumentController@approve')->name('status.approve');
Route::put('/{id}/reject', 'RejectDocumentController@reject')->name('status.reject');
Route::put('/{id}/reference/update', 'UpdateDocumentReferenceController@update')->name('reference.update');
});
});
+8
View File
@@ -0,0 +1,8 @@
<?php
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'job', 'as' => 'job.', 'namespace' => 'Jobs'], function () {
Route::get('/fetch/{job_id}', 'FetchJobResultController@fetch')->name('fetch');
Route::get('/fetch/{job_id}/{is_last}', 'FetchJobResultController@fetch')->name('fetch.last.attempt');
});
+9 -3
View File
@@ -92,6 +92,12 @@ Route::get('/billings', function () {
return view('pages.billings');
})->name('billings');
/* Vue Polling Experiment - Starts */
Route::get('/billings-experiment', function () {
return view('pages.billings_experiment');
})->name('billings.experiment');
/* Vue Polling Experiment - Ends */
Route::get('/currency_orders', function () {
return view('pages.currency_orders');
})->name('currency_orders');
@@ -830,13 +836,13 @@ Route::get('/invoice/{marking}/{started_at}/{ended_at}/fix', function($marking,
->withTrashed()
->orderBy('created_at', 'asc')
->first();
// get the first bill_no
$firstBillNo = $firstInvoice->bill_no;
if (strpos($firstBillNo, '-deleted') !== false) {
$firstBillNo = substr($firstBillNo, 0, strpos($firstBillNo, '-deleted'));
}
// update currentInvoice bill_no to '-deleted-'
$currentInvoice = $booking->transactions()->where('type', TransactionType::INVOICE)->first();
$currentInvoice->bill_no = $currentInvoice->bill_no ."-deleted-" . Str::random(10);
@@ -859,4 +865,4 @@ Route::get('/invoice/{marking}/{started_at}/{ended_at}/fix', function($marking,
}
}
);
})->name('invoice.fix.byCustomerMarking');
})->name('invoice.fix.byCustomerMarking');