Merge branch 'master' of gitlab.com:CIEFWorldwideSdnBhd/exchange-2.0 into dillon/voucherify-35

# Conflicts:
#	routes/api.php
This commit is contained in:
edmondlang
2023-06-22 23:09:50 +08:00
88 changed files with 5423 additions and 85 deletions
@@ -0,0 +1,19 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class DateIn implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereIn('date', $value);
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class HasAccountStatementId implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereHas('statementTransaction', function ($query) use ($value) {
$query->where('account_statement_id', $value);
});
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class IsMapped implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $value ? $builder->whereHas('owners') : $builder->whereDoesntHave('owners');
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use Illuminate\Database\Eloquent\Builder;
class IsMappedWithMultiple implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
$query = $builder->withCount(['owners' => function ($query){
$query->where('status', ApprovalStatus::PENDING_VERIFICATION);
}]);
return $value ? $query->having('owners_count', '>', 1) : $query->having('owners_count', '=', 1);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class MaxAmount implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('amount', '<=', $value);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class MinAmount implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('amount', '>=', $value);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class OwnerId implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('owner_id', $value);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class PayFor implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('pay_for', $value);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class PayForIn implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereIn('pay_for', $value);
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class StatementTransactionAccountId implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereHas('account', function ($query) use ($value) {
$query->where('statement_accounts.id', $value);
});
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class StatementTransactionOwnerStatusIn implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereHas('owners', function ($query) use ($value) {
return $query->whereIn('statement_transaction_owners.status', $value);
});
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class StatementTransactionOwnerTypeIn implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereHas('owners', function ($query) use ($value) {
return $query->whereIn('type', $value);
});
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Classes\Jobs;
use App\Classes\Modules\Accounting\Processors\CreateBankStatementTransactionOwnersProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CreateBankStatementTransactionOwners implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle()
{
(App()->make(CreateBankStatementTransactionOwnersProcessor::class))->execute();
}
public function delay($delay)
{
// Add delay in seconds to the job
$this->delay = $delay;
return $this;
}
}
+16 -14
View File
@@ -10,7 +10,8 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Classes\Modules\PerfexCRM\DataTransferObjects\UpdatePerfexCRMObject;
use App\Classes\Modules\PerfexCRM\DataTransferObjects\UpdatePerfexCRMInvoiceObject;
use Illuminate\Support\Facades\Log;
use PhpOffice\PhpSpreadsheet\Calculation\Logical\Boolean;
class UpdatePerfexCRM implements ShouldQueue
{
@@ -19,38 +20,39 @@ class UpdatePerfexCRM implements ShouldQueue
/** @var UpdatePerfexCRMObject */
private $updatePerfexCRMObject;
/** @var UpdatePerfexCRMInvoice */
private $nextJob;
/** @var */
/** @var */
private $transaction;
/** @var Boolean|null */
private $shouldCreateInvoice;
/**
* UpdatePerfexCRM constructor.
* @param UpdatePerfexCRMObject $updatePerfexCRMObject
* @param UpdatePerfexCRMInvoice $nextJob
* @param $transaction
* @param bool|null $shouldCreateInvoice
*/
public function __construct(UpdatePerfexCRMObject $updatePerfexCRMObject, $transaction, $nextJob)
public function __construct(UpdatePerfexCRMObject $updatePerfexCRMObject, $transaction, ?bool $shouldCreateInvoice = false)
{
$this->updatePerfexCRMObject = $updatePerfexCRMObject;
$this->nextJob = $nextJob;
$this->transaction = $transaction;
$this->shouldCreateInvoice = $shouldCreateInvoice;
}
public function handle()
{
$result = (App()->make(UpdatePerfexCRMProcessor::class))->execute($this->updatePerfexCRMObject);
/** @var UpdatePerfexCRMInvoice $nextJob*/
if($this->nextJob != null && $this->transaction != null){
$updatePerfexCRMInvoiceOject = new UpdatePerfexCRMInvoiceObject(
if($this->transaction != null && $this->shouldCreateInvoice){
$updatePerfexCRMInvoiceObject = new UpdatePerfexCRMInvoiceObject(
$this->updatePerfexCRMObject->getContactEmail(),
$this->updatePerfexCRMObject->getProjectName(),
$result->projectId,
$this->transaction,
true,
true
);
$this->nextJob::dispatch($updatePerfexCRMInvoiceOject);
UpdatePerfexCRMInvoice::dispatch($updatePerfexCRMInvoiceObject);
}
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ class UpdatePerfexCRMInvoice implements ShouldQueue
/**
* CreatePerfexCRMSingleTask constructor.
* @param UpdatePerfexCRMInvoiceObject $createTaskPerfexCRMObject
* @param UpdatePerfexCRMInvoiceObject $updatePerfexCRMInvoiceObject
*/
public function __construct(UpdatePerfexCRMInvoiceObject $updatePerfexCRMInvoiceObject)
{
+34 -34
View File
@@ -21,50 +21,28 @@ class UpdatePerfexCRMPrelude implements ShouldQueue
/** @var UpdatePerfexCRMObject */
private $updatePerfexCRMObject;
/** @var UpdatePerfexCRM */
private $nextJob1;
/** @var UpdatePerfexCRMInvoice */
private $nextJob2;
/** @var Boolean|null */
private $shouldCreateInvoice;
/**
* UpdatePerfexCRMPrelude constructor.
* @param Transaction $transaction
* @param UpdatePerfexCRMObject $updatePerfexCRMObject
* @param UpdatePerfexCRM $nextJob1
* @param UpdatePerfexCRMInvoice $nextJob2
* @param bool|null $shouldCreateInvoice
*/
public function __construct(Transaction $transaction, UpdatePerfexCRMObject $updatePerfexCRMObject, $nextJob1, $nextJob2)
public function __construct(Transaction $transaction, UpdatePerfexCRMObject $updatePerfexCRMObject, ?bool $shouldCreateInvoice = false)
{
$this->transaction = $transaction;
$this->updatePerfexCRMObject = $updatePerfexCRMObject;
$this->nextJob1 = $nextJob1;
$this->nextJob2 = $nextJob2;
$this->shouldCreateInvoice = $shouldCreateInvoice;
}
public function handle()
{
$serviceTypeName = $this->transaction->owner->company->services()->where('id', $this->transaction->owner->service_id)->first()->name;
$booking = $this->transaction->booking;
$bank = $booking->bank;
$bankDetails = $this->transaction->owner->service_id == 4 ?
[
'1688_username' => $bank->account_no,
'1688_password' => $bank->holder_name,
'payment_pin' => $bank->bank_branch,
]:
[
'id' => $bank->id,
'type' => $bank->type,
'reference' => $bank->reference,
'bank_name' => $bank->bank_name,
'bank_branch' => $bank->bank_branch,
'holder_name' => $bank->holder_name,
'account_no' => $bank->account_no,
'country_id' => $bank->country_id,
'default' => $bank->default,
'status' => $bank->status,
];
$bankDetails = $this->generateBankDetails($booking->bank);
$data = [
'amount' => number_format($this->transaction->amount, 2, '.', ''),
'currency' => $this->transaction->currency_id == 1 ? "MYR" : "CNY",
@@ -75,14 +53,36 @@ class UpdatePerfexCRMPrelude implements ShouldQueue
'link_autocount_or' => 'https://docs.google.com/spreadsheets/d/1Q3rJBGQ9Bo04HZp5WR9zbLp7pIW2kfDYsabxG7JON-4/edit#gid=2013983170',
];
$result = $this->replacePlaceholders($this->updatePerfexCRMObject->getTasks(), $data);
$this->updatePerfexCRMObject->setTasks($result);
/** @var UpdatePerfexCRM $nextJob1 */
/** @var UpdatePerfexCRMInvoice $nextJob2 */
$this->nextJob1::dispatch($this->updatePerfexCRMObject, $this->transaction, $this->nextJob2);
UpdatePerfexCRM::dispatch($this->updatePerfexCRMObject, $this->transaction, $this->shouldCreateInvoice);
}
private function generateBankDetails($bank): array
{
return $this->transaction->owner->service_id == 4 ?
[
'1688_username' => $bank->account_no,
'1688_password' => $bank->holder_name,
'payment_pin' => $bank->bank_branch,
]:
[
'id' => $bank->id,
'type' => $bank->type,
'reference' => $bank->reference,
'bank_name' => $bank->bank_name,
'bank_branch' => $bank->bank_branch,
'holder_name' => $bank->holder_name,
'account_no' => $bank->account_no,
'country_id' => $bank->country_id,
'default' => $bank->default,
'status' => $bank->status,
];
}
function replacePlaceholders($template, $data, $prefix = '')
{
foreach ($template as $key => $value) {
@@ -0,0 +1,283 @@
<?php
namespace App\Classes\Modules\Accounting\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Accounting\Services\FetchesBankStatementTransaction;
use App\Http\Resources\BankStatementTransactionResource;
use App\Classes\Modules\Accounting\Services\UpdatesBankStatementTransactionOwnerStatus;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\StatementTransactionOwnerType;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus;
use App\Classes\Modules\Accounting\Services\FetchesBankStatementDetails;
use App\Classes\Modules\Accounting\Services\ListsBankStatementDetails;
use App\Models\StatementTransactionOwner;
use Illuminate\Support\Collection;
class ApproveDuplicateBankStatementDetailsStatusLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification(): array
{
return [
'title' => 'Update Duplicate Bank Statement Details Status',
'message' => 'You have successfully updated the Statement Transation Status'
];
}
/** @var UpdatesBankStatementTransactionOwnerStatus */
private $updatesBankStatementTransactionOwnerStatus;
/**
* @param UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus
*/
public function __construct(UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus)
{
$this->updatesBankStatementTransactionOwnerStatus = $updatesBankStatementTransactionOwnerStatus;
}
// /**
// * Perform logic for approving or rejecting a statement transaction owner and handle matching records.
// *
// * @param Request $request The request object containing route parameters and data.
// * @return JsonResponse The JSON response indicating the result of the logic.
// */
// public function logic(Request $request): JsonResponse
// {
// // Determine the status based on the 'status' route parameter
// $status = $request->route('status') == 'approve' ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED;
//
// // Find the statement transaction owner based on the 'id' route parameter
// $owner = StatementTransactionOwner::find($request->route('id'));
//
// // Execute an update action to change the status of the owner
// $this->updatesBankStatementTransactionOwnerStatus->execute($owner, $status);
//
// // If the status is 'approved', reject all other owners with the same system, owner type, and owner ID
// if ($status === ApprovalStatus::APPROVED) {
// StatementTransactionOwner::where('system', $owner->system)
// ->where('owner_type', $owner->owner_type)
// ->where('owner_id', $owner->owner_id)
// ->where('id', '!=', $owner->id)
// ->update(['status' => ApprovalStatus::REJECTED]);
// }
//
// // Find all siblings (owners with the same statement transaction ID)
// $siblings = StatementTransactionOwner::where('statement_transaction_id', $owner->statement_transaction_id)
// ->where('id', '!=', $owner->id)
// ->get();
//
// // Process each sibling
// foreach ($siblings as $sibling) {
//
// // If the status is 'approved', reject the sibling and save the changes
// if ($status === ApprovalStatus::APPROVED) {
// $sibling->status = ApprovalStatus::REJECTED;
// $sibling->save();
// }
//
// // Find all twins (owners with the same system, owner type, and owner ID)
// $twins = StatementTransactionOwner::where('system', $sibling->system)
// ->where('owner_type', $sibling->owner_type)
// ->where('owner_id', $sibling->owner_id)
// ->where('id', '!=', $sibling->id)
// ->get();
//
// // Process each twin
// foreach ($twins as $twin) {
// // Find all owners with the same statement transaction ID as the twin
// $owners = StatementTransactionOwner::where('statement_transaction_id', $twin->statement_transaction_id)
// ->where('id', '!=', $twin->id)
// ->get();
//
// // If there is only one owner (the twin itself), execute an update action to change its status to 'approved'
// if (count($owners) === 1) {
// $this->updatesBankStatementTransactionOwnerStatus->execute($twin, ApprovalStatus::APPROVED);
// }
// }
// }
//
// // Find all remaining matching owners for the related transaction
// $remainingMatches = StatementTransactionOwner::where('system', $owner->system)
// ->where('owner_type', $owner->owner_type)
// ->where('owner_id', $owner->owner_id)
// ->where('status', ApprovalStatus::PENDING_VERIFICATION) // Consider only pending owners
// ->get();
//
// // Process each remaining match
// foreach ($remainingMatches as $match) {
// // Find all owners with the same statement transaction ID as the match
// $owners = StatementTransactionOwner::where('statement_transaction_id', $match->statement_transaction_id)
// ->where('id', '!=', $match->id)
// ->get();
//
// // If there is only one owner (the match itself), execute an update action to change its status to 'approved'
// if (count($owners) === 1) {
// $this->updatesBankStatementTransactionOwnerStatus->execute($match, ApprovalStatus::APPROVED);
// }
// }
//
// // Return an empty response
// return $this->response([]);
// }
/**
* Perform logic for approving or rejecting a statement transaction owner and handle matching records.
*
* @param Request $request The request object containing route parameters and data.
* @return JsonResponse The JSON response indicating the result of the logic.
*/
public function logic(Request $request): JsonResponse
{
// Determine the approval status
$status = $this->getApprovalStatus($request);
// Find the statement transaction owner
$owner = $this->getOwner($request);
// Update owner status
$this->updateOwnerStatus($owner, $status);
// If the status is 'approved', handle the approval process
if ($status === ApprovalStatus::APPROVED) {
$this->handleApprovedStatus($owner);
}
// Check and approve remaining matches if any
$this->checkAndApproveRemainingMatches($owner);
// Return an empty response
return $this->response([]);
}
private function getApprovalStatus(Request $request): int
{
return $request->route('status') == 'approve' ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED;
}
private function getOwner(Request $request): StatementTransactionOwner
{
return StatementTransactionOwner::find($request->route('id'));
}
private function updateOwnerStatus(StatementTransactionOwner $owner, int $status): void
{
$this->updatesBankStatementTransactionOwnerStatus->execute($owner, $status);
}
private function handleApprovedStatus(StatementTransactionOwner $owner): void
{
// Reject all other owners with the same system, owner type, and owner ID
$this->rejectOtherOwners($owner);
// Find all siblings and process them
$siblings = $this->getSiblings($owner);
$this->processSiblings($siblings);
}
private function rejectOtherOwners(StatementTransactionOwner $owner): void
{
StatementTransactionOwner::where('system', $owner->system)
->where('owner_type', $owner->owner_type)
->where('owner_id', $owner->owner_id)
->where('id', '!=', $owner->id)
->update(['status' => ApprovalStatus::REJECTED]);
}
private function getSiblings(StatementTransactionOwner $owner): Collection
{
return StatementTransactionOwner::where('statement_transaction_id', $owner->statement_transaction_id)
->where('id', '!=', $owner->id)
->get();
}
private function processSiblings(Collection $siblings): void
{
foreach ($siblings as $sibling) {
$this->processSibling($sibling);
}
}
private function processSibling(StatementTransactionOwner $sibling): void
{
// Reject the sibling and save the changes
$sibling->status = ApprovalStatus::REJECTED;
$sibling->save();
// Find all twins and process them
$twins = $this->getTwins($sibling);
$this->processTwins($twins);
}
private function getTwins(StatementTransactionOwner $sibling): Collection
{
return StatementTransactionOwner::where('system', $sibling->system)
->where('owner_type', $sibling->owner_type)
->where('owner_id', $sibling->owner_id)
->where('id', '!=', $sibling->id)
->get();
}
private function processTwins(Collection $twins): void
{
foreach ($twins as $twin) {
$this->processTwin($twin);
}
}
private function processTwin(StatementTransactionOwner $twin): void
{
// Find all owners with the same statement transaction ID as the twin
$owners = $this->getOwners($twin);
// If there is only one owner (the twin itself), approve it
if ($owners->count() === 1) {
$this->updateOwnerStatus($twin, ApprovalStatus::APPROVED);
}
}
private function getOwners(StatementTransactionOwner $transactionOwner): Collection
{
return StatementTransactionOwner::where('statement_transaction_id', $transactionOwner->statement_transaction_id)
->where('id', '!=', $transactionOwner->id)
->get();
}
private function checkAndApproveRemainingMatches(StatementTransactionOwner $owner): void
{
// Find all remaining matching owners for the related transaction
$remainingMatches = $this->getRemainingMatches($owner);
// Process each remaining match
foreach ($remainingMatches as $match) {
$this->processRemainingMatch($match);
}
}
private function getRemainingMatches(StatementTransactionOwner $owner): Collection
{
return StatementTransactionOwner::where('system', $owner->system)
->where('owner_type', $owner->owner_type)
->where('owner_id', $owner->owner_id)
->where('status', ApprovalStatus::PENDING_VERIFICATION)
->get();
}
private function processRemainingMatch(StatementTransactionOwner $match): void
{
// Find all owners with the same statement transaction ID as the match
$owners = $this->getOwners($match);
// If there is only one owner (the match itself), approve it
if ($owners->count() === 1) {
$this->updateOwnerStatus($match, ApprovalStatus::APPROVED);
}
}
}
@@ -0,0 +1,86 @@
<?php
namespace App\Classes\Modules\Accounting\ControllersLogic;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Accounting\Services\ListsBankStatementTransactions;
use App\Http\Resources\BankStatementTransactionResource;
use App\Classes\Modules\Accounting\Services\UpdatesBankStatementTransactionOwnerStatus;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus;
use App\Classes\ValueObjects\Constants\StatementTransactionOwnerType;
class GroupApproveStatementTransactionLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification(): array
{
return [
'title' => 'Group Approve Statement Transaction',
'message' => 'You have successfully approveed a group of Statement Transation'
];
}
/** @var ListsBankStatementTransactions */
private $listsBankStatementTransactions;
/** @var UpdatesBankStatementTransactionOwnerStatus */
private $updatesBankStatementTransactionOwnerStatus;
/** @var UpdatesTransactionStatus */
private $updatesTransactionStatus;
/**
* @param ListsBankStatementTransactions $listsBankStatementTransactions
* @param UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus
* @param UpdatesTransactionStatus $updatesTransactionStatus
*/
public function __construct(ListsBankStatementTransactions $listsBankStatementTransactions, UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus, UpdatesTransactionStatus $updatesTransactionStatus)
{
$this->listsBankStatementTransactions = $listsBankStatementTransactions;
$this->updatesBankStatementTransactionOwnerStatus = $updatesBankStatementTransactionOwnerStatus;
$this->updatesTransactionStatus = $updatesTransactionStatus;
}
/**
* @param Request $request
* @return JsonResponse
* @throws MalformedRequestException
*/
public function logic(Request $request): JsonResponse
{
$filters = [
"min_amount" => 0,
"is_mapped" => true,
"is_mapped_with_multiple" => false,
"statement_transaction_owner_type_in" => [1, 2],
"statement_transaction_owner_status_in" => [1]
];
$statementTransactions = $this->listsBankStatementTransactions->execute($filters);
foreach ($statementTransactions as $statementTransaction) {
$owners = $statementTransaction->owners;
if (count($owners)) {
$this->updatesBankStatementTransactionOwnerStatus->execute($owners->first(), ApprovalStatus::APPROVED);
// automatically approve payment if pending verification
// if ($statementTrasactionOwner->owner->type !== StatementTransactionOwnerType::SALES) continue;
// if ($statementTrasactionOwner->owner->status === ApprovalStatus::PENDING_VERIFICATION ) {
// $this->updatesTransactionStatus->execute($statementTrasactionOwner->owner, ApprovalStatus::APPROVED);
// }
}
}
return $this->response([]);
}
}
@@ -0,0 +1,147 @@
<?php
namespace App\Classes\Modules\Accounting\ControllersLogic;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Jobs\CreateBankStatementTransactionOwners;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Models\AccountStatement;
use App\Models\StatementAccount;
use App\Models\StatementTransaction;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Maatwebsite\Excel\Facades\Excel;
class ImportBankStatementLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Import Bank Statement Transactions Details',
'message' => 'You have successfully updated the Bank Statement Transactions Details'
];
}
/**
* @param Request $request
* @return JsonResponse
* @throws MalformedRequestException
*/
public function logic(Request $request) : JsonResponse
{
$files = $request->file('files');
$object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
foreach ($object->getFiles() as $file){
$collection = Excel::toCollection(null, json_decode($file)->file_info->original->file, null, null, true);
$sheet = $collection->first()->skip(1);
$statementDetails = $sheet->first();
$accountNumber = $statementDetails[0];
$accountType = $statementDetails[1];
$accountName = $statementDetails[2];
$accountCurrency = $statementDetails[3];
$dateFrom = carbon::parse(str_replace(' MY (UTC+08:00)', '', $statementDetails[4]));
$dateTo = carbon::parse(str_replace(' MY (UTC+08:00)', '', $statementDetails[5]));
$totalDebit = $statementDetails[6];
$totalCredit = $statementDetails[7];
$beginBalance = $statementDetails[8];
$endBalance = $statementDetails[9];
$account = StatementAccount::updateOrCreate(
['number' => $accountNumber],
[
'type' => $accountType,
'name' => $accountName,
'currency' => $accountCurrency,
]
);
$statement = AccountStatement::where('date_from', $dateFrom)
->where('date_to', $dateTo)
->where('total_amount', $totalDebit ?: $totalCredit,)
->where('begin_balance', $beginBalance)
->where('end_balance', $endBalance)->first();
if(!$statement){
$statement = new AccountStatement([
'date_from' => $dateFrom,
'date_to' => $dateTo,
'total_amount' => $totalDebit ?: $totalCredit,
'begin_balance' => $beginBalance,
'end_balance' => $endBalance,
]);
}
$account->statements()->save($statement);
// CreateBankStatementTransactionOwners::dispatch($statement);
$sheet->map(function ($row) use ($statement, $account) {
$transactionRef = $row[15];
$amount = $row[17] !== '-' ? ((float) str_replace(',', '', $row[17])) : (-((float) str_replace(',', '', $row[16])));
$transactionDate = $row[10] !== '-' ? carbon::parse(str_replace(' MY (UTC+08:00)', '', $row[10]) . $row[11]) : null;
$postingDate = carbon::createFromFormat('d/M/Y H:i', str_replace(' MY (UTC+08:00)', '', $row[12]) . str_replace(' MY (UTC+08:00)', '', $row[13]));
$transactionDescription = is_numeric($row[14]) ? (int) sprintf('%.2f', $row[14]) : $row[14];
$tellerId = $row[19];
$branchChannel = $row[20];
$transactionCode = $row[21];
$endBalance = $row[22];
$description2 = $row[25];
$description3 = $row[26];
$description4 = $row[27];
$description5 = $row[28];
$transaction = new StatementTransaction([
'transaction_ref' => $transactionRef,
'amount' => $amount,
'transaction_date' => $transactionDate,
'posting_date' => $postingDate,
'transaction_description' => $transactionDescription,
'teller_id' => $tellerId,
'branch_channel' => $branchChannel,
'transaction_code' => $transactionCode,
'end_balance' => $endBalance,
'transaction_description_2' => $description2,
'transaction_description_3' => $description3,
'transaction_description_4' => $description4,
'transaction_description_5' => $description5,
]);
// Check if the transaction already exists for this statement
$existingTransaction = StatementTransaction::where('transaction_ref', $transactionRef)
->where('posting_date', $postingDate)
->where('amount', $amount)
->where('transaction_description', $transactionDescription)
->where('teller_id', $tellerId)
->where('branch_channel', $branchChannel)
->where('transaction_code', $transactionCode)
->where('end_balance', $endBalance)
->first();
if (!$existingTransaction) {
$statement->transactions()->save($transaction);
}
return $transaction;
});
}
return $this->response([]);
}
}
@@ -0,0 +1,52 @@
<?php
namespace App\Classes\Modules\Accounting\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Accounting\Services\ListsBankStatementDetails;
use App\Http\Resources\BankStatementDetailResource;
use ErrorException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListBankStatementDetailsLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Retrieved Bank Statement Details',
'message' => 'You have successfully retrieved a Bank Statement Details'
];
}
/** @var ListsBankStatementDetails */
private $listsBankStatementDetails;
/**
* ListBankStatementDetailsLogic constructor.
* @param ListsBankStatementDetails $listsBankStatementDetails
*/
public function __construct(ListsBankStatementDetails $listsBankStatementDetails)
{
$this->listsBankStatementDetails = $listsBankStatementDetails;
}
/**
* @param Request $request
* @return JsonResponse
* @throws ErrorException
*/
public function logic(Request $request) : JsonResponse
{
$query = $this->listsBankStatementDetails->execute($this->listsBankStatementDetails->deserializeFilters($request->input('filters')));
return $this->collectionResponse(BankStatementDetailResource::collection($query));
}
}
@@ -0,0 +1,53 @@
<?php
namespace App\Classes\Modules\Accounting\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Accounting\Services\ListsBankStatementDetails;
use App\Classes\Modules\Accounting\Services\ListsBankStatementTransactions;
use App\Http\Resources\BankStatementTransactionResource;
use App\Models\StatementTransaction;
use ErrorException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListBankStatementTransactionsLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Retrieved Bank Statement Transactions',
'message' => 'You have successfully retrieved a Bank Statement Transactions'
];
}
/** @var ListsBankStatementTransactions */
private $listsBankStatementTransactions;
/**
* @param ListsBankStatementTransactions $listsBankStatementTransactions
*/
public function __construct(ListsBankStatementTransactions $listsBankStatementTransactions)
{
$this->listsBankStatementTransactions = $listsBankStatementTransactions;
}
/**
* @param Request $request
* @return JsonResponse
* @throws ErrorException
*/
public function logic(Request $request) : JsonResponse
{
$query = $this->listsBankStatementTransactions->execute($this->listsBankStatementTransactions->deserializeFilters($request->input('filters')));
return $this->collectionResponse(BankStatementTransactionResource::collection($query));
}
}
@@ -0,0 +1,137 @@
<?php
namespace App\Classes\Modules\Accounting\ControllersLogic;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Accounting\Services\CreatesBankStatementTransactionOwner;
use App\Classes\Modules\Accounting\Services\FetchesBankStatementTransaction;
use App\Classes\ValueObjects\Constants\StatementTransactionOwnerType;
use App\Classes\ValueObjects\Constants\SystemType;
use App\Classes\Modules\Accounting\Processors\ChecksBillNumber;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Booking;
use App\Models\Transaction;
use App\Models\Wallet;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UpdateBankStatementDetailLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Updated Bank Statement Transactions Details',
'message' => 'You have successfully updated the Bank Statement Transactions Details'
];
}
/** @var FetchesBankStatementTransaction */
private $fetchesBankStatementTransaction;
/** @var ChecksBillNumber */
private $checksBillNumber;
/**
* @param FetchesBankStatementTransaction $fetchesBankStatementTransaction
* @param ChecksBillNumber $checksBillNumber
*/
public function __construct(FetchesBankStatementTransaction $fetchesBankStatementTransaction, ChecksBillNumber $checksBillNumber)
{
$this->fetchesBankStatementTransaction = $fetchesBankStatementTransaction;
$this->checksBillNumber = $checksBillNumber;
}
public function logic(Request $request): JsonResponse
{
$bankStatementTransaction = $this->fetchesBankStatementTransaction->execute(['id' => $request->route('id')]);
$transactionReference = $request->input('transaction_reference');
$systemReference = $request->input('system_references');
$owner_type = $owner_id = $owner_reference = $statementTransactionOwnerType = $system = null;
$salesSystems = ['lite', 'cntr', 'probashi', 'pets'];
$allowedSystems = array_merge($salesSystems, ['exchange', 'izyim']);
if (!in_array($systemReference, $allowedSystems)) {
throw new MalformedRequestException('System Reference not allowed');
}
switch ($request->input('pay_for')) {
case 'sales':
$owner_reference = $transactionReference;
$statementTransactionOwnerType = StatementTransactionOwnerType::SALES;
break;
case 'top_up':
$owner_reference = $transactionReference;
$statementTransactionOwnerType = StatementTransactionOwnerType::WALLET_TOP_UP;
break;
case 'internal_bank_transfer':
$statementTransactionOwnerType = StatementTransactionOwnerType::INTERNAL_BANK_TRANSFER_IN;
break;
case 'others':
$owner_reference = $transactionReference;
$statementTransactionOwnerType = StatementTransactionOwnerType::NON_OPERATIONAL;
break;
default:
throw new MalformedRequestException('Transaction Type Not Allowed');
}
if (in_array($systemReference, ['exchange', 'izyim'])) {
$transaction = $this->checksBillNumber->execute($transactionReference, $systemReference);
if($systemReference === 'exchange') {
$owner_type = Transaction::class;
$owner_id = $transaction->id;
if($transaction->type === TransactionType::TOP_UP) {
$owner_reference = $transaction->owner->owner->reference;
}
if($transaction->type === TransactionType::PAYMENT && $transaction->owner_type === Booking::class) {
$owner_reference = $transaction->owner->marking;
}
}
if($systemReference === 'izyim') {
$transaction = $transaction[0];
$owner_type = Transaction::class;
$owner_id = $transaction['owner_id'];
$owner_reference = $transaction['owner_reference'];
}
}
$system = $systemReference != null ? SystemType::SYSTEM_NAMES[$systemReference] : '';
$this->createBankStatementTransactionOwner($bankStatementTransaction, $statementTransactionOwnerType, $system, $owner_type, $owner_id, $owner_reference);
return $this->response([]);
}
private function createBankStatementTransactionOwner($bankStatementTransaction, $statementTransactionOwnerType, $system, $owner_type, $owner_id, $owner_reference)
{
$ownerData = [
'type' => $statementTransactionOwnerType,
'system' => $system,
'owner_type' => $owner_type,
'owner_id' => $owner_id,
'owner_reference' => $owner_reference,
];
$bankStatementTransaction->owners()->firstOrCreate($ownerData);
}
}
@@ -0,0 +1,81 @@
<?php
namespace App\Classes\Modules\Accounting\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Accounting\Services\FetchesBankStatementTransaction;
use App\Http\Resources\BankStatementTransactionResource;
use App\Classes\Modules\Accounting\Services\UpdatesBankStatementTransactionOwnerStatus;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\StatementTransactionOwnerType;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus;
class UpdateStatementTransactionStatusLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification(): array
{
return [
'title' => 'Update Statement Transaction Status',
'message' => 'You have successfully updated the Statement Transation Status'
];
}
/** @var FetchesBankStatementTransaction */
private $fetchesBankStatementTransaction;
/** @var UpdatesBankStatementTransactionOwnerStatus */
private $updatesBankStatementTransactionOwnerStatus;
/** @var UpdatesTransactionStatus */
private $updatesTransactionStatus;
/**
* UpdateAnnouncementLogic constructor.
* @param FetchesBankStatementTransaction $fetchesBankStatementTransaction
* @param UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus
* @param UpdatesTransactionStatus $updatesTransactionStatus
*/
public function __construct(
FetchesBankStatementTransaction $fetchesBankStatementTransaction,
UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus,
UpdatesTransactionStatus $updatesTransactionStatus
) {
$this->fetchesBankStatementTransaction = $fetchesBankStatementTransaction;
$this->updatesBankStatementTransactionOwnerStatus = $updatesBankStatementTransactionOwnerStatus;
$this->updatesTransactionStatus = $updatesTransactionStatus;
}
/**
* @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
{
$statementTrasaction = $this->fetchesBankStatementTransaction->execute(['id' => $request->route('id')]);
$statementTrasactionOwner = $statementTrasaction->owners->first();
$this->updatesBankStatementTransactionOwnerStatus->execute($statementTrasactionOwner, $request->route('status') == 'approve' ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED);
// todo-new: approve payments status, need to check the owner(if system is shipping, need to api with shipping portal)
// if ($request->route('status') == 'approve') {
// if ($statementTrasactionOwner->transaction->type === StatementTransactionOwnerType::SALES) {
// if ($statementTrasactionOwner->owner->status === ApprovalStatus::PENDING_VERIFICATION) {
// $this->updatesTransactionStatus->execute($statementTrasactionOwner->owner, ApprovalStatus::APPROVED);
// }
// }
// }
return $this->resourceResponse(new BankStatementTransactionResource($statementTrasaction));
}
}
@@ -0,0 +1,137 @@
<?php
namespace App\Classes\Modules\Accounting\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use PhpOffice\PhpSpreadsheet\Calculation\Logical\Boolean;
class BankStatementTransactionObject implements DataTransferObject
{
/** @var int */
private $statement_transaction_id;
/** @var int */
private $type;
/** @var string */
private $system;
/** @var string */
private $owner_type;
/** @var int */
private $owner_id;
/** @var string */
private $invoice_reference;
/** @var string */
private $receipt_reference;
/** @var Boolean */
private $is_auto_mapped;
/** @var int|null */
private $status;
/**
* BankStatementDetailObject constructor.
* @param string $pay_for
* @param string $system_references
* @param int|null $type
*/
public function __construct(
$statement_transaction_id,
$type,
$system,
$owner_type,
$owner_id,
$invoice_reference,
$receipt_reference,
$is_auto_mapped,
?int $status = ApprovalStatus::PENDING_VERIFICATION
) {
$this->statement_transaction_id = $statement_transaction_id;
$this->type = $type;
$this->system = $system;
$this->owner_type = $owner_type;
$this->owner_id = $owner_id;
$this->invoice_reference = $invoice_reference;
$this->receipt_reference = $receipt_reference;
$this->is_auto_mapped = $is_auto_mapped;
$this->status = $status;
}
/**
* @return int
*/
public function getStatementTransactionId(): int
{
return $this->statement_transaction_id;
}
/**
* @return int
*/
public function getType(): int
{
return $this->type;
}
/**
* @return string
*/
public function getOwnerType(): string
{
return $this->owner_type;
}
/**
* @return int
*/
public function getOwnerId(): int
{
return $this->owner_id;
}
/**
* @return string
*/
public function getSystem(): string
{
return $this->system;
}
/**
* @return string
*/
public function getInvoiceReference(): string
{
return $this->invoice_reference;
}
/**
* @return string
*/
public function getReceipteReference(): string
{
return $this->receipt_reference;
}
/**
* @return Boolean
*/
public function getIsAutoMapped(): Boolean
{
return $this->is_auto_mapped;
}
/**
* @return int
*/
public function getStatus(): int
{
return $this->status;
}
}
@@ -0,0 +1,46 @@
<?php
namespace App\Classes\Modules\Accounting\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class BankStatementTrasactionOwnerObject implements DataTransferObject
{
/** @var string */
private $pay_for;
/** @var string */
private $system_references;
// /** @var int|null */
// private $type;
/**
* BankStatementDetailObject constructor.
* @param string $pay_for
* @param string $system_references
* @param int|null $type
*/
public function __construct(string $pay_for, string $system_references)
{
$this->pay_for = $pay_for;
$this->system_references = $system_references;
}
/**
* @return string
*/
public function getPayFor(): string
{
return $this->pay_for;
}
/**
* @return string
*/
public function getSystemReferences(): string
{
return $this->system_references;
}
}
@@ -0,0 +1,41 @@
<?php
namespace App\Classes\Modules\Accounting\Processors;
use App\Classes\Exceptions\MalformedRequestException;
use App\Models\Transaction;
use Illuminate\Support\Facades\Log;
class ChecksBillNumber
{
public function execute($bill_no, $system_reference)
{
if ($system_reference == 'izyim') {
try {
$url = 'https://izyim.cief-malaysia.com/public/api/v1/transactions/mappable/query';
$client = new \GuzzleHttp\Client(['verify' => false]);
$response = $client->request('GET', $url . '?api-key=510acd13d8d24375cf038ad626c282565451461a9c2399357e0b65365300787e&filters={"bill_no":"' . $bill_no . '"}');
$body = $response->getBody();
$data = json_decode($body, true);
$payload = $data['payload'];
return $payload['data'];
} catch (\Exception $exception) {
dd($exception->getMessage());
// preg_match('/\{.*\}/s', $exception->getMessage(), $matches);
// $jsonError = json_decode($matches[0]);
// Retrieved Transactions failed
// throw new MalformedRequestException($jsonError->title);
}
}
if ($system_reference == 'exchange') {
$transaction = Transaction::where('bill_no', $bill_no)->first();
if ($transaction) {
return $transaction;
}
}
// if not found
throw new MalformedRequestException('Bill Number Not Found.');
}
}
@@ -0,0 +1,255 @@
<?php
namespace App\Classes\Modules\Accounting\Processors;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\StatementTransactionOwnerType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Booking;
use App\Models\Company;
use App\Models\Group;
use App\Models\StatementTransaction;
use App\Models\Transaction;
use App\Models\Wallet;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class CreateBankStatementTransactionOwnersProcessor
{
/**
* @return void
*/
public function execute() {
$transactions = StatementTransaction::whereDoesntHave('owners', function($query){
return $query->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
})->orderBy('posting_date')->get();
// $transactions = StatementTransaction::whereDoesntHave('owners')->where('amount', '<', 0)->get();
foreach ($transactions as $transaction) {
if($transaction->amount > 0){
// Exchange Sales
$creditTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::PAYMENT, Booking::class, PaymentMethodType::WALLET, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
foreach ($creditTransactions as $creditTransaction) {
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::SALES,
'system' => 'EXCHANGE',
'owner_type' => Transaction::class,
'owner_id'=> $creditTransaction->id,
'owner_reference'=> $creditTransaction->owner->marking,
]);
}
// Shipping Portal Sales
$creditTransactions = $this->getTransactionsFromShippingPortal($transaction->amount, $this->getDateRange($transaction->posting_date), 2);
foreach ($creditTransactions as $creditTransaction) {
if($creditTransaction['owner_type'] === Wallet::class) continue;
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::SALES,
'system' => 'SHIPPING_PORTAL',
'owner_type' => $creditTransaction['owner_type'],
'owner_id'=> $creditTransaction['owner_id'],
'owner_reference'=> $creditTransaction['owner_reference'],
]);
}
// Exchange Wallet Top Up
$creditTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::TOP_UP, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
foreach ($creditTransactions as $creditTransaction) {
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::WALLET_TOP_UP,
'system' => 'EXCHANGE',
'owner_type' => Transaction::class,
'owner_id'=> $creditTransaction->id,
'owner_reference'=> $creditTransaction->owner->owner->reference,
]);
}
$creditTransactions = $this->getTransactionsFromShippingPortal($transaction->amount, $this->getDateRange($transaction->posting_date), 5);
foreach ($creditTransactions as $creditTransaction) {
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::WALLET_TOP_UP,
'system' => 'SHIPPING_PORTAL',
'owner_type' => $creditTransaction['owner_type'],
'owner_id'=> $creditTransaction['owner_id'],
'owner_reference'=> $creditTransaction['owner_reference'],
]);
}
// fpx charge refund
if($transaction->transaction_description === 'DUITNOW S/CHRG REFUND'){
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::FPX_CHARGE_REFUND
]);
}
// Customer Refund
// INTERNAL_BANK_TRANSFER_IN
if(str_contains($transaction->transaction_description_2, 'CIEF WORLDWIDE')){
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::INTERNAL_BANK_TRANSFER_IN
]);
}
}
if($transaction->amount < 0){
// Supplier Purchase Order Payments
foreach (['YSN', 'HCK', 'ATVANTIC', 'HIGH HILL'] as $reference){
if(str_contains($transaction->transaction_description.' '.$transaction->transaction_description_2.' '.$transaction->transaction_description_3.' '.$transaction->transaction_description_4.' '.$transaction->transaction_description_5 , $reference)) {
$paymentDateStart = $transaction->posting_date->startOfDay()->subDays(1);
$paymentDateEnd = $transaction->posting_date->endOfDay();
if($paymentDateStart->dayOfWeek === Carbon::SUNDAY){
$paymentDateStart->subDays(2);
}
$issuer = Company::where('name', 'like', '%'.$reference.'%')->get()->pluck('id');
$debitTransactions = Group::whereIn('issuer', $issuer)->where('amount', '>=', (($transaction->amount * -1) - 0.01))
->where('amount', '<=', (($transaction->amount * -1) + 0.01))->whereDate('created_at', '>=', $paymentDateStart)->whereDate('created_at', '<=', $paymentDateEnd)->get();
foreach ($debitTransactions as $debitTransaction) {
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::SUPPLIER_PAYMENT,
'system' => 'EXCHANGE',
'owner_type' => Group::class,
'owner_id'=> $debitTransaction->id,
'owner_reference'=> $debitTransaction->reference
]);
}
}
}
// Exchange Wallet Withdrawal
$debitTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::DEBIT_NOTE, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
foreach ($debitTransactions as $debitTransaction) {
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::WALLET_WITHDRAWAL,
'system' => 'EXCHANGE',
'owner_type' => Transaction::class,
'owner_id'=> $debitTransaction->id,
'owner_reference'=> $debitTransaction->owner->owner->marking,
]);
}
// SALARY
// STATUTORY
if(str_contains($transaction->transaction_description_2, 'PEMBANGUNAN SUMBER') || str_contains($transaction->transaction_description_2, 'HASIL') || str_contains($transaction->transaction_description_2, 'PERTUBUHAN KESELAMAT') || str_contains($transaction->transaction_description_2, 'KUMPULAN WANG SIMPAN')){
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::STATUTORY
]);
}
// FPX_CHARGE
if($transaction->transaction_description === 'DR DUITNOW S/CHRG' || str_contains($transaction->transaction_description, 'Manual FPX') || str_contains($transaction->transaction_description, 'CMS - DR FPX CHG')){
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::FPX_CHARGE
]);
}
// BANK_CHARGE
if($transaction->transaction_description === 'CMS - DR CORP CHG' || $transaction->transaction_description === 'MONTHLY PROFIT DEBIT'){
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::BANK_CHARGE
]);
}
// CREDIT_CARD_PAYMENT
if(str_contains($transaction->transaction_description_2, 'VISA CARD')){
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::CREDIT_CARD_PAYMENT
]);
}
// INTERNAL_BANK_TRANSFER_OUT
if(str_contains($transaction->transaction_description_2, 'CIEF WORLDWIDE') || str_contains($transaction->transaction_description_2, 'CIEF WORLWIDE') || str_contains($transaction->transaction_description_2, 'IZYIM GLOBAL')){
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::INTERNAL_BANK_TRANSFER_OUT
]);
}
// non-operational charges
if(str_contains($transaction->transaction_description_2, 'HIRE PURCHASE') || str_contains($transaction->transaction_description_2, 'TENAGA NASIONAL') || str_contains($transaction->transaction_description, 'CABLE CHARGE') || str_contains($transaction->transaction_description_2, 'CTOS DATA SYSTEMS') || str_contains($transaction->transaction_description_2, 'MAXIS')){
$transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::NON_OPERATIONAL
]);
}
}
}
}
private function getTransactions($date, $amount, $type, $ownerType, $paymentMethod, $statuses, $model = Transaction::class) {
$query = $model::whereIn('status', $statuses)
->where(function ($query) use ($ownerType, $paymentMethod, $type) {
if ($ownerType) {
$query->where('owner_type', $ownerType);
}
if ($paymentMethod) {
$query->where('payment_method', '!=', $paymentMethod);
}
if ($type) {
$query->where('type', $type);
}
})
->whereDate('created_at', $date->format('Y-m-d'))
->where('amount', '>', ($amount - 0.01))
->where('amount', '<', ($amount + 0.01));
return $query->get();
}
private function getTransactionsFromShippingPortal($amount, $dateRange, $type){
$url = 'https://izyim.cief-malaysia.com/public/api/v1/transactions/mappable/query';
return $this->getFromShippingPortal($amount, $dateRange, $url, $type);
}
private function getGroupsFromShippingPortal($amount, $dateRange, $type){
$url = 'https://izyim.cief-malaysia.com/public/api/v1/groups/query';
return $this->getFromShippingPortal($amount, $dateRange, $url, $type);
}
private function getFromShippingPortal($amount, $dateRange, $url, $type){
try{
$client = new \GuzzleHttp\Client(['verify' => false]);
$response = $client->request('GET', $url.'?api-key=510acd13d8d24375cf038ad626c282565451461a9c2399357e0b65365300787e&filters={"order_by":{"column":"id","DESC":true},"status_in":[2],"type":2,"created_after":"'.$dateRange['start_date'].'","created_before":"'.$dateRange['end_date'].'","amount_exceed":'.($amount - 0.01).',"amount_short":'.($amount + 0.01).',"type_in:"['.$type.']}');
$body = $response->getBody();
$data = json_decode($body, true);
$payload = $data['payload'];
$transactions2 = $payload['data'];
return $transactions2;
}catch(\Exception $exception){
Log::error($exception);
return [];
}
}
private function getDateRange(string $dateStr) {
// Create a DateTime object from the input string
$date = strtotime($dateStr);
// Get the first day of the month
$today = date('Y-m-d', strtotime('-1 day', $date));
// Get the first day of the next month
$nextDay = date('Y-m-d', strtotime('+1 day', $date));
return [
'start_date' => $today,
'end_date' => $nextDay,
];
}
}
@@ -0,0 +1,34 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Classes\Modules\Accounting\DataTransferObjects\BankStatementTransactionObject;
use App\Models\StatementTransactionOwner;
class CreatesBankStatementTransactionOwner extends AbstractUpdateRecord
{
/**
* @param StatementTransactionOwner $model
* @param BankStatementDetailsObject $object
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(BankStatementTransactionObject $object)
{
$model = new StatementTransactionOwner();
$model->statement_transaction_id = $object->getStatementTransactionId();
$model->type = $object->getType();
$model->system = $object->getSystem();
$model->owner_type = $object->getOwnerType();
$model->owner_id = $object->getOwnerId();
$model->invoice_reference = $object->getInvoiceReference();
$model->receipt_reference = $object->getReceipteReference();
$model->is_auto_mapped = $object->getIsAutoMapped();
$model->status = $object->getStatus();
return $this->handler($model);
}
}
@@ -0,0 +1,34 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractFetchRecord;
use Illuminate\Database\Eloquent\Builder;
use App\Models\StatementTransactionOwner;
class FetchesBankStatementDetails extends AbstractFetchRecord
{
/** @var StatementTransactionOwner */
private $repository;
/**
* FetchesBankStatementDetails constructor.
* @param StatementTransactionOwner $repository
*/
public function __construct(StatementTransactionOwner $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
public function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractFetchRecord;
use Illuminate\Database\Eloquent\Builder;
use App\Models\StatementTransaction;
class FetchesBankStatementTransaction extends AbstractFetchRecord
{
/** @var StatementTransaction */
private $repository;
/**
* FetchesBankStatementDetails constructor.
* @param StatementTransactionOwner $repository
*/
public function __construct(StatementTransaction $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
public function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractListRecord;
use Illuminate\Database\Eloquent\Builder;
use App\Models\StatementTransactionOwner;
class ListsBankStatementDetails extends AbstractListRecord
{
/** @var StatementTransactionOwner */
private $repository;
/**
* ListsBankStatementDetails constructor.
* @param StatementTransactionOwner $repository
*/
public function __construct(StatementTransactionOwner $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
public function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractListRecord;
use App\Models\StatementTransaction;
use Illuminate\Database\Eloquent\Builder;
use App\Models\StatementTransactionOwner;
class ListsBankStatementTransactions extends AbstractListRecord
{
/** @var StatementTransaction */
private $repository;
/**
* ListsBankStatementDetails constructor.
* @param StatementTransaction $repository
*/
public function __construct(StatementTransaction $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
public function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Classes\Modules\Accounting\DataTransferObjects\BankStatementDetailObject;
use App\Models\StatementTransactionOwner;
class UpdatesBankStatementDetails extends AbstractUpdateRecord
{
/**
* @param StatementTransactionOwner $model
* @param BankStatementDetailsObject $object
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(StatementTransactionOwner $model, BankStatementDetailObject $object)
{
$model->system_references = $object->getSystemReferences();
$model->pay_for = $object->getPayFor();
return $this->handler($model);
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Classes\Modules\Accounting\DataTransferObjects\BankStatementTransactionObject;
use App\Models\StatementTransactionOwner;
class UpdatesBankStatementTransactionOwner extends AbstractUpdateRecord
{
/**
* @param StatementTransactionOwner $model
* @param BankStatementDetailsObject $object
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(StatementTransactionOwner $model, BankStatementTransactionObject $object)
{
$model->statement_transaction_id = $object->getStatementTransactionId();
$model->type = $object->getType();
$model->system = $object->getSystem();
$model->owner_type = $object->getOwnerType();
$model->owner_id = $object->getOwnerId();
$model->invoice_reference = $object->getInvoiceReference();
$model->receipt_reference = $object->getReceipteReference();
$model->is_auto_mapped = $object->getIsAutoMapped();
$model->status = $object->getStatus();
return $this->handler($model);
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Models\StatementTransactionOwner;
class UpdatesBankStatementTransactionOwnerStatus extends AbstractUpdateRecord
{
/**
* @param StatementTransactionOwner $model
* @param BankStatementDetailsObject $object
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(StatementTransactionOwner $model, int $status)
{
$model->status = $status;
return $this->handler($model);
}
}
@@ -65,7 +65,7 @@ class AutoPurchaseOrderFillLogic extends AbstractControllerLogic
public function logic(Request $request) : JsonResponse
{
$bookings = Booking::where('service_id', '!=', 4)->where(function($query){
return $query->whereMonth('created_at', '=', 04)->whereYear('created_at', 2023);
return $query->whereMonth('created_at', '=', 03)->whereYear('created_at', 2023);
})->whereDoesntHave('transactions', function($q){
$q->where('type', TransactionType::PURCHASE_ORDER);
$q->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED]);
@@ -17,6 +17,7 @@ use App\Classes\Modules\PerfexCRM\Processors\BookingToPerfexCRMProcessor;
use App\Http\Resources\BookingResource;
use App\Models\Booking;
use ErrorException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -82,6 +83,7 @@ class CreateBookingLogic extends AbstractControllerLogic
$this->canCreateBooking->passes($object);
/** @var Booking $booking */
$booking = $this->createsBooking->execute($company, $object);
if(config('perfexcrm.is_enabled') == 'true'){
@@ -28,7 +28,7 @@ class FileObject implements DataTransferObject
*/
public function getData()
{
return in_array($this->getExtension(), ['pdf', 'excel']) ? $this->data : (new imageManager())->make($this->data);
return in_array($this->getExtension(), ['pdf', 'excel', 'text']) ? $this->data : (new imageManager())->make($this->data);
}
/**
@@ -66,7 +66,7 @@ class FileObject implements DataTransferObject
*/
public function getDecodedData(): string
{
return in_array($this->getExtension(), ['pdf', 'excel']) ?
return in_array($this->getExtension(), ['pdf', 'excel', 'text']) ?
base64_decode((explode('base64,', $this->getData()))[1]):
$this->getData()->encode('data-url')->encoded;
}
@@ -83,4 +83,4 @@ class FileObject implements DataTransferObject
}
}
@@ -36,7 +36,7 @@ class ConvertsBase64ToFile
foreach ($files as $file) {
$object = new FileObject($file);
in_array($object->getExtension(), ['pdf', 'excel']) ? $this->generatePDF($object) : $this->generateImage($object);
in_array($object->getExtension(), ['pdf', 'excel', 'text']) ? $this->generatePDF($object) : $this->generateImage($object);
}
@@ -122,4 +122,4 @@ class ConvertsBase64ToFile
]);
}
}
}
@@ -0,0 +1,82 @@
<?php
namespace App\Classes\Modules\Imports\Services;
use App\Models\AccountStatement;
use App\Models\StatementAccount;
use App\Models\StatementTransaction;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
class BankStatementImport implements ToCollection, WithHeadingRow
{
public function collection(Collection $rows)
{
$accountNumber = null;
foreach ($rows as $row) {
if ($row->has('account_number')) {
// If the row has an account number, create a new statement account
$accountNumber = $row->get('account_number');
$accountType = $row->get('account_type');
$accountName = $row->get('account_name');
$accountCurrency = $row->get('account_currency');
$account = StatementAccount::updateOrCreate(
['number' => $accountNumber],
[
'type' => $accountType,
'name' => $accountName,
'currency' => $accountCurrency,
]
);
} else {
// Otherwise, create a new statement transaction for the current statement account
$dateFrom = $row->get('date_from');
$dateTo = $row->get('date_to');
$totalAmount = $row->get('total_amount');
$beginBalance = $row->get('begin_balance');
$endBalance = $row->get('end_balance');
$statement = AccountStatement::updateOrCreate(
[
'account_id' => $account->id,
'date_from' => $dateFrom,
'date_to' => $dateTo,
],
[
'total_amount' => $totalAmount,
'begin_balance' => $beginBalance,
'end_balance' => $endBalance,
]
);
$transactionDate = $row->get('transaction_date');
$transactionTime = $row->get('transaction_time');
$postingDate = $row->get('posting_date');
$transactionDescription = $row->get('transaction_description');
$transactionRef = $row->get('transaction_ref');
$amount = $row->get('amount');
$tellerId = $row->get('teller_id');
$branchChannel = $row->get('branch_channel');
$transactionCode = $row->get('transaction_code');
$transaction = new StatementTransaction([
'statement_id' => $statement->id,
'transaction_date' => $transactionDate,
'transaction_time' => $transactionTime,
'posting_date' => $postingDate,
'transaction_description' => $transactionDescription,
'transaction_ref' => $transactionRef,
'amount' => $amount,
'teller_id' => $tellerId,
'branch_channel' => $branchChannel,
'transaction_code' => $transactionCode,
]);
$transaction->save();
}
}
}
}
@@ -2,6 +2,7 @@
namespace App\Classes\Modules\PerfexCRM\Processors;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\Modules\PerfexCRM\DataTransferObjects\UpdatePerfexCRMObject;
use App\Classes\Jobs\UpdatePerfexCRM;
use App\Models\Booking;
@@ -11,10 +12,10 @@ class BookingToPerfexCRMProcessor
{
/**
* @param Booking $booking
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
* @return bool
* @throws MalformedRequestException
*/
public function execute(Booking $booking)
public function execute(Booking $booking): bool
{
$companyReference = $booking->company->reference;
$companyName = $booking->company->name;
@@ -37,6 +38,7 @@ class BookingToPerfexCRMProcessor
[],
[]
);
UpdatePerfexCRM::dispatch($updatePerfexCRMObject, null, null);
return true;
@@ -0,0 +1,246 @@
<?php
namespace App\Classes\Modules\PerfexCRM\Processors;
use App\Classes\Modules\PerfexCRM\DataTransferObjects\UpdatePerfexCRMObject;
use App\Classes\ValueObjects\Constants\PerfexCRMTasks;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Classes\ValueObjects\Constants\PerfexCRMProjectStatus;
use App\Classes\ValueObjects\Constants\PerfexCRMTaskStatus;
use App\Classes\Jobs\UpdatePerfexCRMInvoice;
use App\Classes\Jobs\UpdatePerfexCRM;
use App\Classes\Jobs\UpdatePerfexCRMPrelude;
use App\Models\Booking;
use App\Models\Transaction;
use Illuminate\Support\Facades\Log;
class TransactionToPerfexCRMProcessorV2
{
public function execute(Transaction $model, int $status)
{
try {
if(in_array($model->type, [TransactionType::PAYMENT, TransactionType::BILL, TransactionType::PURCHASE_ORDER])) {
$transaction = ($model->type === TransactionType::BILL ? $model->owner : $model);
$bookingInfo = $this->extractBookingInfo($transaction);
$projectName = 'Exchange | ' . $bookingInfo['serviceTypeName'] . ' | ' . $bookingInfo['bookingMarking'];
$tasks = $this->defineTasks($model, $status);
if(count($tasks) > 0) {
$updatePerfexCRMObject = new UpdatePerfexCRMObject(
$bookingInfo['companyName'],
$bookingInfo['companyReference'],
$bookingInfo['contactName'],
$bookingInfo['contactEmail'],
$bookingInfo['bookingMarking'],
$projectName,
PerfexCRMProjectStatus::IN_PROGRESS,
[],
$tasks
);
$this->dispatchUpdateJob($transaction, $status, $updatePerfexCRMObject);
}
}
} catch (\Exception $exception) {
Log::error('TransactionToPerfexCRMProcessor debug:');
Log::error($exception);
}
return true;
}
private function extractBookingInfo(Transaction $model): array
{
$bookingInfo = [];
$bookingInfo['companyReference'] = $model->owner->company->reference;
$bookingInfo['companyName'] = $model->owner->company->name;
$bookingInfo['contactEmail'] = $model->owner->company->employees()->first()->email;
$bookingInfo['contactName'] = $model->owner->company->employees()->first()->name;
$bookingInfo['bookingMarking'] = $model->owner->marking;
$bookingInfo['serviceTypeName'] = $model->owner->company->services()->where('id', $model->owner->service_id)->first()->name;
return $bookingInfo;
}
private function defineTasks(Transaction $model, int $status): array
{
$tasks = [];
$ownerServiceId = $model->type === TransactionType::BILL ? $model->owner->owner->service_id : $model->owner->service_id;
if ($status === ApprovalStatus::PENDING_VERIFICATION) {
$tasks = $this->handlePendingVerificationStatus($model, $ownerServiceId);
} elseif ($status === ApprovalStatus::APPROVED) {
$tasks = $this->handleApprovedStatus($model, $ownerServiceId);
}
return $tasks;
}
private function handlePendingVerificationStatus(Transaction $model, int $serviceId): array
{
$tasks = [];
switch($model->type) {
case TransactionType::PAYMENT:
$tasks = $this->definePaymentTasks($model);
break;
case TransactionType::PURCHASE_ORDER:
if ($serviceId === 1 || $serviceId === 3) {
$task = PerfexCRMTasks::TASK_PURCHASE_ORDER_1;
$task['status'] = PerfexCRMTaskStatus::IN_PROGRESS;
$tasks = [$task];
}
break;
case TransactionType::BILL:
$tasks = $this->handleBillPendingStatus($serviceId);
break;
}
return $tasks;
}
private function handleApprovedStatus(Transaction $model, int $serviceId): array
{
$tasks = [];
switch($model->type) {
case TransactionType::PAYMENT:
$tasks = $this->handlePaymentApprovedStatus($serviceId);
break;
case TransactionType::BILL:
if ($serviceId === 4) {
$tasks = $this->completeTask(PerfexCRMTasks::TASK_1688_PAYMENT_9, PerfexCRMTasks::TASK_1688_PAYMENT_10);
}
break;
}
return $tasks;
}
private function handlePaymentApprovedStatus(int $serviceId): array
{
$tasks = [];
switch ($serviceId) {
case 1:
$tasks = $this->completeTask(PerfexCRMTasks::TASK_1_DAY_TRANSFER_2, PerfexCRMTasks::TASK_1_DAY_TRANSFER_5);
break;
case 3:
$tasks = $this->completeTask(PerfexCRMTasks::TASK_3_DAY_TRANSFER_2, PerfexCRMTasks::TASK_3_DAY_TRANSFER_5);
break;
case 4:
$tasks = $this->completeTask(PerfexCRMTasks::TASK_1688_PAYMENT_2, PerfexCRMTasks::TASK_1688_PAYMENT_5);
break;
}
return $tasks;
}
private function handleBillPendingStatus(int $serviceId): array
{
$tasks = [];
switch ($serviceId) {
case 1:
$tasks = $this->completeTask(PerfexCRMTasks::TASK_1_DAY_TRANSFER_5, PerfexCRMTasks::TASK_1_DAY_TRANSFER_6);
break;
case 3:
$tasks = $this->completeTask(PerfexCRMTasks::TASK_3_DAY_TRANSFER_5, PerfexCRMTasks::TASK_3_DAY_TRANSFER_6);
break;
case 4:
$tasks = $this->completeTask(PerfexCRMTasks::TASK_1688_PAYMENT_5, PerfexCRMTasks::TASK_1688_PAYMENT_7);
break;
}
return $tasks;
}
private function definePaymentTasks(Transaction $model): array
{
$tasks = [
// PerfexCRMTasks::TASK_1
];
if ($model->owner->service_id === 1) {
$tasks = array_merge($tasks, $this->oneDayTransferTasks());
} elseif ($model->owner->service_id === 3) {
$tasks = array_merge($tasks, $this->threeDayTransferTasks());
} elseif ($model->owner->service_id === 4) {
$tasks = array_merge($tasks, $this->payment1688Tasks());
}
if ($model->owner->service_id === 1 || $model->owner->service_id === 3) {
$purchaseOrder = $model->booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->complete()->first();
if(is_null($purchaseOrder)){
$tasks = array_merge($tasks, $this->purchaseOrderTasks());
}
}
return $tasks;
}
private function oneDayTransferTasks(): array
{
return [
// PerfexCRMTasks::TASK_1_DAY_TRANSFER_1,
PerfexCRMTasks::TASK_1_DAY_TRANSFER_2,
// PerfexCRMTasks::TASK_1_DAY_TRANSFER_3,
// PerfexCRMTasks::TASK_1_DAY_TRANSFER_3_1,
// PerfexCRMTasks::TASK_1_DAY_TRANSFER_4,
PerfexCRMTasks::TASK_1_DAY_TRANSFER_5,
PerfexCRMTasks::TASK_1_DAY_TRANSFER_6
];
}
private function threeDayTransferTasks(): array
{
return [
// PerfexCRMTasks::TASK_3_DAY_TRANSFER_1,
PerfexCRMTasks::TASK_3_DAY_TRANSFER_2,
// PerfexCRMTasks::TASK_3_DAY_TRANSFER_3,
// PerfexCRMTasks::TASK_3_DAY_TRANSFER_3_1,
// PerfexCRMTasks::TASK_3_DAY_TRANSFER_4,
PerfexCRMTasks::TASK_3_DAY_TRANSFER_5,
PerfexCRMTasks::TASK_3_DAY_TRANSFER_6
];
}
private function payment1688Tasks(): array
{
return [
// PerfexCRMTasks::TASK_1688_PAYMENT_1,
PerfexCRMTasks::TASK_1688_PAYMENT_2,
// PerfexCRMTasks::TASK_1688_PAYMENT_3,
// PerfexCRMTasks::TASK_1688_PAYMENT_3_1,
// PerfexCRMTasks::TASK_1688_PAYMENT_4,
PerfexCRMTasks::TASK_1688_PAYMENT_5,
// PerfexCRMTasks::TASK_1688_PAYMENT_6,
PerfexCRMTasks::TASK_1688_PAYMENT_7,
PerfexCRMTasks::TASK_1688_PAYMENT_8,
PerfexCRMTasks::TASK_1688_PAYMENT_9,
PerfexCRMTasks::TASK_1688_PAYMENT_10,
PerfexCRMTasks::TASK_1688_PAYMENT_11,
PerfexCRMTasks::TASK_1688_PAYMENT_12,
// PerfexCRMTasks::TASK_1688_PAYMENT_13
];
}
private function purchaseOrderTasks(): array
{
$potask1 = PerfexCRMTasks::TASK_PURCHASE_ORDER_1;
$potask1['status'] = PerfexCRMTaskStatus::NOT_STARTED;
return [
$potask1,
// PerfexCRMTasks::TASK_PURCHASE_ORDER_2
];
}
private function dispatchUpdateJob(Transaction $model, int $status, UpdatePerfexCRMObject $updatePerfexCRMObject)
{
$withInvoice = ($model->type === TransactionType::PAYMENT && $model->owner === Booking::class && $status === ApprovalStatus::APPROVED);
UpdatePerfexCRMPrelude::dispatch($model, $updatePerfexCRMObject, $withInvoice);
}
private function completeTask($startTask, $endTask) {
$startTask['status'] = PerfexCRMTaskStatus::COMPLETED;
$endTask['status'] = PerfexCRMTaskStatus::IN_PROGRESS;
return [$startTask, $endTask];
}
}
@@ -109,7 +109,7 @@ class UpdatePerfexCRMProcessor
*/
public function execute(UpdatePerfexCRMObject $updatePerfexCRMObject) {
$projectId = "";
// Customer has to exist first before Project can appear under it
// Check with Perfex CRM, if this user (email) was previously a lead, should automatically now become a customer
$crmCompany = $updatePerfexCRMObject->getCompanyName();
@@ -205,7 +205,9 @@ class UpdatePerfexCRMProcessor
// Get existing or create task
$result = $this->fetchesPerfexCRMTask->execute($tasks[$count]['name'], $milestoneId, 'project', $projectId);
if(isset($result->payload)){
if($taskStatus != PerfexCRMTaskStatus::NOT_STARTED && $result->payload[0]['status'] == PerfexCRMTaskStatus::NOT_STARTED)
//&& $result->payload[0]['status'] == PerfexCRMTaskStatus::NOT_STARTED
Log::info(json_encode([$taskStatus]));
if($taskStatus != PerfexCRMTaskStatus::NOT_STARTED )
{
$task = $result->payload[0];
$result = $this->updatesPerfexCRMTask->execute($task['id'], $task['name'], $task['milestone'], $task['rel_id'], $taskStatus, $task['startdate'], is_null($task['duedate']) ? '': $task['duedate']);
@@ -90,10 +90,11 @@ class CreateSupplierTransactionProcessor
$object = new TransactionObject($billNumber, TransactionType::BILL, $supplier->id, 1,
$supplier->banks()->where('default', true)->first()->id, PaymentMethodType::CASH,
$payment->original_amount * (1 / $rate), $payment->original_amount, 1, $payment->original_currency_id,
$rate, 0, $serviceCharge, null, ApprovalStatus::PENDING_VERIFICATION);
$rate, 0, $serviceCharge, null, ApprovalStatus::PENDING_SUBMISSION);
/** @var Transaction $billTransaction */
$billTransaction = $this->createsTransaction->execute($payment, $object);
$this->updatesTransactionStatus->execute($billTransaction, ApprovalStatus::PENDING_VERIFICATION);
$this->pushBill($billTransaction);
$transferFeeNumber = $this->generatesTransactionBillNumber->execute('TRFR-');
@@ -140,4 +141,4 @@ class CreateSupplierTransactionProcessor
$this->transferFee->push($transferFee);
}
}
}
@@ -3,23 +3,24 @@
namespace App\Classes\Modules\Transactions\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Classes\Modules\PerfexCRM\Processors\TransactionToPerfexCRMProcessorV2;
use App\Models\Transaction;
use App\Classes\Modules\PerfexCRM\Processors\TransactionToPerfexCRMProcessor;
class UpdatesTransactionStatus extends AbstractUpdateRecord
{
/** @var TransactionToPerfexCRMProcessor */
/** @var TransactionToPerfexCRMProcessorV2 */
private $transactionToPerfexCRMProcessor;
/**
* UpdatesTransactionStatus constructor.
* @param TransactionToPerfexCRMProcessor $transactionToPerfexCRMProcessor
* @param TransactionToPerfexCRMProcessorV2 $transactionToPerfexCRMProcessor
*/
public function __construct(TransactionToPerfexCRMProcessor $transactionToPerfexCRMProcessor)
public function __construct(TransactionToPerfexCRMProcessorV2 $transactionToPerfexCRMProcessor)
{
$this->transactionToPerfexCRMProcessor = $transactionToPerfexCRMProcessor;
}
/**
* @param Transaction $model
* @param int $status
@@ -23,6 +23,7 @@ class FileType
'application/pdf' => 'pdf',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'excel',
'application/vnd.ms-excel' => 'excel',
'text/plain' => 'text',
];
}
}
@@ -43,9 +43,9 @@ class PerfexCRMTasks
○ Outcomes: The payment will be approved in exchange, allowing the next steps in the process to be initiated.<br>',
'milestone' => '',
'reference' => 'TASK_1_DAY_TRANSFER_2',
'on_task_completion' => 'TASK_1_DAY_TRANSFER_3,TASK_1_DAY_TRANSFER_5',
'on_task_completion' => 'TASK_1_DAY_TRANSFER_5',
'department' => 'Accounts',
'status' => '',
'status' => PerfexCRMTaskStatus::IN_PROGRESS,
'priority' => PerfexCRMTaskPriority::HIGH,
'duedate' => 0
];
@@ -160,9 +160,9 @@ class PerfexCRMTasks
○ Link to order page: {link_transfer}<br>',
'milestone' => '',
'reference' => 'TASK_3_DAY_TRANSFER_2',
'on_task_completion' => 'TASK_3_DAY_TRANSFER_3,TASK_3_DAY_TRANSFER_5',
'on_task_completion' => 'TASK_3_DAY_TRANSFER_5',
'department' => 'Accounts',
'status' => '',
'status' => PerfexCRMTaskStatus::IN_PROGRESS,
'priority' => PerfexCRMTaskPriority::HIGH,
'duedate' => 0
];
@@ -286,9 +286,9 @@ class PerfexCRMTasks
○ Outcomes: The payment will be approved in exchange, allowing the next steps in the process to be initiated.<br>',
'milestone' => '',
'reference' => 'TASK_1688_PAYMENT_2',
'on_task_completion' => 'TASK_1688_PAYMENT_3,TASK_1688_PAYMENT_5',
'on_task_completion' => 'TASK_1688_PAYMENT_5',
'department' => 'Accounts',
'status' => '',
'status' => PerfexCRMTaskStatus::IN_PROGRESS,
'priority' => PerfexCRMTaskPriority::HIGH,
'duedate' => 0
];
@@ -353,7 +353,7 @@ class PerfexCRMTasks
○ Outcomes: The order is placed with a supplier and the customer\'s order is confirmed.<br>',
'milestone' => '',
'reference' => 'TASK_1688_PAYMENT_5',
'on_task_completion' => 'TASK_1688_PAYMENT_6',
'on_task_completion' => 'TASK_1688_PAYMENT_7',
'department' => 'Operations',
'status' => '',
'priority' => PerfexCRMTaskPriority::HIGH,
@@ -454,14 +454,14 @@ class PerfexCRMTasks
○ link: {link_transfer}<br>',
'milestone' => '',
'reference' => 'TASK_1688_PAYMENT_12',
'on_task_completion' => 'TASK_1688_PAYMENT_13',
'on_task_completion' => '',
'department' => 'Operations',
'status' => '',
'priority' => PerfexCRMTaskPriority::LOW,
'duedate' => 1
];
public const TASK_1688_PAYMENT_13 = [
'name' => 'Complete Order bookeeping',
'name' => 'Complete Order bookkeeping',
'description' => '○ Purpose: To complete the bookkeeping for the booking.<br>
○ Initial Status: Not Started<br>
○ Deadline: Same day<br>
@@ -493,7 +493,7 @@ class PerfexCRMTasks
○ Outcomes: Bank transaction is mapped successfully, allowing the next steps in the process to be initiated.<br>',
'milestone' => '',
'reference' => 'TASK_PURCHASE_ORDER_1',
'on_task_completion' => 'TASK_PURCHASE_ORDER_2',
'on_task_completion' => '',
'department' => 'Operations',
'status' => PerfexCRMTaskStatus::IN_PROGRESS,
'priority' => PerfexCRMTaskPriority::LOW,
@@ -0,0 +1,37 @@
<?php
namespace App\Classes\ValueObjects\Constants;
final class StatementTransactionOwnerType {
public const UNKNOWN = 0;
public const SALES = 1;
public const WALLET_TOP_UP = 2;
public const WALLET_WITHDRAWAL = 3;
public const CUSTOMER_REFUND = 4;
public const SUPPLIER_PAYMENT = 5;
public const INTERNAL_BANK_TRANSFER_OUT = 6;
public const INTERNAL_BANK_TRANSFER_IN = 7;
public const SALARY = 8;
public const STATUTORY = 9;
public const FPX_CHARGE = 10;
public const FPX_CHARGE_REFUND = 11;
public const BANK_CHARGE = 12;
public const CREDIT_CARD_PAYMENT = 13;
public const NON_OPERATIONAL = 14;
}
@@ -0,0 +1,16 @@
<?php
namespace App\Classes\ValueObjects\Constants;
final class SystemType {
public const EXCHANGE = 'EXCHANGE';
public const SHIPPING_PORTAL = 'SHIPPING_PORTAL';
public const SYSTEM_NAMES = [
'exchange' => self::EXCHANGE,
'shipping_portal' => self::SHIPPING_PORTAL,
'izyim' => self::SHIPPING_PORTAL,
];
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Accounting;
use App\Classes\Modules\Accounting\ControllersLogic\ApproveDuplicateBankStatementDetailsStatusLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ApproveDuplicateBankStatementDetailsStatusController
{
/**
* @param Request $request
* @param ApprovePaymentLogic $logic
* @return JsonResponse
*/
public function update(Request $request, ApproveDuplicateBankStatementDetailsStatusLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,365 @@
<?php
namespace App\Http\Controllers\Accounting;
use App\Classes\Jobs\CreateBankStatementTransactionOwners;
use App\Classes\Modules\Accounting\ControllersLogic\ImportBankStatementLogic;
use App\Classes\Modules\Accounting\ControllersLogic\ListBankStatementDetailsLogic;
use App\Classes\Modules\Accounting\ControllersLogic\ListBankStatementTransactionsLogic;
use App\Classes\Modules\Accounting\ControllersLogic\UpdateBankStatementDetailLogic;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\StatementAccount;
use App\Models\AccountStatement;
use App\Models\Booking;
use App\Models\Company;
use App\Models\Group;
use App\Models\StatementTransactionOwner;
use App\Models\Transaction;
use App\Models\Wallet;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use DateTime;
class BankStatementController extends Controller
{
public function index(Request $request)
{
$selectedAccount = $request->input('account');
$search = $request->input('search');
$accounts = StatementAccount::all();
$statementsQuery = AccountStatement::query();
if ($selectedAccount) {
$statementsQuery->where('statement_account_id', $selectedAccount);
}
if ($search) {
$statementsQuery->where(function ($query) use ($search) {
$query->where('date_from', 'LIKE', "%$search%")
->orWhere('date_to', 'LIKE', "%$search%")
->orWhere('total_amount', 'LIKE', "%$search%")
->orWhere('begin_balance', 'LIKE', "%$search%")
->orWhere('end_balance', 'LIKE', "%$search%");
});
}
$statements = $statementsQuery->orderBy('date_from')->paginate(10);
return view('pages.accounting.bank-statements.index', compact('accounts', 'selectedAccount', 'search', 'statements'));
}
public function indexv2(Request $request)
{
$selectedAccount = $request->input('account');
$search = $request->input('search');
$accounts = StatementAccount::all();
$statementsQuery = AccountStatement::query();
if ($selectedAccount) {
$statementsQuery->where('statement_account_id', $selectedAccount);
}
if ($search) {
$statementsQuery->where(function ($query) use ($search) {
$query->where('date_from', 'LIKE', "%$search%")
->orWhere('date_to', 'LIKE', "%$search%")
->orWhere('total_amount', 'LIKE', "%$search%")
->orWhere('begin_balance', 'LIKE', "%$search%")
->orWhere('end_balance', 'LIKE', "%$search%");
});
}
$statements = $statementsQuery->paginate(10);
return view('pages.accounting.bank-statements.indexv2', compact('accounts', 'selectedAccount', 'search', 'statements'));
}
public function import(Request $request, ImportBankStatementLogic $logic): JsonResponse
{
return $logic->execute($request);
}
public function rerun()
{
CreateBankStatementTransactionOwners::dispatch();
return redirect()->back()->with('success', 'Rerun triggered successfully');
}
public function show(AccountStatement $statement, Request $request)
{
$transactions = $statement->transactions();
// $account = $statement->account();
// dd(json_encode($account->where('id', '>=', 1)->first()));
// dd(json_encode($transactions->where('id', '>=', 1)->first()));
if ($request->get('transaction_filter')) {
$transactionFilter = $request->get('transaction_filter');
$transactions = $transactions->where('transaction_description', 'LIKE', "%$transactionFilter%");
}
if ($request->get('from_amount_filter')) {
$fromAmountFilter = $request->get('from_amount_filter');
$transactions = $transactions->where('amount', '>=', $fromAmountFilter);
}
if ($request->get('to_amount_filter')) {
$toAmountFilter = $request->get('to_amount_filter');
$transactions = $transactions->where('amount', '<=', $toAmountFilter);
}
// $transactions = $transactions->paginate(100);
$transactions = $transactions->get();
echo $this->process3_merged($transactions);
//return view('pages.accounting.bank-statements.show', compact('statement', 'transactions'));
}
public function download(AccountStatement $statement)
{
$transactions = $statement->transactions;
$csvExporter = new \Laracsv\Export();
$csvExporter->build($transactions, ['transaction_date', 'transaction_time', 'posting_date', 'transaction_description', 'transaction_ref', 'debit', 'credit'])
->download($statement->date_from->format('Y-m-d') . '_' . $statement->date_to->format('Y-m-d') . '_statement.csv');
}
public function fetch(Request $request, ListBankStatementDetailsLogic $logic): JsonResponse
{
return $logic->execute($request);
}
public function transactions(Request $request, ListBankStatementTransactionsLogic $logic): JsonResponse
{
return $logic->execute($request);
}
public function update(Request $request, UpdateBankStatementDetailLogic $logic): JsonResponse
{
return $logic->execute($request);
}
private function process3_merged($transactions){
// $statement = $transactions[0]->statement();
// dd(json_encode($statement->first()));
$headers = [
'Date',
'Bank',
'Description',
'Credit',
'Debit',
'Pay For',
'System',
'System Reference',
'Human Reference',
'Multiple',
'Match?',
'System Amount'
];
$branches = [
0 => 'MBB Cyber',
1 => 'MBB SS2',
];
$yes = 'Yes';
$no = 'No';
$table = '<table><tr><th>'.implode('</th><th>', $headers).'</th></tr>';
$count = 0;
foreach ($transactions as $row) {
$isExist = StatementTransactionOwner::where('statement_transaction_id', $row->id)->first();
if ($isExist) {
continue;
}
$count++;
$credit = 0.00;
$debit = 0.00;
// dd(json_encode($row['posting_date']));
$date = new DateTime($row['posting_date']);
$description = $row['transaction_description_2'];
if($row['amount'] < 0){
$debit = (float) $row['amount'];
}
else{
$credit = (float) $row['amount'];
}
$creditTransactions = [];
$debitTransactions = [];
$system = '';
$systemReference = null;
$systemAmount = null;
if($credit){
$creditTransactions = $this->getTransactions($date, $credit, TransactionType::PAYMENT, Booking::class, PaymentMethodType::WALLET, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
foreach ($creditTransactions as $transaction) {
$systemReference[] = $transaction->owner instanceof Booking ? $transaction->owner->marking : $transaction->bill_no;
$systemAmount[] = $transaction->amount;
$system[] = 'EXCHANGE';
}
$creditTransactions = $this->getTransactions($date, $credit, TransactionType::TOP_UP, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
foreach ($creditTransactions as $transaction) {
$systemReference[] = $transaction->owner instanceof Booking ? $transaction->owner->marking : $transaction->bill_no;
$systemAmount[] = $transaction->amount;
$system[] = 'EXCHANGE';
}
$creditTransactions = $this->getTransactionsFromShippingPortal($credit, $this->getDateRange($row['posting_date']));
foreach ($creditTransactions as $transaction) {
$systemReference[] = $transaction['order']['reference'];
$systemAmount[] = $transaction['amount'];
$system[] = 'SHIPPING';
}
}
if($debit){
$debitTransactions = $this->getTransactions($date, $debit, null, null, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED], Group::class);
if(!count($debitTransactions)) {
foreach (['YSN', 'HCK', 'ATVANTIC', 'HIGH HILL'] as $reference){
if(str_contains($description, $reference)) {
$paymentDate = date('Y-m-d', strtotime('+1 day', strtotime($row['posting_date']))); //$date->addDays(1)->format('Y-m-d');
if($reference = 'ATVANTIC'){
$paymentDate = date('Y-m-d', strtotime($row['posting_date']));//$date->format('Y-m-d');
}
$issuer = Company::where('name', 'like', '%'.$reference.'%')->get()->pluck('id');
$debitTransactions = Group::whereIn('issuer', $issuer)->whereDate('created_at', $paymentDate)->get();
break;
}
}
}
foreach ($debitTransactions as $transaction) {
$systemReference[] = $transaction->reference;
$systemAmount[] = $transaction->amount;
$system[] = 'EXCHANGE';
}
}
$multiple = count($creditTransactions) + count($debitTransactions) > 1 ? $yes : $no;
$systemReference = $systemReference ? implode(',', $systemReference) : null;
$systemAmount = $systemAmount ? implode(',', $systemAmount) : null;
$matches = $systemReference == $row['remarkreferences'] ? $yes : $no;
$table .= '<tr>
<td>'.$date->format('d-m-Y').'</td>
<td>branch</td>
<td>'.$description.'</td>
<td>'.$credit.'</td>
<td>'.$debit.'</td>
<td>'.$row['pay_for'].'</td>
<td>'.$system.'</td>
<td>'.$systemReference.'</td>
<td>'.$row['remarkreferences'].'</td>
<td>'.$multiple.'</td>
<td>'.$matches.'</td>
<td>'.$systemAmount.'</td>
</tr>';
//AccountStatement
// $row->statement()->first()->id)
$statementTransactionsDetail = new StatementTransactionOwner([
'date' => $date,
'statement_transaction_id' => $row->id,
'description' => is_null($description) ? "" : $description,
'credit' => $credit,
'debit' => $debit,
'pay_for' => $system,
'system_references' => is_null($systemReference) ? "" : $systemReference,
'remark_references' => is_null($row['remarkreferences']) ? "" : $row['remarkreferences'],
'is_multiple' => $multiple == "Yes" ? 1 : 0,
'is_matches' => $matches == "Yes" ? 1 : 0,
'system_amounts'=> is_null($systemAmount) ? "" : $systemAmount,
]);
$statementTransactionsDetail->save();
if($count == 10){
break;
}
}
$table .= '</table>';
return $table;
}
private function getTransactions($date, $amount, $type, $ownerType, $paymentMethod, $statuses, $model = Transaction::class) {
$query = $model::whereIn('status', $statuses)
->where(function ($query) use ($ownerType, $paymentMethod, $type) {
if ($ownerType) {
$query->where('owner_type', $ownerType);
}
if ($paymentMethod) {
$query->where('payment_method', '!=', $paymentMethod);
}
if ($type) {
$query->where('type', $type);
}
})
->whereDate('created_at', $date->format('Y-m-d'))
->where('amount', '>', ($amount - 0.01))
->where('amount', '<', ($amount + 0.01));
return $query->get();
}
private function getDateRange(string $dateStr) {
// Create a DateTime object from the input string
$date = strtotime($dateStr);
// Get the first day of the month
$today = date('Y-m-d', $date);
// Get the first day of the next month
$nextDay = date('Y-m-d', strtotime('+1 day', $date));
return [
'start_date' => $today,
'end_date' => $nextDay,
];
}
private function getTransactionsFromShippingPortal($amount, $dateRange){
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://izyim.cief-malaysia.com/public/api/v1/list?api-key=510acd13d8d24375cf038ad626c282565451461a9c2399357e0b65365300787e&filters={"order_by":{"column":"id","DESC":true},"status_in":[2],"type":2,"created_after":"'.$dateRange['start_date'].'","created_before":"'.$dateRange['end_date'].'","amount_exceed":'.($amount - 0.01).',"amount_short":'.($amount + 0.01).'}');
$body = $response->getBody();
$data = json_decode($body, true);
$payload = $data['payload'];
$transactions2 = $payload['data'];
// $filters = [
// ['field' => 'created_at', 'value' => '2023-03-01 08:07:00'],
// ];
// $transactions2 = $this->getTransactions3($transactions2, $filters);
return $transactions2;
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Accounting;
use App\Classes\Modules\Accounting\ControllersLogic\GroupApproveStatementTransactionLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class GroupApproveStatementTransactionController
{
/**
* @param Request $request
* @param ApprovePaymentLogic $logic
* @return JsonResponse
*/
public function approve(Request $request, GroupApproveStatementTransactionLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Accounting;
use App\Classes\Modules\Accounting\ControllersLogic\UpdateStatementTransactionStatusLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UpdateStatementTransactionStatusController
{
/**
* @param Request $request
* @param ApprovePaymentLogic $logic
* @return JsonResponse
*/
public function update(Request $request, UpdateStatementTransactionStatusLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers\Imports;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Imports\Services\GenericImport;
use App\Classes\Modules\Segments\DataTransferObjects\SeasonalSegmentObject;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Models\Segment;
use App\Models\User;
use Carbon\Carbon;
use DateTime;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use App\Classes\Modules\Segments\Services\CreatesSeasonalSegment;
use App\Classes\Modules\Companies\Processors\AssignSegmentProcessor;
use App\Models\Company;
use App\Models\SeasonalSegment;
use App\Models\Transaction;
class ImportStatementInvoiceController
{
/**
* @param Request $request
* @return array
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function import(Request $request)
{
ini_set('memory_limit', '-1');
$object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
$file = json_decode($object->getFiles()[0])->file_info->original->file;
$import = new GenericImport();
Excel::import($import, $file);
$excelRows = $import->rows;
$excelRows = $excelRows->toArray();
foreach ($excelRows as $row) {
dd($row);
// $row['debtor_code']
// attempt 1 - try map by amount and date
// $transactionDate = $this->changeExcelDate($row['date']);
// $transaction = Transaction::where('original_amount', $row['total'])->whereDate('created_at', $transactionDate)->get();
// if ($transaction) {
// // check company
// // $company = Company::where('debtor', $row['debtor_code'])->first();
// // dd($company);
// // try to verify is it the correct transaction
// }
// Shipping Info
// TOPUP -> map with transaction.bill_no
if (str_starts_with($row['shipping_info'], 'TOPUP')) {
// find in exchange first, if cannont then find in izyim
// (App()->make(ChecksBillNumber::class))->execute($bill_no, 'exchange');
}
// if 5 digits -> exchange booking reference
// find transation
// find statement_transaction_owners, and fill up the details
// if <5 digits, find the transaction id (order number in izyim), find the payment in izyim
// find transation
// find statement_transaction_owners, and fill up the details
// dd([
// 'type' => $statementTransactionOwnerType,
// 'system' => $system,
// // 'owner_type' => Transaction::class,
// // todo-new: make sure owner_type is a class
// 'owner_type' => $owner_type,
// 'owner_id' => $owner_id,
// 'owner_reference' => $owner_reference
// ]);
// $bankStatementTransaction->owners()->firstOrCreate([
// 'type' => $statementTransactionOwnerType,
// 'system' => $system,
// // 'owner_type' => Transaction::class,
// // todo-new: make sure owner_type is a class
// 'owner_type' => $owner_type,
// 'owner_id' => $owner_id,
// 'owner_reference' => $owner_reference
// ]);
}
}
public function changeExcelDate($date)
{
$unixTime = (($date - 25569) * 86400);
$date = new DateTime("@$unixTime");
return $date->format('Y-m-d'); // Change the format to 'Y-m-d'
}
}
@@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Imports;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Imports\Services\GenericImport;
use App\Classes\Modules\Segments\DataTransferObjects\SeasonalSegmentObject;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Models\Segment;
use App\Models\User;
use Carbon\Carbon;
use DateTime;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use App\Classes\Modules\Segments\Services\CreatesSeasonalSegment;
use App\Classes\Modules\Companies\Processors\AssignSegmentProcessor;
use App\Models\Company;
use App\Models\SeasonalSegment;
use App\Models\Transaction;
class ImportStatementReceiptsController
{
/**
* @param Request $request
* @return array
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function import(Request $request)
{
$object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
$file = json_decode($object->getFiles()[0])->file_info->original->file;
$import = new GenericImport();
Excel::import($import, $file);
$excelRows = $import->rows;
$excelRows = $excelRows->toArray();
foreach ($excelRows as $row) {
// if has date column
// $transactionDate = $this->changeExcelDate($row['date']);
}
}
public function changeExcelDate($date)
{
$unixTime = (($date - 25569) * 86400);
$date = new DateTime("@$unixTime");
return $date->format('Y-m-d'); // Change the format to 'Y-m-d'
}
}
@@ -0,0 +1,60 @@
<?php
namespace App\Http\Resources;
use App\Classes\ValueObjects\Constants\StatementTransactionOwnerType;
use App\Models\Booking;
use App\Models\Group;
use App\Models\Transaction;
use App\Models\Wallet;
use Illuminate\Http\Resources\Json\JsonResource;
class BankStatementTransactionOwnerResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
$referenceLink = null;
if($this->system === 'EXCHANGE') {
if($this->owner_type === Transaction::class){
if($this->type === StatementTransactionOwnerType::SALES){
$referenceLink = route('booking.details', $this->owner_reference);
}
if($this->type === StatementTransactionOwnerType::WALLET_TOP_UP){
$referenceLink = route('booking.details', $this->owner_reference);
}
}
}
if($this->system === 'SHIPPING_PORTAL') {
if($this->owner_type === Transaction::class){
if($this->type === StatementTransactionOwnerType::SALES){
$referenceLink = 'https://izyim.cief-malaysia.com/order/show/'. $this->owner_reference;
}
if($this->type === StatementTransactionOwnerType::WALLET_TOP_UP){
$referenceLink = 'https://izyim.cief-malaysia.com/wallet/'. $this->owner_reference .'/details';
}
}
}
return [
'id' => $this->id,
'type' => $this->type,
'system' => $this->system,
'owner_type' => $this->owner_type,
'owner_id' => $this->owner_id,
'reference' => $this->owner_reference,
'reference_link' => $referenceLink,
'invoice_reference' => $this->invoice_reference,
'receipt_reference' => $this->receipt_reference,
'status' => $this->status
];
}
}
@@ -0,0 +1,41 @@
<?php
namespace App\Http\Resources;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use Illuminate\Http\Resources\Json\JsonResource;
class BankStatementTransactionResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'account_number' => $this->statement->account->number,
'account_type' => $this->statement->account->type,
'account_name' => $this->statement->account->name,
'account_statement_id' => $this->statement->id,
'account_statement_date_from' => $this->statement->date_from,
'account_statement_date_to' => $this->statement->date_to,
'posting_date' => $this->posting_date->format('d-m-Y g:i A'),
'amount' => $this->amount,
'transaction_description_1' => $this->transaction_description,
'transaction_description_2' => $this->transaction_description_2,
'transaction_description_3' => $this->transaction_description_3,
'transaction_description_4' => $this->transaction_description_4,
'transaction_description_5' => $this->transaction_description_5,
'owners' => [
'approved' => BankStatementTransactionOwnerResource::collection($this->owners()->whereIn('status', [ApprovalStatus::APPROVED])->get()),
'pending_verification' => BankStatementTransactionOwnerResource::collection($this->owners()->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION])->get()),
'rejected' => BankStatementTransactionOwnerResource::collection($this->owners()->whereIn('status', [ApprovalStatus::REJECTED])->get())
]
];
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class AccountStatement extends Model
{
use HasFactory;
protected $fillable = [
'statement_account_id',
'date_from',
'date_to',
'total_amount',
'begin_balance',
'end_balance',
];
protected $casts = [
'date_from' => 'date',
'date_to' => 'date',
];
public function account()
{
return $this->belongsTo(StatementAccount::class, 'statement_account_id', 'id');
}
public function transactions()
{
return $this->hasMany(StatementTransaction::class);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class StatementAccount extends Model
{
use HasFactory;
protected $fillable = [
'number',
'type',
'name',
'currency',
];
public function statements()
{
return $this->hasMany(AccountStatement::class);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
class StatementTransaction extends Model
{
use HasRelationships;
use HasFactory;
protected $fillable = [
'account_statement_id',
'transaction_date',
'posting_date',
'transaction_description',
'transaction_description_2',
'transaction_description_3',
'transaction_description_4',
'transaction_description_5',
'transaction_ref',
'amount',
'teller_id',
'branch_channel',
'transaction_code',
'end_balance',
];
protected $casts = [
'posting_date' => 'datetime',
];
public function account()
{
return $this->hasOneDeep(StatementAccount::class, [AccountStatement::class], ['id', 'id'], ['account_statement_id', 'statement_account_id']);
}
public function statement()
{
return $this->belongsTo(AccountStatement::class, 'account_statement_id', 'id');
}
public function owners()
{
return $this->hasMany(StatementTransactionOwner::class);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class StatementTransactionOwner extends Model
{
use HasFactory;
protected $fillable = [
'statement_transaction_id',
'type',
'system',
'owner_type',
'owner_id',
'owner_reference',
'invoice_reference',
'receipt_reference',
'status',
];
public function transaction(): BelongsTo
{
return $this->belongsTo(StatementTransaction::class, 'statement_transaction_id', 'id');
}
}
+4
View File
@@ -25,6 +25,10 @@ class Transaction extends AbstractModel implements Documentable, Transactionable
use SoftDeletes;
use LogData;
protected $casts = [
'type' => 'int'
];
protected $table = 'transactions';
public function owner(): morphTo
+1 -1
View File
@@ -1,7 +1,7 @@
<?php
return [
'base_url' => env('PERFEXCRM_BASE_URL', 'http://192.168.1.100:8084'), //cief todo: Update crm api domain here
'base_url' => env('PERFEXCRM_BASE_URL', 'http://192.168.1.101:8084'), //cief todo: Update crm api domain here
'api_key' => env('PERFEXCRM_API_KEY', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZXhjaGFuZ2Utc2hpcHBpbmciLCJuYW1lIjoiRXhjaGFuZ2UgYW5kIFNoaXBwaW5nIFBvcnRhbCIsIkFQSV9USU1FIjoxNjc1MDg2Mzc4fQ.SGAHWl5stcxQwp55TBGeMRVTdlLeWQIbsvJh5glyVvs'),
'is_enabled' => env('PERFEXCRM_IS_ENABLED', 'true'),
];
@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStatementAccountsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('statement_accounts', function (Blueprint $table) {
$table->id();
$table->string('number')->unique();
$table->string('type');
$table->string('name');
$table->string('currency');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('statement_accounts');
}
}
@@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAccountStatementsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('account_statements', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('statement_account_id');
$table->date('date_from');
$table->date('date_to');
$table->float('total_amount');
$table->float('begin_balance');
$table->float('end_balance');
$table->timestamps();
$table->foreign('statement_account_id')->references('id')->on('statement_accounts');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('account_statements');
}
}
@@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStatementTransactionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('statement_transactions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('account_statement_id');
$table->dateTime('transaction_date')->nullable();
$table->dateTime('posting_date');
$table->string('transaction_description')->nullable();
$table->string('transaction_description_2')->nullable();
$table->string('transaction_description_3')->nullable();
$table->string('transaction_description_4')->nullable();
$table->string('transaction_description_5')->nullable();
$table->string('transaction_ref')->nullable();
$table->float('amount', 15, 2)->unsigned(false);
$table->string('teller_id')->nullable();
$table->string('branch_channel');
$table->string('transaction_code');
$table->string('end_balance')->nullable();
$table->timestamps();
$table->foreign('account_statement_id')->references('id')->on('account_statements');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('statement_transactions');
}
}
@@ -0,0 +1,45 @@
<?php
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\StatementTransactionOwnerType;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStatementTransactionOwnersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('statement_transaction_owners', function (Blueprint $table) {
$table->id();
$table->foreignId('statement_transaction_id');
$table->integer('type')->default(StatementTransactionOwnerType::UNKNOWN);
$table->string('system')->nullable();
$table->string('owner_type')->nullable();
$table->bigInteger('owner_id')->nullable();
$table->string('owner_reference')->nullable();
$table->string('invoice_reference')->nullable();
$table->string('receipt_reference')->nullable();
$table->string('is_auto_mapped')->default(false);
$table->integer('status')->default(ApprovalStatus::PENDING_VERIFICATION);
$table->foreign('statement_transaction_id')->references('id')->on('statement_transactions');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('statement_transaction_owners');
}
}
@@ -0,0 +1,141 @@
<template>
<div class="row ">
<div class="col bg-white padding-15">
<div class="row">
<div class="col-12">
<div class="form-group">
<label>Date: {{ item.posting_date }}</label>
</div>
<div class="form-group">
<label>Transaction Description 1: {{ item.transaction_description_1 }}</label>
</div>
<div class="form-group">
<label>Transaction Description 2: {{ item.transaction_description_2 }}</label>
</div>
<div class="form-group">
<label>Transaction Description 3: {{ item.transaction_description_3 }}</label>
</div>
<div class="form-group">
<label>Transaction Description 4: {{ item.transaction_description_4 }}</label>
</div>
<div class="form-group">
<label>Transaction Description 5: {{ item.transaction_description_5 }}</label>
</div>
</div>
</div>
<div class="row m-b-15">
<div class="col">
<div class="row">
<div class="col">
<validation-wrapper-component selectable :validator="$v.pay_for_value">
<label>Transaction Type</label>
<select-component :options="['Sales','Top Up', 'Internal Bank Transfer', 'Others']" v-model="pay_for_value"></select-component>
</validation-wrapper-component>
</div>
</div>
<div class="row m-t-15" v-if="['Sales', 'Top Up'].includes(pay_for_value)">
<div class="col">
<validation-wrapper-component selectable :key="custom_options_key" :validator="$v.system_references">
<label>Service</label>
<select-component :options="customOptions()" v-model="system_references"></select-component>
</validation-wrapper-component>
</div>
</div>
<div class="row m-t-15" v-if="['Sales', 'Top Up', 'Others'].includes(pay_for_value)">
<div class="col">
<validation-wrapper-component :validator="$v.transaction_reference">
<label>Transaction ID / Reference</label>
<input type="text" class="form-control" v-model.lazy="transaction_reference">
</validation-wrapper-component>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="row">
<div class="col p-r-5">
<div data-dismiss="modal" class="btn btn-sm btn-default bg-master-lighter btn-block b-rad-none">Cancel</div>
</div>
<div class="col p-l-5">
<div data-dismiss="modal" class="btn btn-sm btn-success btn-block b-rad-none" @click="submitForm()">Update</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import componentHandler from '../../../general/mixins/componentHandler';
// import ModalFormHandler from '../../../general/mixins/modalFormHandler';
// import FormHandler from '../../../general/mixins/formHandler';
import { required } from "vuelidate/lib/validators";
export default {
data(){
return {
error: '',
system_references: "",
transaction_reference: "",
pay_for_value: "",
custom_options_key: 1,
pay_for: {
status: false,
options: ['sales', 'top_up', 'internal_bank_transfer', 'others'],
},
}
},
created() {
this.pay_for = this.item.pay_for,
this.system_references = this.item.system_references
},
validations: {
system_references: { },
transaction_reference: { },
pay_for_value: { required },
},
watch: {
'data': function() {
this.pay_for = this.item.pay_for;
this.system_references = this.item.system_references;
},
pay_for_value(newVal, oldVal) {
this.custom_options_key ++;
this.transaction_reference = '';
}
},
methods: {
submitForm(){
this.pay_for_value = this.pay_for_value.replace(/\s/g, "_").toLowerCase();
this.system_references = this.system_references ? this.system_references.toLowerCase() : '';
this.parameters = {
pay_for : this.pay_for_value,
system_references : this.system_references,
transaction_reference : this.transaction_reference
};
this.submit(this.route('api.accounting.statement.details.update', this.data.id), 'put', this.section, true, true);
},
successHandler(){
// this.closeModal();
// this.formHandler('');
},
customOptions(){
switch (this.pay_for_value) {
case 'Sales':
return ['Exchange', 'Izyim', 'Lite', 'Cntr', 'Probashi', 'Pets'];
case 'Top Up':
return ['Exchange', 'Izyim'];
default:
return [];
}
},
},
mixins: [componentHandler]
}
</script>
@@ -0,0 +1,161 @@
<template>
<div class="row m-b-10 parentContainer">
<div class="col">
<div class="row p-b-10 b-b b-grey">
<div class="col-2">{{ item.posting_date }}</div>
<div class="col-2">{{ item.transaction_description_1 + ' - ' + item.transaction_description_2 }}</div>
<div class="col-5" v-if="item.owners.pending_verification.length === 1">
<div class="row">
<div class="col">{{item.owners.pending_verification[0].system}}</div>
<div class="col">{{ typeString(item.owners.pending_verification[0].type) }}</div>
<div class="col"><a :href="item.owners.pending_verification[0].reference_link" target="_blank">{{item.owners.pending_verification[0].reference}}</a></div>
</div>
</div>
<div class="col-5" v-if="item.owners.pending_verification.length > 1">
<div class="row">
<div class="col">
<div class="row" v-for="owner in item.owners.pending_verification">
<div class="col">
{{ owner.system }}
</div>
</div>
</div>
<div class="col">
<div class="row" v-for="owner in item.owners.pending_verification">
<div class="col">
{{ typeString(owner.type) }}
</div>
</div>
</div>
<div class="col">
<div class="row parentContainer" v-for="owner in item.owners.pending_verification">
<div class="col d-flex justify-content-between">
<a :href="owner.reference_link" target="_blank">{{ owner.reference }}</a>
<div v-if="stage === 2">
<button class="btn btn-xs btn-outline-primary b-rad-none m-r-5 requestModal" data-type="approveCorrectMappingTransaction">
<i class="fa fa-check fa-fw"></i>
</button>
<modal-component class="animate__animated animate__fast animate__fadeIn" type="approveCorrectMappingTransaction">
<general-confirmation-form-component
:contentText="returnTextWithVariable(owner.reference)"
modalType="confirm"
class="text-center"
:apiRoute="route('api.accounting.bankStatement.details.status.update', owner.id, 'approve')"
apiMethod="post"
:section="section"
>
</general-confirmation-form-component>
</modal-component>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-5" v-if="!item.owners.pending_verification.length">
<div class="row">
<div class="col">
<button class="btn btn-xs btn-outline-success b-rad-none m-r-5 requestModal" data-type="updateOwner">
<i class="fa fa-plus"></i>
</button>
</div>
<div class="col">
<button class="btn btn-xs btn-outline-success b-rad-none m-r-5 requestModal" data-type="updateOwner">
<i class="fa fa-plus"></i>
</button>
</div>
<div class="col">
<button class="btn btn-xs btn-outline-success b-rad-none m-r-5 requestModal" data-type="updateOwner">
<i class="fa fa-plus"></i>
</button>
</div>
</div>
<div class="row">
<div class="col">
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="updateOwner">
<edit-single-item-in-list-component :data="item" :section="section"></edit-single-item-in-list-component>
</modal-component>
</div>
</div>
</div>
<div class="col-1">{{ item.amount }}</div>
<div class="col-1 text-success" v-if="item.owners.approved.length">{{ [6, 7, 8, 9, 10, 11, 12, 13, 14].include(item.owners.approved[0].type) ? 'Miscellaneous' : 'Approved' }}</div>
<div class="col-1 text-danger" v-if="!item.owners.approved.length">Pending...</div>
<div class="col-1" v-if="stage === 1">
<button class="btn btn-xs btn-outline-danger b-rad-none m-r-5 requestModal" data-type="deleteMappingTransaction">
<i class="fa fa-times fa-fw"></i>
</button>
<modal-component class="animate__animated animate__fast animate__fadeIn" type="deleteMappingTransaction">
<general-confirmation-form-component
contentText="Are you sure you want to reject this mapping?"
modalType="delete"
class="text-center"
:apiRoute="route('api.accounting.statement_transaction.owner.status.update', item.id, 'reject')"
apiMethod="post"
:section="section"
>
</general-confirmation-form-component>
</modal-component>
</div>
</div>
<!-- <span v-for="owner in item.owners">{{owner.system + ' - ' + owner.owner_id}}</span>-->
<!-- [0].system + ' - ' + item.owner_guess[0].bill_no-->
</div>
</div>
</template>
<script>
import componentHandler from '../../../general/mixins/componentHandler';
export default {
props: {
stage: {
type: Number,
default: 0
},
section:{
type: String,
required: true
}
},
methods: {
typeString(type){
switch(type) {
case 1:
return 'Sales';
case 2:
return 'Wallet Top Up';
case 3:
return 'Wallet Withdrawal';
case 4:
return 'Payment Refund';
case 5:
return 'Supplier Payment';
case 6:
return 'Internal Bank Transfer Out';
case 7:
return 'Internal Bank Transfer In';
case 8:
return 'Salary Payment';
case 9:
return 'Statutory Payment';
case 10:
return 'FPX Charges';
case 11:
return 'FPX Charges Refund';
case 12:
return 'Bank Charges';
case 13:
return 'Credit Card Payment';
case 14:
return 'Non-Operational Payment';
}
},
returnTextWithVariable(variable) {
return "Are you sure you want to choose this mapping " + variable + "?";
}
},
mixins: [componentHandler]
}
</script>
@@ -0,0 +1,59 @@
<template>
<div class="row">
<div class="col">
<div class="row" @keyup.enter="submitForm">
<div class="col">
<loading-component style="height: 200px; top: 0;" key="1" color="success" v-show="$store.getters.isLoading(section)"></loading-component>
<div class="row" v-show="!$store.getters.isLoading(section)">
<div class="col">
<div class="row">
<div class="col">
<file-input-component :validator="$v.parameters.files" v-model="parameters.files">
<template slot="label">
<div class="font-heading fs-11 text-primary all-caps">Import Bank Statement CSV</div>
</template>
</file-input-component>
</div>
</div>
<div class="row m-t-20">
<div class="col">
<div class="row">
<div class="col">
<button type="button" class="btn btn-sm btn-block p-t-10 p-b-10 p-r-35 p-l-35 btn-success b-rad-none" @click="submitForm">Upload</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { required } from "vuelidate/lib/validators";
import formHandler from '../../../general/mixins/formHandler';
export default {
data(){
return {
parameters: {
files: []
}
}
},
validations: {
parameters: {
files: {
required
}
}
},
methods: {
submitForm(){
this.submit(this.route('api.accounting.statement.import'), 'post', this.section, true, false)
}
},
mixins: [formHandler]
}
</script>
@@ -0,0 +1,232 @@
<template>
<div class="row h-100 parentContainer">
<div class="col-12" style="min-height: 20px;">
<loading-component style="height: 20px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
</div>
<div class="col-12">
<div id="accordion">
<div class="card">
<div class="card-header" id="headingOne">
<h5 class="mb-0">
<button class="btn btn-link" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Filters
</button>
</h5>
</div>
<div id="collapseOne" class="collapse" aria-labelledby="headingOne" data-parent="#accordion">
<div class="card-group" style="margin-bottom: 0px;">
<div class="card">
<div class="card-header">
<h3 class="card-title">Pay For</h3>
<div class="card-tools">
<div class="form-check" v-for="(match, index) in matches">
<input class="form-check-input" type="checkbox" :value="match" :id="'match'+index" v-model="selected.matches">
<label class="form-check-label" :for="'match' + index">
{{ match }}
</label>
</div>
</div>
</div>
</div>
<div class="card" style="margin-top: 0px">
<div class="card-header">
<h3 class="card-title">Date</h3>
<div class="form-check" v-for="(day, index) in days" :key="day">
<input class="form-check-input" type="checkbox" :id="'day'+index" :value="day" v-model="selected.days">
<label class="form-check-label" :for="'day'+index">
{{ day }}
</label>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-end mb-2">
<button type="button" class="btn btn-sm btn-primary" @click="fetchList(true)">
<i class="fa fa-plus-square"></i>
Filter
</button>
</div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 v-if="$store.getters.getListData(section)[0] !== undefined" class="card-title">{{ $store.getters.getListData(section)[0].account_name }} - {{ $store.getters.getListData(section)[0].account_number }},{{ $store.getters.getListData(section)[0].account_type }} </h3><br/>
<h3 v-if="$store.getters.getListData(section)[0] !== undefined" class="card-title">{{ new Date($store.getters.getListData(section)[0].account_statement_date_from).toDateString() }} -> {{ new Date($store.getters.getListData(section)[0].account_statement_date_to).toDateString() }}</h3>
</div>
<!-- /.card-header -->
<div class="card-body table-responsive p-0">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Date</th>
<th>Description 1</th>
<th>Pay For</th>
<th>System References</th>
<th>Amount</th>
<th>Action</th>
</tr>
</thead>
<tbody v-show="!isLoading">
<tr v-for="item in $store.getters.getListData(section)" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.date}}</td>
<td>{{item.transaction_description_1 | truncate(30, '...')}}</td>
<td>{{item.pay_for}}</td>
<td>{{item.system_references}}</td>
<td>{{item.amount}}</td>
<td>
<a href="#">
<i class="fa fa-edit blue requestModal" data-type="editSingleItem" @click="editModal({item})"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
<!-- /.card-body -->
<div class="card-footer">
</div>
</div>
<!-- /.card -->
</div>
<div class="col-12">
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="editSingleItem">
<edit-single-item-in-list-component v-if="data != null" :data="data" :section="'editSingleItem'" class="text-left"></edit-single-item-in-list-component>
</modal-component>
</div>
<div class="col-12">
<pagination-component :section="section" class="mb-5" ref="pagination"></pagination-component>
</div>
</div>
</template>
<script>
import EditSingleItemInListComponent from "../../accounting/elements/EditSingleItemInListComponent";
export default {
components: {EditSingleItemInListComponent},
props: {
statement: {
type: Number,
required: true
}
},
data(){
return {
data: null,
section: 'statementTransactionsDetails',
isLoading: false,
matches: ['exchange', 'shipping', 'none'],
selected: {
matches: [],
days: []
},
filters: {'per_page': 10, order_by: {column: 'id', DESC: true}},
page: 1,
days: [],
}
},
computed: {
pendingQueue () {
return this.$store.getters.isInCompleteQueue(this.section);
},
},
watch: {
pendingQueue(inComplete, oldValue){
if(inComplete){
this.fetchList();
}
},
selected: {
handler: function () {
//this.filters = {'per_page': 10, order_by: {column: 'id', DESC: true}};
//pay_for
if(this.selected.matches.toString() != ""){
const newArr = this.selected.matches.slice();
const index = newArr.indexOf('none');
if(index > -1){
newArr.splice(index, 1, '');
}
const newObject = {pay_for_in: newArr};
this.filters = {...this.filters, ...newObject};
}
else{
delete this.filters.pay_for_in;
}
//date
if(this.selected.days.toString() != ""){
const newArr = this.selected.days.slice();
const newObject = {date_in: newArr};
this.filters = {...this.filters, ...newObject};
}
else{
delete this.filters.date_in;
}
},
deep: true
}
},
created(){
this.setDecoratorDefault();
if (this.statement === 0) {
this.filters = { 'per_page': 10, order_by: {column: 'id', DESC: true} };
} else {
this.filters = { 'per_page': 10, order_by: {column: 'id', DESC: true}, 'has_account_statement_id': this.statement };
}
this.$store.dispatch('updateListQueue', {'name': this.section, 'page': 1, 'filters': this.filters});
},
methods: {
fetchList(isUpdate = false){
this.isLoading = true;
if(!isUpdate){
let listDecorators = this.$store.getters.getListDetails(this.section);
this.page = listDecorators.page;
}
this.submit(route('api.accounting.statement.details', this.statement) + '?page=' + this.page + '&filters=' + JSON.stringify(this.filters), 'get', this.section, false, false);
},
successHandler(response){
this.$store.dispatch('completeList', {'name': this.section, 'data': response.payload.data});
this.$refs.pagination.makePagination(response.payload.meta, response.payload.links + '&filters=' + JSON.stringify(this.filters));
this.generateDays();
this.isLoading = false;
},
editModal(param){
this.data = param.item;
},
generateDays(){
if(this.$store.getters.getListData(this.section)[0] !== undefined)
{
const startDate = new Date(this.$store.getters.getListData(this.section)[0].account_statement_date_from);
const endDate = new Date(this.$store.getters.getListData(this.section)[0].account_statement_date_to);
const days = [];
for (let d = startDate; d <= endDate; d.setDate(d.getDate() + 1)) {
const formattedDate = this.formatDate(d);
days.push(formattedDate);
}
this.days = days;
}
},
formatDate(date) {
const year = date.getFullYear();
const month = ('0' + (date.getMonth() + 1)).slice(-2);
const day = ('0' + date.getDate()).slice(-2);
return `${year}-${month}-${day}`;
},
},
}
</script>
@@ -0,0 +1,268 @@
<template>
<div class="row">
<div class="col">
<div class="row">
<div class="col">
<div class="row justify-content-center align-items-center m-t-50 m-b-50" v-show="step === 0">
<div class="col-5">
<div class="row text-center">
<div class="col b-a b-grey padding-50 m-r-15 pointer" :class="[{'bg-complete': type === 1}, {'text-white': type === 1}]" @click="type=1">Receivable Mapping</div>
<div class="col b-a b-grey padding-50 pointer" :class="[{'bg-complete': type === 2}, {'text-white': type === 2}]" @click="type=2">Payable Mapping</div>
</div>
</div>
</div>
<div class="row justify-content-center align-items-center m-t-50 m-b-50" v-if="type">
<div class="col-8">
<div class="row text-center m-b-50" v-if="step">
<div class="col">
<h4 class="all-caps">{{ type === 1 ? 'Receivable Mapping' : 'Payable Mapping'}}</h4>
</div>
</div>
<div class="row text-center align-items-center">
<div class="col b-a b-grey padding-30 pointer" :class="[{'bg-info': stage === 1}, {'text-white': stage === 1}]" @click="startMapping(1)">Mapping Approval</div>
<div class="col-auto"><i class="fa fa-angle-double-right fs-20"></i></div>
<div class="col b-a b-grey padding-30 pointer" :class="[{'bg-info': stage === 2}, {'text-white': stage === 2}]" @click="startMapping(2)">Mapping Review</div>
<div class="col-auto"><i class="fa fa-angle-double-right fs-20"></i></div>
<div class="col b-a b-grey padding-30 pointer" :class="[{'bg-info': stage === 3}, {'text-white': stage === 3}]" @click="startMapping(3)">Unknown</div>
<div class="col-auto"><i class="fa fa-angle-double-right fs-20"></i></div>
<div class="col b-a b-grey padding-30 pointer" :class="[{'bg-info': stage === 4}, {'text-white': stage === 4}]" @click="startMapping(4)">Pending Export</div>
</div>
</div>
</div>
<div class="row justify-content-center align-items-center m-t-50 m-b-50 hide" v-if="type">
<div class="col-5">
<div class="row text-center">
<div class="col b-a b-grey padding-30 m-r-15 pointer" :class="[{'bg-complete': type === 1}, {'text-white': type === 1}]" @click="type=1">All</div>
<div class="col b-a b-grey padding-30 m-r-15 pointer" :class="[{'bg-complete': type === 1}, {'text-white': type === 1}]" @click="type=1">Sales</div>
<div class="col b-a b-grey padding-30 pointer" :class="[{'bg-complete': type === 2}, {'text-white': type === 2}]" @click="type=2">Wallet Top Up</div>
<div class="col b-a b-grey padding-30 pointer" :class="[{'bg-complete': type === 2}, {'text-white': type === 2}]" @click="type=2">Internal Bank Transfer In</div>
<div class="col b-a b-grey padding-30 pointer" :class="[{'bg-complete': type === 2}, {'text-white': type === 2}]" @click="type=2">Unknown</div>
</div>
</div>
</div>
<div class="row justify-content-center m-b-50" v-show="step === 0">
<div class="col-4">
<div class="row">
<div class="col">
<button type="button" class="btn btn-lg btn-block btn-primary b-rad-none" @click="startMapping(1)" v-if="type">Start Mapping</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row p-b-15 p-t-15 bg-master-lighter" v-show="step !== 0">
<div class="col"></div>
<div class="col-auto pointer bold text-danger" @click="step=0">X</div>
</div>
<div class="row parentContainer" v-if="step > 0 && stage !== 4">
<div class="col">
<div class="row">
<div class="col">
<div v-if="stage === 1">
<div class="btn btn-primary btn-sm m-t-10 requestModal" data-type="approveMappingTransaction">Approve all the Mapping Below</div>
<modal-component class="animate__animated animate__fast animate__fadeIn" type="approveMappingTransaction">
<general-confirmation-form-component
contentText="Are you sure you want to approve all these Transaction?"
modalType="confirm"
class="text-center"
:apiRoute="route('api.accounting.statement_transaction.owner.groupApprove')"
apiMethod="post"
:section="section"
>
</general-confirmation-form-component>
</modal-component>
</div>
<div class="row m-t-10 m-b-10">
<div class="col">
<div class="row p-t-10 p-b-10 b-b b-grey text-master-light">
<div class="col-2">Date</div>
<div class="col-2">Description</div>
<div class="col-5">
<div class="row">
<div class="col">System</div>
<div class="col">Type</div>
<div class="col">Reference</div>
</div>
</div>
<div class="col-1">Amount</div>
</div>
</div>
</div>
<list-component ref="bankTransactionsList" section="bankTransactionSection" :endpoint="route('api.accounting.bank.transaction')" :options="this.filter">
<template slot="list" slot-scope="{data}">
<statement-transaction-component :data="data" :section="section" :stage="stage"></statement-transaction-component>
</template>
</list-component>
</div>
</div>
</div>
</div>
<div class="row" v-if="stage === 4">
<div class="col">
<div class="row text-center m-t-50 m-b-50 p-t-50 p-b-50" v-show="exportStage === 0">
<div class="col">
<div class="btn btn-lg btn-primary" @click="exportStage++">Export Invoices To AutoCount</div>
<br>
<div class="row">
<div class="col">
<a href="https://docs.google.com/presentation/d/1N9QQOEMajYVVHQ4lMN7_Cu0BYNe4NYdV-TnxAhJUgRE/edit?usp=sharing" target="_blank">Learn How to do this step?</a>
</div>
</div>
</div>
</div>
<div class="row text-center m-t-50 m-b-50 p-t-50 p-b-50" v-if="exportStage === 1">
<div class="col">
<div class="row">
<div class="col">
<file-input-component :validator="$v.files" v-model="files">
<template slot="label">
</template>
</file-input-component>
</div>
</div>
<div class="btn btn-lg btn-primary m-t-20" @click="importInvoice">Import Invoices</div>
<!-- todo-new: delete later --><br><div class="btn btn-lg btn-primary m-t-20" @click="exportStage++">Nest Step</div>
<br>
<div class="row">
<div class="col">
<a href="https://docs.google.com/presentation/d/1XwKcdBCpHQSCQmsgnUHsypcqk5kdc8uZMqkjW9JXiw4/edit?usp=sharing" target="_blank">Learn How to do this step?</a>
</div>
</div>
</div>
</div>
<div class="row text-center m-t-50 m-b-50 p-t-50 p-b-50" v-show="exportStage === 2">
<div class="col">
<div class="btn btn-lg btn-primary" @click="exportStage++">Export Receipts To AutoCount</div>
<br>
<div class="row">
<div class="col">
<a href="https://docs.google.com/presentation/d/1xMiWjU1hqPaihMhbgflFSDsu1sC_0Lk7OFmwDaXVt-w/edit?usp=sharing" target="_blank">Learn How to do this step?</a>
</div>
</div>
</div>
</div>
<div class="row text-center m-t-50 m-b-50 p-t-50 p-b-50" v-if="exportStage === 3">
<div class="col">
<div class="row">
<div class="col">
<file-input-component :validator="$v.files" v-model="files">
<template slot="label">
</template>
</file-input-component>
</div>
</div>
<div class="btn btn-lg btn-primary m-t-20" @click="importReceipts">Import Receipts</div>
<!-- todo-new: delete later --><br><div class="btn btn-lg btn-primary m-t-20" @click="exportStage++">Nest Step</div>
<br>
<div class="row">
<div class="col">
<a href="https://docs.google.com/presentation/d/1xMiWjU1hqPaihMhbgflFSDsu1sC_0Lk7OFmwDaXVt-w/edit?usp=sharing" target="_blank">Learn How to do this step?</a>
</div>
</div>
</div>
</div>
<div class="row text-center m-t-50 m-b-50 p-t-50 p-b-50" v-show="exportStage === 4">
<div class="col">
All Done, Good Job <span class="fs-50">&#128079;</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { required } from "vuelidate/lib/validators";
export default {
components: {},
data(){
return {
type: null,
stage: null,
exportStage: 0,
step: 0,
filter: {},
files: [],
parameters: {},
section: 'bankTransactionSection',
}
},
validations: {
files: {
// required // todo-new: set required if is pdf section
}
},
methods: {
importInvoice(){
this.parameters = {
files: this.files
};
this.submit(this.route('api.import_invoices.upload'), 'post', this.section, true, false);
},
importReceipts(){
this.parameters = {
files: this.files
};
this.submit(this.route('api.import_receipts.upload'), 'post', this.section, true, false);
},
successHandler(){
this.step += 1;
// reset parameters
this.files = [];
this.parameters = {};
},
startMapping(stage){
this.stage = stage;
this.exportStage = 0;
if(this.type === 1){
switch(this.stage){
case 1:
this.filter = {min_amount: 0, is_mapped: true, is_mapped_with_multiple: false, statement_transaction_owner_type_in: [1, 2], statement_transaction_owner_status_in: [1], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 2:
this.filter = {min_amount: 0, is_mapped: true, is_mapped_with_multiple: true, statement_transaction_owner_type_in: [1, 2], statement_transaction_owner_status_in: [1], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 3:
this.filter = {min_amount: 0, is_mapped: false, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 4:
this.filter = {min_amount: 0, is_mapped: true, statement_transaction_owner_type_in: [1, 2], statement_transaction_owner_status_in: [2], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
}
}
if(this.type === 2){
switch(this.stage){
case 1:
this.filter = {max_amount: 0, is_mapped: true, is_mapped_with_multiple: false, statement_transaction_owner_type_in: [3, 5],statement_transaction_owner_status_in: [1], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 2:
this.filter = {max_amount: 0, is_mapped: true, is_mapped_with_multiple: true, statement_transaction_owner_type_in: [3, 5], statement_transaction_owner_status_in: [1], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 3:
this.filter = {max_amount: 0, is_mapped: false, per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
case 4:
this.filter = {max_amount: 0, is_mapped: true, statement_transaction_owner_type_in: [3, 5], statement_transaction_owner_status_in: [2], per_page: 100, order_by: {column: 'posting_date', DESC: true}}
break;
}
}
this.step = 1;
if(this.$refs.bankTransactionsList){
this.$refs.bankTransactionsList.updateFilters(this.filter);
}
}
}
}
</script>
@@ -27,9 +27,10 @@
<div class="row m-b-10">
<div class="col">
<validation-wrapper-component :validator="$v.parameters.holder_name">
<label>Account Holder Name</label>
<label>Account Holder Name / Company Name</label>
<input type="text" class="form-control" v-model="parameters.holder_name" :disabled="disabled">
</validation-wrapper-component>
<span class="text-danger fs-10 m-l-5" v-if="serviceType">{{ serviceType.id != 4 ? '*Please ensure to use the appropriate company name for corporate transfers, instead of personal names.' : ''}}</span>
</div>
</div>
<div class="row m-b-10">
@@ -0,0 +1,134 @@
<template>
<div class="row">
<div class="col">
<div class="row">
<div class="col">
<loading-component style="height: 50px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
</div>
</div>
<div class="row" @keyup.enter="submitSearch()" v-show="!isLoading">
<div class="col-12 col-md">
<choose-currency-component v-on:input="updateCurrencyId($event)" :currecy_id="filters.fixed_currency_id"></choose-currency-component>
</div>
<div class="col-12 col-md">
<choose-service-component v-on:input="updateServiceId($event)" v-on:loaded="doneLoadingServiceId"></choose-service-component>
</div>
<div class="col-12 col-md">
<validation-wrapper-component selectable :validator="$v.filters.status">
<label>Status</label>
<select-component :options="['Active', 'Complete']" v-model="filters.status"></select-component>
</validation-wrapper-component>
</div>
<div class="col-auto">
<div class="row h-100">
<div class="col">
<button type="button" class="btn btn-lg btn-primary w-100 h-100 d-block" @click="submitSearch()">Search</button>
</div>
<div class="col">
<button type="button" class="btn btn-lg btn-default w-100 h-100 d-block" @click="reset()">Reset</button>
</div>
</div>
</div>
</div>
<div class="row m-t-50" v-if="hasSubmited">
<div class="col">
<list-component :key="searchKey" section="transferFiltersSection" :endpoint="route('api.booking.list')" :options="options">
<template slot="list" slot-scope="{data}">
<booking-component :data="data"></booking-component>
</template>
</list-component>
</div>
</div>
</div>
</div>
</template>
<script>
import componentHandler from '../../../general/mixins/componentHandler';
import { required, requiredIf, minValue, decimal } from "vuelidate/lib/validators";
export default {
data() {
return {
isLoading: true,
searchKey: 1,
hasSubmited: false,
options: {
per_page: 10
},
filters: {
fixed_currency_id: null,
service_id: null,
status: null,
},
}
},
validations: {
filters: {
fixed_currency_id: {},
service_id: {},
status: {},
}
},
methods: {
submitSearch() {
this.options = {
per_page: 10
},
this.filters.service_id != null ? (this.options.service_id = this.filters.service_id) : null;
this.filters.fixed_currency_id != null ? (this.options.fixed_currency_id = this.filters.fixed_currency_id) : null;
if (this.filters.status != null) {
switch(this.filters.status) {
case 'Active':
this.options.status = '2';
break;
case 'Complete':
this.options.status = '3';
break;
}
}
this.searchKey +=1;
this.hasSubmited = true;
},
reset() {
this.marking = '';
this.hasSubmited = false;
this.currentKey += 1;
},
updateCurrencyId(id){
this.filters.fixed_currency_id = id;
},
updateServiceId(id){
this.filters.service_id = id;
},
doneLoadingServiceId(){
this.isLoading = !this.isLoading;
var currency = new URL(location.href).searchParams.get('currency');
if (currency != null) {
switch (currency) {
case 'usd':
this.filters.fixed_currency_id = 3;
this.submitSearch();
break;
case 'cny':
this.filters.fixed_currency_id = 2;
this.submitSearch();
break;
case 'rmb':
this.filters.fixed_currency_id = 2;
this.submitSearch();
break;
}
}
}
},
mixins: [componentHandler]
}
</script>
@@ -33,6 +33,11 @@
<div class="font-heading fs-8 all-caps" :class="[{'text-danger': item.status === 4}, {'text-primary': item.status !== 4}]">{{ item.status === 2 ? 'Received' : item.status === 4 ? 'Rejected' : 'Submitted'}} On: {{item.updated_at}}</div>
</div>
</div>
<div class="row" v-if="$store.getters.isAdmin && item.bill_no">
<div class="col">
<div class="font-heading fs-8 all-caps m-t-5">Bill Number: <b>{{ item.bill_no }}</b></div>
</div>
</div>
</div>
<div class="col-auto" v-if="item.status !== 3" :class="[{'bg-master-light': item.status === 1 && item.type !== 6}, {'bg-master-lighter': item.status === 2}, {'bg-warning-light': item.type === 6}]">
<div class="row align-items-center h-100" v-if="item.status !== 1 || item.payment_method !== 5">
@@ -86,6 +91,11 @@
<div class="font-heading fs-8 all-caps" >{{ item.transaction_bill.status === 1 ? 'Paid On: ' + item.updated_at : 'Transferred On:' + item.transaction_bill.updated_at }}</div>
</div>
</div>
<div class="row" v-if="$store.getters.isAdmin && item.bill_no">
<div class="col">
<div class="font-heading fs-8 all-caps m-t-5">Bill Number: <b>{{ item.bill_no }}</b></div>
</div>
</div>
</div>
<div class="col-auto" v-if="item.transaction_bill.status !== 3 && item.transaction_bill.status !== 2" :class="[{'bg-master-light': item.transaction_bill.status === 1}, {'bg-master-lighter': item.transaction_bill.status === 2}]">
<div class="row align-items-center h-100">
@@ -28,7 +28,7 @@
v-bind:key="currency.id">
<div class="col b-b b-grey p-t-10 p-b-10 pointer hover-t-10 p-b-10 "
:class="[{ 'bg-primary-light': selectedCurrency.id === currency.id }, { 'text-white': selectedCurrency.id === currency.id }, { 'hover-primary': selectedCurrency.id !== currency.id }, { 'pointer': selectedCurrency.id !== currency.id }]"
@click="UpdateCurrency(currency)">
@click="updateCurrency(currency)">
<div class="row align-items-center justify-content-center">
<div class="col">
<div class="row align-items-center justify-content-center">
@@ -59,6 +59,19 @@
<script>
export default {
props: {
currecy_id: {
type: Number,
required: false,
default: null
},
},
watch: {
currecy_id(newValue, oldValue) {
var currencySelected = this.currencyList.find(currencyList => currencyList.id === newValue);
this.updateCurrency(currencySelected);
}
},
data() {
return {
section: 'chooseCurrencySection',
@@ -105,10 +118,10 @@ export default {
};
},
mounted() {
this.UpdateCurrency(Object.values(this.currencyList)[0]);
this.updateCurrency(Object.values(this.currencyList)[0]);
},
methods: {
UpdateCurrency(currency) {
updateCurrency(currency) {
this.selectedCurrency = currency;
this.recipientMenu = false;
this.$emit('input', currency.id);
@@ -13,7 +13,7 @@
<div class="row m-b-15">
<div class="col">
<validation-wrapper-component :validator="$v.parameters.identification_no">
<label class="text-primary">{{ data.type === 0 ? 'IC/Passport' : 'SSM Registration' }} Number</label>
<label class="text-primary">{{ data.type === 0 ? 'IC' : 'SSM Registration' }} Number</label>
<div class="controls">
<input type="text" class="form-control fs-12" v-model="parameters.identification_no">
</div>
@@ -0,0 +1,60 @@
<template>
<div class="row">
<div class="col">
<loading-component style="height: 300px; top: 0;" key="1" color="success" v-show="isLoading" ></loading-component>
<div class="row justify-content-center" v-show="!isLoading">
<div class="col">
<div class="row m-b-20">
<div class="col">
<h3 class="all-caps">Are you Sure?</h3>
<div class="fs-11">{{ contentText }}</div>
</div>
</div>
<div class="row">
<div class="col p-r-5">
<div class="btn btn-sm btn-default btn-block bg-master-lighter" data-dismiss="modal">Cancel</div>
</div>
<div class="col p-l-5">
<div class="btn btn-sm btn-block b-rad-none" :class="[{'btn-danger': modalType !== 'confirm'}, {'btn-success': modalType === 'confirm'}]" @click="submit(apiRoute, apiMethod, section, true, true)">{{ buttonText }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import componentHandler from '../../../general/mixins/componentHandler';
import ModalFormHandler from '../../../general/mixins/modalFormHandler';
export default {
props: {
contentText: {
type: String,
required: true
},
modalType: {
type: String,
default: 'confirm'
},
buttonText: {
type: String,
default: 'Confirm'
},
apiRoute: {
type: String,
required: true
},
apiMethod: {
type: String,
required: true
},
},
methods: {
test() {
console.log('sjhb');
},
},
mixins: [componentHandler, ModalFormHandler]
}
</script>
@@ -19,13 +19,18 @@
<div class="font-heading fs-10 bold">
MYR {{(Math.round((item.amount + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="font-heading fs-8 all-caps" >Submitted On: {{ item.updated_at }}</div>
</div>
</div>
<div class="row" v-if="$store.getters.isAdmin && item.bill_no">
<div class="col">
<div class="font-heading fs-8 all-caps m-t-5">Bill Number: <b>{{ item.bill_no }}</b></div>
</div>
</div>
</div>
<div class="col-auto bg-master-light">
<div class="row align-items-center h-100">
@@ -66,6 +71,11 @@
<div class="font-heading fs-8 all-caps" >Paid On: {{ item.updated_at }}</div>
</div>
</div>
<div class="row" v-if="$store.getters.isAdmin && item.bill_no">
<div class="col">
<div class="font-heading fs-8 all-caps m-t-5">Bill Number: <b>{{ item.bill_no }}</b></div>
</div>
</div>
</div>
<div class="col-auto pointer bg-success">
<a :href="route('billplz.bill', item.reference)" target="_blank">
@@ -110,6 +120,11 @@
<div class="font-heading fs-8 all-caps" >Rejected On: {{ item.updated_at }}</div>
</div>
</div>
<div class="row" v-if="$store.getters.isAdmin && item.bill_no">
<div class="col">
<div class="font-heading fs-8 all-caps m-t-5">Bill Number: <b>{{ item.bill_no }}</b></div>
</div>
</div>
</div>
<div class="col-auto">
<div class="row align-items-center h-100">
@@ -0,0 +1,12 @@
@extends('layouts.base_portal')
@section('inner_content')
<div class="row">
<div class="col">
<list-component ref="paymentProofList" section="bankStatementSection" :endpoint="route('api.accounting.bank.transaction')" :options="{statement_transaction_account_id: {{$account}}, per_page: 100, order_by: {column: 'posting_date', DESC: true}}">
<template slot="list" slot-scope="{data}">
<statement-transaction-component section="bankStatementSection" :data="data"></statement-transaction-component>
</template>
</list-component>
</div>
</div>
@endsection
@@ -0,0 +1,8 @@
@extends('layouts.base_portal')
@section('inner_content')
<div class="row">
<div class="col">
<statement-transactions-details-component section="bankStatementDetail" :statement="{{$statement}}"></statement-transactions-details-component>
</div>
</div>
@endsection
File diff suppressed because one or more lines are too long
@@ -0,0 +1,461 @@
@extends('layouts.base_portal')
@section('inner_content')
<upload-debtor-excel-component section="uploadDebtorExcelSection"></upload-debtor-excel-component>
<div class="container-fluid">
<div class="row m-b-50">
<div class="col-12">
<h1 class="text-left">Accounting Dashboard</h1>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">Import Bank Statement</div>
<div class="card-body">
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
@if (session('error'))
<div class="alert alert-danger mt-3">
{{ session('error') }}
</div>
@endif
<form method="POST" action="{{ route('statements.import') }}" enctype="multipart/form-data">
@csrf
<div class="form-group">
<label for="file">Select CSV File</label>
<input type="file" class="form-control-file" id="file" name="file" required>
</div>
<button type="submit" class="btn btn-primary">Import</button>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">Transaction Filtering</div>
<div class="card-body">
<form>
<div class="form-group">
<label for="startDate">Start Date:</label>
<input type="date" id="startDate" name="startDate" class="form-control">
</div>
<div class="form-group">
<label for="endDate">End Date:</label>
<input type="date" id="endDate" name="endDate" class="form-control">
</div>
<div class="form-group">
<label for="transactionType">Transaction Type:</label>
<select id="transactionType" name="transactionType" class="form-control">
<option value="">All</option>
<option value="Deposit">Deposit</option>
<option value="Withdrawal">Withdrawal</option>
</select>
</div>
<div class="form-group">
<label for="accountName">Account Name:</label>
<select id="accountName" name="accountName" class="form-control">
<option value="">All</option>
<option value="Bank Account 1">Bank Account 1</option>
<option value="Bank Account 2">Bank Account 2</option>
<option value="Bank Account 3">Bank Account 3</option>
</select>
</div>
<input type="submit" value="Filter" class="btn btn-primary">
</form>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-8">
<div class="row">
<div class="col-12">
<ul class="nav nav-tabs no-border" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="tab1-tab" data-toggle="tab" href="#tab1" role="tab" aria-controls="tab1" aria-selected="true">Transactions</a>
</li>
<li class="nav-item">
<a class="nav-link" id="tab2-tab" data-toggle="tab" href="#tab2" role="tab" aria-controls="tab2" aria-selected="false">Deposit Mapping</a>
</li>
<li class="nav-item">
<a class="nav-link" id="tab3-tab" data-toggle="tab" href="#tab3" role="tab" aria-controls="tab3" aria-selected="false">Withdrawal Mapping</a>
</li>
<li class="nav-item">
<a class="nav-link" id="tab4-tab" data-toggle="tab" href="#tab4" role="tab" aria-controls="tab4" aria-selected="false">Unknown Transactions</a>
</li>
<li class="nav-item ml-auto">
<a class="nav-link" id="tab5-tab" data-toggle="tab" href="#tab5" role="tab" aria-controls="tab5" aria-selected="false">Statements</a>
</li>
</ul>
<div class="card no-border">
<div class="card-body">
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="tab1" role="tabpanel" aria-labelledby="tab1-tab">
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Transaction Type</th>
<th>Account Name</th>
<th>Description</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>2023-04-11</td>
<td>Deposit</td>
<td>Bank Account 1</td>
<td>Salary Payment</td>
<td>Pending</td>
</tr>
<tr>
<td>2023-04-09</td>
<td>Withdrawal</td>
<td>Bank Account 2</td>
<td>Vendor Payment</td>
<td>Reconciled</td>
</tr>
<tr>
<td>2023-04-07</td>
<td>Deposit</td>
<td>Bank Account 3</td>
<td>Online Order Payment</td>
<td>Exception</td>
</tr>
</tbody>
</table>
</div>
<div class="tab-pane fade" id="tab2" role="tabpanel" aria-labelledby="tab2-tab">
<ul class="nav nav-pills nav-justified mb-3" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-toggle="pill" href="#step1">Step 1</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#step2">Step 2</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#step3">Step 3</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#step4">Step 4</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#step5">Step 5</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#step6">Step 6</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="pill" href="#step7">Step 7</a>
</li>
</ul>
<!-- Wizard Content -->
<div class="tab-content">
<div class="tab-pane fade show active" id="step1">
<h4 class="card-title">Step 1: Start Mapping bank Transactions</h4>
<p class="card-text">This is the first step in the wizard.</p>
</div>
<div class="tab-pane fade" id="step2">
<h4 class="card-title">Step 2: List of automatically mapped bank transactions</h4>
<p class="card-text">This is the second step in the wizard.</p>
</div>
<div class="tab-pane fade" id="step3">
<h4 class="card-title">Step 3: List of bank transactions that require manual mapping</h4>
<p class="card-text">This is the third step in the wizard.</p>
</div>
<div class="tab-pane fade" id="step4">
<h4 class="card-title">Step 4: Export invoices to accounting software</h4>
<p class="card-text">This is the fourth step in the wizard.</p>
</div>
<div class="tab-pane fade" id="step5">
<h4 class="card-title">Step 5: Import invoices from accounting software</h4>
<p class="card-text">This is the fifth step in the wizard.</p>
</div>
<div class="tab-pane fade" id="step6">
<h4 class="card-title">Step 6: Export payment receipts</h4>
<p class="card-text">This is the sixth step in the wizard.</p>
</div>
<div class="tab-pane fade" id="step7">
<h4 class="card-title">Step 7: Import payment receipts</h4>
<p class="card-text">This is the final step in the wizard.</p>
</div>
</div>
<!-- Wizard Navigation Buttons -->
<ul class="list-inline">
<li class="list-inline-item"><button type="button" class="btn btn-secondary prev-step">Previous</button></li>
<li class="list-inline-item"><button type="button" class="btn btn-primary next-step">Next</button></li>
<li class="list-inline-item"><button type="button" class="btn btn-success finish-step">Finish</button></li>
</ul>
</div>
<div class="tab-pane fade" id="tab3" role="tabpanel" aria-labelledby="tab3-tab">
<h2>Tab 3 Content</h2>
<p>Aliquam vitae magna sit amet tellus bibendum posuere. Integer nec leo quis arcu tincidunt eleifend. Suspendisse potenti. Proin ullamcorper sodales nisl vel tincidunt. Etiam malesuada, mauris sit amet tincidunt facilisis, nisl enim aliquet turpis, ac pretium lacus nulla a libero. Vivamus euismod ex vel sapien dignissim, a fringilla enim fringilla. Sed sit amet lobortis augue. Aenean ut neque ac elit interdum pretium vel vel purus. Duis ut magna at ante dignissim efficitur. Pellentesque molestie bibendum ipsum a malesuada. Nulla cursus vehicula felis vel dapibus. Suspendisse eget vulputate ex. In pulvinar tincidunt justo, eu viverra orci malesuada id. Sed posuere arcu ac diam finibus posuere.</p>
</div>
<div class="tab-pane fade" id="tab5" role="tabpanel" aria-labelledby="tab5-tab">
<form method="GET" action="{{ route('statements.index') }}" class="form-inline mb-3">
<div class="form-group mr-3">
<label for="account">Account:</label>
<select name="account" id="account" class="form-control ml-2">
<option value="">All Accounts</option>
@foreach($accounts as $account)
<option value="{{ $account->id }}" {{ $account->id == $selectedAccount ? 'selected' : '' }}>{{ $account->name }} ({{ $account->number }})</option>
@endforeach
</select>
</div>
<button type="submit" class="btn btn-primary mr-2">Filter</button>
<a href="{{ route('statements.index') }}" class="btn btn-secondary">Clear</a>
</form>
<form method="GET" action="{{ route('statements.index') }}" class="form-inline mb-3">
<div class="form-group mr-3">
<label for="search">Search:</label>
<input type="text" class="form-control ml-2" id="search" name="search" value="{{ $search }}">
</div>
<button type="submit" class="btn btn-primary">Search</button>
</form>
<table class="table">
<thead>
<tr>
<th>Account</th>
<th>Date From</th>
<th>Date To</th>
<th>Total Amount</th>
<th>Begin Balance</th>
<th>End Balance</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($statements as $statement)
<tr>
<td>{{ $statement->account->name }} ({{ $statement->account->number }})</td>
<td>{{ $statement->date_from->format('d-m-Y') }}</td>
<td>{{ $statement->date_to->format('d-m-Y') }}</td>
<td>{{ $statement->total_amount }}</td>
<td>{{ $statement->begin_balance }}</td>
<td>{{ $statement->end_balance }}</td>
<td>
<a href="{{ route('statements.show', $statement) }}" class="btn btn-primary btn-sm">View</a>
</td>
</tr>
@endforeach
</tbody>
</table>
{{ $statements->links() }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<ul class="nav nav-tabs no-border" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link" id="tab3-tab" data-toggle="tab" href="#tab3" role="tab" aria-controls="tab3" aria-selected="false">Pending Transactions</a>
</li>
<li class="nav-item">
<a class="nav-link" id="tab3-tab" data-toggle="tab" href="#tab3" role="tab" aria-controls="tab3" aria-selected="false">Exception Handling</a>
</li>
<li class="nav-item">
<a class="nav-link" id="tab3-tab" data-toggle="tab" href="#tab3" role="tab" aria-controls="tab3" aria-selected="false">Non-operational Transactions</a>
</li>
<li class="nav-item ml-auto">
<a class="nav-link" id="tab3-tab" data-toggle="tab" href="#tab3" role="tab" aria-controls="tab3" aria-selected="false">Reconciled Transactions</a>
</li>
</ul>
<div class="card no-border">
<div class="card-body">
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="tab1" role="tabpanel" aria-labelledby="tab1-tab">
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Transaction Type</th>
<th>Account Name</th>
<th>Description</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>2023-04-11</td>
<td>Deposit</td>
<td>Bank Account 1</td>
<td>Salary Payment</td>
<td>Pending</td>
</tr>
<tr>
<td>2023-04-09</td>
<td>Withdrawal</td>
<td>Bank Account 2</td>
<td>Vendor Payment</td>
<td>Reconciled</td>
</tr>
<tr>
<td>2023-04-07</td>
<td>Deposit</td>
<td>Bank Account 3</td>
<td>Online Order Payment</td>
<td>Exception</td>
</tr>
</tbody>
</table>
</div>
<div class="tab-pane fade" id="tab2" role="tabpanel" aria-labelledby="tab2-tab">
<form method="GET" action="{{ route('statements.index') }}" class="form-inline mb-3">
<div class="form-group mr-3">
<label for="account">Account:</label>
<select name="account" id="account" class="form-control ml-2">
<option value="">All Accounts</option>
@foreach($accounts as $account)
<option value="{{ $account->id }}" {{ $account->id == $selectedAccount ? 'selected' : '' }}>{{ $account->name }} ({{ $account->number }})</option>
@endforeach
</select>
</div>
<button type="submit" class="btn btn-primary mr-2">Filter</button>
<a href="{{ route('statements.index') }}" class="btn btn-secondary">Clear</a>
</form>
<form method="GET" action="{{ route('statements.index') }}" class="form-inline mb-3">
<div class="form-group mr-3">
<label for="search">Search:</label>
<input type="text" class="form-control ml-2" id="search" name="search" value="{{ $search }}">
</div>
<button type="submit" class="btn btn-primary">Search</button>
</form>
<table class="table">
<thead>
<tr>
<th>Account</th>
<th>Date From</th>
<th>Date To</th>
<th>Total Amount</th>
<th>Begin Balance</th>
<th>End Balance</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($statements as $statement)
<tr>
<td>{{ $statement->account->name }} ({{ $statement->account->number }})</td>
<td>{{ $statement->date_from->format('d-m-Y') }}</td>
<td>{{ $statement->date_to->format('d-m-Y') }}</td>
<td>{{ $statement->total_amount }}</td>
<td>{{ $statement->begin_balance }}</td>
<td>{{ $statement->end_balance }}</td>
<td>
<a href="{{ route('statements.show', $statement) }}" class="btn btn-primary btn-sm">View</a>
</td>
</tr>
@endforeach
</tbody>
</table>
{{ $statements->links() }}
</div>
<div class="tab-pane fade" id="tab3" role="tabpanel" aria-labelledby="tab3-tab">
<h2>Tab 3 Content</h2>
<p>Aliquam vitae magna sit amet tellus bibendum posuere. Integer nec leo quis arcu tincidunt eleifend. Suspendisse potenti. Proin ullamcorper sodales nisl vel tincidunt. Etiam malesuada, mauris sit amet tincidunt facilisis, nisl enim aliquet turpis, ac pretium lacus nulla a libero. Vivamus euismod ex vel sapien dignissim, a fringilla enim fringilla. Sed sit amet lobortis augue. Aenean ut neque ac elit interdum pretium vel vel purus. Duis ut magna at ante dignissim efficitur. Pellentesque molestie bibendum ipsum a malesuada. Nulla cursus vehicula felis vel dapibus. Suspendisse eget vulputate ex. In pulvinar tincidunt justo, eu viverra orci malesuada id. Sed posuere arcu ac diam finibus posuere.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
// Initialize the wizard
$('#rootwizard').bootstrapWizard({
'tabClass': 'nav nav-pills',
'nextSelector': '.next-step',
'previousSelector': '.prev-step',
'finishSelector': '.finish-step',
'onTabClick': function (tab, navigation, index) {
return false;
},
'onNext': function (tab, navigation, index) {
return true;
},
'onPrevious': function (tab, navigation, index) {
return true;
},
'onFinish': function (tab, navigation, index) {
return true;
}
});
});
</script>
</body>
</html>
```
Next, we add some custom CSS styles to the wizard to make it look more appealing.
php
Copy code
<style>
.card {
margin-bottom: 30px;
}
.nav-pills .nav-link {
border-radius: 0;
}
.nav-pills .nav-link.active {
background-color: #007bff;
color: #fff;
}
.btn {
border-radius: 0;
}
.list-inline-item {
margin-right: 10px;
}
</style>
Finally, we add some JavaScript code to handle the wizard functionality. We use the Bootstrap Wizard plugin to enable the wizard navigation buttons.
php
Copy code
<script>
$(document).ready(function () {
// Initialize the wizard
$('#rootwizard').bootstrapWizard({
'tabClass': 'nav nav-pills',
'nextSelector': '.next-step',
'previousSelector': '.prev-step',
'finishSelector': '.finish-step',
'onTabClick': function (tab, navigation, index) {
return false;
},
'onNext': function (tab, navigation, index) {
return true;
},
'onPrevious': function (tab, navigation, index) {
return true;
},
'onFinish': function (tab, navigation, index) {
return true;
}
</script>
@endsection
@@ -0,0 +1,123 @@
@extends('layouts.base_portal')
@section('inner_content')
<div class="container">
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Statement: {{ $statement->date_from->format('M d, Y') }} - {{ $statement->date_to->format('M d, Y') }}</h5>
<p class="card-text">Account: {{ $statement->account->name }} ({{ $statement->account->number }})</p>
{{-- <p class="card-text">Total Debit: {{ number_format($statement->total_debit, 2) }}</p>--}}
{{-- <p class="card-text">Total Credit: {{ number_format($statement->total_credit, 2) }}</p>--}}
<p class="card-text">Beginning Balance: {{ number_format($statement->begin_balance, 2) }}</p>
<p class="card-text">Ending Balance: {{ number_format($statement->end_balance, 2) }}</p>
</div>
</div>
<div class="mt-3 mb-3">
<a href="{{ route('statements.download', $statement->id) }}" class="btn btn-primary">Download Statement</a>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">Transactions</h5>
<form action="{{ route('statements.show', $statement->id) }}" method="get">
<div class="row">
<div class="col-sm-4 mb-3">
<label for="transaction_filter">Transaction:</label>
<input type="text" name="transaction_filter" id="transaction_filter" class="form-control" value="{{ request()->get('transaction_filter') }}">
</div>
<div class="col-sm-4 mb-3">
<label for="from_amount_filter">From Amount:</label>
<input type="number" step="0.01" name="from_amount_filter" id="from_amount_filter" class="form-control" value="{{ request()->get('from_amount_filter') }}">
</div>
<div class="col-sm-4 mb-3">
<label for="to_amount_filter">To Amount:</label>
<input type="number" step="0.01" name="to_amount_filter" id="to_amount_filter" class="form-control" value="{{ request()->get('to_amount_filter') }}">
</div>
</div>
<button type="submit" class="btn btn-primary">Filter</button>
</form>
<table class="table table-hover mt-4">
<thead>
<tr>
<th>Date</th>
<th>time</th>
<th>From</th>
<th>Type</th>
<th>Payment Method</th>
<th class="text-right">Debit</th>
<th class="text-right">Credit</th>
</tr>
</thead>
<tbody>
@foreach ($transactions as $transaction)
@php
$from = $transaction->transaction_description_2;
$type = '';
$paymentMethod = '';
if(!$transaction->transaction_description_2){
$from = $transaction->transaction_description;
}
if(str_contains($transaction->transaction_description_3, 'FPX')){
$paymentMethod = 'FPX';
}
if(str_contains($transaction->transaction_description_3, 'A/C')){
$paymentMethod = 'Bank Transfer';
}
if(str_contains($transaction->transaction_description_3, 'IBFT')){
$paymentMethod = 'Bank Transfer';
}
if(str_contains($transaction->transaction_description_3, 'FOREIGN TT')){
$paymentMethod = 'International Transfer';
}
if($transaction->amount > 0) {
$type = 'Sale';
}
if($transaction->amount < 0 && str_contains($transaction->transaction_description_3, 'ESI')) {
$type = 'Statutory Payment';
}
if($transaction->amount < 0 && str_contains($transaction->transaction_description_3, 'ESI')) {
$type = 'Statutory Payment';
}
if($transaction->amount < 0 && str_contains($transaction->transaction_description, 'DR DUITNOW S/CHRG')) {
$type = 'Bank Charge';
$paymentMethod = 'Bank Deduction';
}
if($transaction->amount < 0 && str_contains($transaction->transaction_description, 'CMS - DR CORP CHG')) {
$type = 'Card Payment';
$paymentMethod = 'Card';
}
if($transaction->amount < 0 && str_contains($transaction->transaction_description_3, 'ELECTRONIC')) {
$type = 'Electric Bill';
}
@endphp
<tr>
<td>{{ $transaction->posting_date->format('d-m-Y') }}</td>
<td>{{ $transaction->posting_date->format('g:i A') }}</td>
<td>{{ $from }}</td>
<td>{{ $type }}</td>
<td>{{ $paymentMethod }}</td>
<td class="text-right">{{ $transaction->amount < 0 ? number_format($transaction->amount, 2) : 0.00}}</td>
<td class="text-right">{{ $transaction->amount > 0 ? number_format($transaction->amount, 2) : 0.00}}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="mt-3">
{{ $transactions->appends(request()->query())->links() }}
</div>
</div>
@endsection
@@ -0,0 +1,17 @@
@extends('layouts.base_portal')
@section('inner_content')
<div class="row">
<div class="col">
<div class="row">
<div class="col-12">
<div class="row m-b-15 p-b-10 b-b b-grey">
<div class="col">
<small class="all-caps muted fs-10">Transfer Filters</small>
</div>
</div>
<filter-booking-component section="transferFiltersSection" ></filter-booking-component>
</div>
</div>
</div>
</div>
@endsection
+19
View File
@@ -0,0 +1,19 @@
<?php
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'accounting', 'as' => 'accounting.', 'namespace' => 'Accounting'], function () {
Route::post('/import', 'BankStatementController@import')->name('statement.import');
Route::get('/bank_account', 'BankStatementController@transactions')->name('bank.transaction');
Route::group(['prefix' => 'statements/{id}', 'as' => 'statement.'], function () {
Route::get('/details', 'BankStatementController@fetch')->name('details');
Route::put('/details/update', 'BankStatementController@update')->name('details.update');
});
Route::post('bankStatement/{id}/details/{status}', 'ApproveDuplicateBankStatementDetailsStatusController@update')->where('status', 'approve|reject')->name('bankStatement.details.status.update');
Route::group(['prefix' => 'statement_transaction', 'as' => 'statement_transaction.'], function () {
Route::post('/owner/group-approve', 'GroupApproveStatementTransactionController@approve')->name('owner.groupApprove');
Route::post('/{id}/owner/{status}', 'UpdateStatementTransactionStatusController@update')->where('status', 'approve|reject')->name('owner.status.update');
});
});
+4
View File
@@ -30,6 +30,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
Route::get('/storage/{fileName}/fetch', 'Documents\RenderDocumentController@fileStorageServe')->where(['fileName' => '.*'])->name('storage.document.file');
Route::post('/import/update-debtor/f614e339d7058904a831aad742e24d55', 'Imports\ImportUpdateDebtorController@import')->name('debtor.import');
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');
require __DIR__ . '/company.php';
@@ -56,6 +58,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
require __DIR__ . '/wallet.php';
require __DIR__ . '/voucher.php';
require __DIR__ . '/accounting.php';
// require __DIR__ . '/rate.php';
// require __DIR__ . '/receipt.php';
+18 -1
View File
@@ -1,5 +1,6 @@
<?php
use App\Http\Controllers\Accounting\BankStatementController;
use Carbon\Carbon;
use App\Models\User;
use App\Models\Wallet;
@@ -89,6 +90,10 @@ Route::get('/transfers', function () {
return view('pages.bookings.index');
})->name('bookings');
Route::get('/list-transfer', function () {
return view('pages.bookings.filter');
})->name('list_transfer');
Route::get('/bookings/urgent', function () {
return view('pages.urgent_list');
})->name('bookings.urgent');
@@ -557,6 +562,18 @@ Route::get('/currency-rate-history', function () {
return view('pages.rate_histories')->with('paymentMethods', $paymentMethods);
})->name('currency_rate.history');
Route::get('/statements', [BankStatementController::class, 'index'])->name('statements.index');
Route::get('/statements/v2', [BankStatementController::class, 'indexv2'])->name('statements.indexv2');
Route::post('/statements/import', [BankStatementController::class, 'import'])->name('statements.import');
Route::get('/statements/{statement}/details', function ($statement) {
return view('pages.accounting.bank-statements.details', ['statement' => $statement]);
})->name('statements.transactions.details');
Route::get('/statements/{account}/transactions', function ($account) {
return view('pages.accounting.bank-statements.bank_statement', ['account' => $account]);
})->name('statements.account.transactions');
Route::get('/statements/{statement}', [BankStatementController::class, 'show'])->name('statements.show');
Route::get('/statements/mapping/rerun', [BankStatementController::class, 'rerun'])->name('statements.rerun');
Route::get('/statements/{statement}/download', 'StatementController@download')->name('statements.download');
Route::get('/bank-record', 'Imports\ImportBankRecordController@import');
Route::get('/po/outsource/check', function(){
@@ -600,4 +617,4 @@ Route::get('/po/outsource/check', function(){
Route::get('/upload-honey-trap', function () {
return view('pages.honey_trap');
})->name('upload_honey_trap');
})->name('upload_honey_trap');