Merge branch 'master' of gitlab.com:CIEFWorldwideSdnBhd/exchange-2.0 into regenerate-invoice-with-first-bill-no

This commit is contained in:
edmondlang
2023-11-08 00:11:44 +08:00
88 changed files with 2831 additions and 897 deletions
@@ -16,6 +16,8 @@ class CreatedAfterOrEqual implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('created_at', '>=', Carbon::parse($value));
$table = $builder->getModel()->getTable();
$startDate = Carbon::createFromFormat('d-m-Y', $value)->startOfDay();
return $builder->where("{$table}.created_at", '>=', $startDate);
}
}
@@ -15,6 +15,8 @@ class CreatedBeforeOrEqual implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('created_at', '<=', Carbon::parse($value));
$table = $builder->getModel()->getTable();
$endDate = Carbon::createFromFormat('d-m-Y', $value)->endOfDay();
return $builder->where("{$table}.created_at", '<=', $endDate);
}
}
@@ -15,7 +15,7 @@ class HasAccountStatementId implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereHas('statementTransaction', function ($query) use ($value) {
return $builder->whereHas('transaction', function ($query) use ($value) {
$query->where('account_statement_id', $value);
});
}
@@ -0,0 +1,23 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class IsMappedFalseOrMappedButStatusIn implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where(function($q) use ($value) {
$q->whereDoesntHave('owners');
$q->orwhereDoesntHave('owner_status');
});
}
}
@@ -14,7 +14,8 @@ class OwnerId implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('owner_id', $value);
$table = $builder->getModel()->getTable();
return $builder->where("{$table}.owner_id", $value);
}
}
@@ -14,7 +14,8 @@ class OwnerType implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('owner_type', $value);
$table = $builder->getModel()->getTable();
return $builder->where("{$table}.owner_type", $value);
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class StatementTransactionOwnerInvoiceOrReceiptRefNotNull implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereHas('owners', function ($query) {
return $query->where(function ($q) {
$q->orWhereNotNull('invoice_reference')->orWhereNotNull('receipt_reference');
});
});
}
}
@@ -0,0 +1,18 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class StatementTransactionPostingEnd implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereDate('posting_date', '<=', date('Y-m-d',strtotime($value)));
}
}
@@ -0,0 +1,18 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class StatementTransactionPostingStart implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereDate('posting_date', '>=', date('Y-m-d',strtotime($value)));
}
}
@@ -14,7 +14,8 @@ class StatusIn implements Filter
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereIn('status', $value);
$table = $builder->getModel()->getTable();
return $builder->whereIn("{$table}.status", $value);
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Booking;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class WithBookingMarkingLike implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->join('transactions as t2', 't2.payment_reference', '=', 'transactions.bill_no')
->join('bookings', function ($join) use ($value) {
$join->on('bookings.id', '=', 't2.owner_id')
->where('bookings.marking', 'LIKE', '%'.$value.'%');
})
->addSelect(['transactions.*','t2.owner_id as bookingId', 'bookings.marking as bookingMarking']);
}
}
@@ -13,9 +13,15 @@ class CreateBankStatementTransactionOwners implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $transactions;
public function __construct($transactions) {
$this->transactions = $transactions;
}
public function handle()
{
(App()->make(CreateBankStatementTransactionOwnersProcessor::class))->execute();
(App()->make(CreateBankStatementTransactionOwnersProcessor::class))->execute($this->transactions);
}
public function delay($delay)
@@ -135,7 +135,7 @@ class ApproveDuplicateBankStatementDetailsStatusLogic extends AbstractController
public function logic(Request $request): JsonResponse
{
// Determine the approval status
$status = $this->getApprovalStatus($request);
$status = $this->getConstantStatus($request->route('status'));
// Find the statement transaction owner
$owner = $this->getOwner($request);
@@ -143,21 +143,33 @@ class ApproveDuplicateBankStatementDetailsStatusLogic extends AbstractController
// Update owner status
$this->updateOwnerStatus($owner, $status);
// If the status is 'approved', handle the approval process
if ($status === ApprovalStatus::APPROVED) {
$this->handleApprovedStatus($owner);
}
// handle the siblings process
$this->handleSiblingsStatus($owner, $this->getSiblingsStatus($status));
// Check and approve remaining matches if any
$this->checkAndApproveRemainingMatches($owner);
$this->checkAndApproveRemainingMatches($owner, $status);
// Return an empty response
return $this->response([]);
}
private function getApprovalStatus(Request $request): int
private function getConstantStatus(String $statusName=null): int
{
return $request->route('status') == 'approve' ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED;
switch ($statusName) {
case 'approve':
return ApprovalStatus::APPROVED;
case 'pending_verification':
return ApprovalStatus::PENDING_VERIFICATION;
default:
return ApprovalStatus::REJECTED;
}
}
private function getSiblingsStatus(int $status): int
{
return $status == ApprovalStatus::APPROVED ? ApprovalStatus::REJECTED : ApprovalStatus::PENDING_VERIFICATION;
}
private function getOwner(Request $request): StatementTransactionOwner
@@ -170,23 +182,24 @@ class ApproveDuplicateBankStatementDetailsStatusLogic extends AbstractController
$this->updatesBankStatementTransactionOwnerStatus->execute($owner, $status);
}
private function handleApprovedStatus(StatementTransactionOwner $owner): void
private function handleSiblingsStatus(StatementTransactionOwner $owner, int $siblingStatus): void
{
// Reject all other owners with the same system, owner type, and owner ID
$this->rejectOtherOwners($owner);
// update all other owners with the same system, owner type, and owner ID
$this->updateOtherOwners($owner, $siblingStatus);
// Find all siblings and process them
$siblings = $this->getSiblings($owner);
$this->processSiblings($siblings);
foreach ($siblings as $sibling) {
$this->processSibling($sibling, $siblingStatus);
}
}
private function rejectOtherOwners(StatementTransactionOwner $owner): void
private function updateOtherOwners(StatementTransactionOwner $owner, int $status): void
{
StatementTransactionOwner::where('system', $owner->system)
->where('owner_type', $owner->owner_type)
->where('owner_id', $owner->owner_id)
StatementTransactionOwner::getSiblingsOwner()
->where('id', '!=', $owner->id)
->update(['status' => ApprovalStatus::REJECTED]);
->update(['status' => $status]);
}
private function getSiblings(StatementTransactionOwner $owner): Collection
@@ -196,86 +209,65 @@ class ApproveDuplicateBankStatementDetailsStatusLogic extends AbstractController
->get();
}
private function processSiblings(Collection $siblings): void
{
foreach ($siblings as $sibling) {
$this->processSibling($sibling);
}
}
private function processSibling(StatementTransactionOwner $sibling): void
private function processSibling(StatementTransactionOwner $sibling, int $siblingStatus): void
{
// Reject the sibling and save the changes
$sibling->status = ApprovalStatus::REJECTED;
$sibling->status = $siblingStatus;
$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 getTwins(StatementTransactionOwner $sibling): Collection
{
return StatementTransactionOwner::getSiblingsOwner()
->where('id', '!=', $sibling->id)
->get();
}
private function processTwin(StatementTransactionOwner $twin): void
{
// Find all owners with the same statement transaction ID as the twin
$owners = $this->getOwners($twin);
$owners = $this->getSiblings($twin);
// If there is only one owner (the twin itself), approve it
if ($owners->count() === 1) {
$this->updateOwnerStatus($twin, ApprovalStatus::APPROVED);
$this->updateOwnerStatus($twin, $this->getConstantStatus(request()->route('status')));
}
}
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
private function checkAndApproveRemainingMatches(StatementTransactionOwner $owner, int $status): void
{
// Find all remaining matching owners for the related transaction
$remainingMatches = $this->getRemainingMatches($owner);
$checkStatus = ($status == ApprovalStatus::APPROVED ? ApprovalStatus::PENDING_VERIFICATION : ApprovalStatus::APPROVED);
$remainingMatches = $this->getRemainingMatches($owner, $checkStatus);
// Process each remaining match
foreach ($remainingMatches as $match) {
$this->processRemainingMatch($match);
$this->processRemainingMatch($match, $status);
}
}
private function getRemainingMatches(StatementTransactionOwner $owner): Collection
private function getRemainingMatches(StatementTransactionOwner $owner, int $checkStatus): Collection
{
return StatementTransactionOwner::where('system', $owner->system)
->where('owner_type', $owner->owner_type)
->where('owner_id', $owner->owner_id)
->where('status', ApprovalStatus::PENDING_VERIFICATION)
return StatementTransactionOwner::getSiblingsOwner()
->where('status', $checkStatus)
->get();
}
private function processRemainingMatch(StatementTransactionOwner $match): void
private function processRemainingMatch(StatementTransactionOwner $match, int $status): void
{
// Find all owners with the same statement transaction ID as the match
$owners = $this->getOwners($match);
$owners = $this->getSiblings($match);
// If there is only one owner (the match itself), approve it
if ($owners->count() === 1) {
$this->updateOwnerStatus($match, ApprovalStatus::APPROVED);
$this->updateOwnerStatus($match, $status);
}
}
@@ -57,13 +57,8 @@ class GroupApproveStatementTransactionLogic extends AbstractControllerLogic
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]
];
$filters = $request->except(['per_page','order_by']);
$statementTransactions = $this->listsBankStatementTransactions->execute($filters);
foreach ($statementTransactions as $statementTransaction) {
@@ -19,6 +19,9 @@ use App\Models\Wallet;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Classes\Modules\Accounting\Services\UpdatesBankStatementTransactionOwnerStatus;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Models\StatementTransactionOwner;
class UpdateBankStatementDetailLogic extends AbstractControllerLogic
{
@@ -40,14 +43,21 @@ class UpdateBankStatementDetailLogic extends AbstractControllerLogic
/** @var ChecksBillNumber */
private $checksBillNumber;
/** @var UpdatesBankStatementTransactionOwnerStatus */
private $updatesBankStatementTransactionOwnerStatus;
/**
* @param FetchesBankStatementTransaction $fetchesBankStatementTransaction
* @param ChecksBillNumber $checksBillNumber
*/
public function __construct(FetchesBankStatementTransaction $fetchesBankStatementTransaction, ChecksBillNumber $checksBillNumber)
public function __construct(
FetchesBankStatementTransaction $fetchesBankStatementTransaction,
ChecksBillNumber $checksBillNumber,
UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus)
{
$this->fetchesBankStatementTransaction = $fetchesBankStatementTransaction;
$this->checksBillNumber = $checksBillNumber;
$this->updatesBankStatementTransactionOwnerStatus = $updatesBankStatementTransactionOwnerStatus;
}
public function logic(Request $request): JsonResponse
@@ -129,7 +139,10 @@ class UpdateBankStatementDetailLogic extends AbstractControllerLogic
$system = $systemReference != null ? SystemType::SYSTEM_NAMES[$systemReference] : '';
$this->createBankStatementTransactionOwner($bankStatementTransaction, $statementTransactionOwnerType, $system, $owner_type, $owner_id, $owner_reference);
$statementTransactionOwner = $this->createBankStatementTransactionOwner($bankStatementTransaction, $statementTransactionOwnerType, $system, $owner_type, $owner_id, $owner_reference);
// this for edit a transaction already mapped
if ($request->has('editMapped')) $this->editAccountMapped($statementTransactionOwner);
return $this->response([]);
}
@@ -144,9 +157,26 @@ class UpdateBankStatementDetailLogic extends AbstractControllerLogic
'owner_reference' => $owner_reference,
];
$bankStatementTransaction->owners()->firstOrCreate($ownerData);
return $bankStatementTransaction->owners()->firstOrCreate($ownerData);
}
private function editAccountMapped(StatementTransactionOwner $owner){
$this->updateOwnerStatus($owner, ApprovalStatus::APPROVED);
// update all other owners with the same system, owner type, and owner ID
$this->updateOtherOwners($owner, ApprovalStatus::REJECTED);
}
private function updateOwnerStatus(StatementTransactionOwner $owner, int $status)
{
$this->updatesBankStatementTransactionOwnerStatus->execute($owner, $status);
}
private function updateOtherOwners(StatementTransactionOwner $owner, int $status): void
{
StatementTransactionOwner::where('statement_transaction_id', $owner->statement_transaction_id)
->where('id', '!=', $owner->id)
->update(['status' => $status]);
}
}
@@ -23,15 +23,12 @@ 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();
public function execute($transactions) {
// $transactions = StatementTransaction::whereDoesntHave('owners')->where('amount', '<', 0)->get();
foreach ($transactions as $transaction) {
$mapped = false;
$keywords = array_filter(explode(" ", $transaction->transaction_description . " " . $transaction->transaction_description_2));
if($transaction->amount > 0){
@@ -39,21 +36,21 @@ class CreateBankStatementTransactionOwnersProcessor
// Exchange Sales
$creditTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::PAYMENT, Booking::class, PaymentMethodType::WALLET, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED], $keywords);
foreach ($creditTransactions as $creditTransaction) {
$transaction->owners()->firstOrCreate([
$isArray = is_array($creditTransaction);
$data = $transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::SALES,
'system' => 'EXCHANGE',
'owner_type' => Transaction::class,
'owner_id'=> $creditTransaction->id,
'owner_reference'=> $creditTransaction->owner->marking,
'owner_id'=> $isArray ? $creditTransaction['owner_id'] : $creditTransaction->id,
'owner_reference'=> $isArray ? $creditTransaction['owner_reference'] : $creditTransaction->owner->marking,
]);
}
// Shipping Portal Sales
$creditTransactions = $this->getTransactionsFromShippingPortal($transaction->amount, $this->getDateRange($transaction->posting_date, 1), 2, PaymentMethodType::WALLET);
$creditTransactions = $this->getTransactionsFromShippingPortal($transaction->amount, $this->getDateRange($transaction->posting_date, 1), [2], PaymentMethodType::WALLET);
foreach ($creditTransactions as $creditTransaction) {
if($creditTransaction['owner_type'] === Wallet::class) continue;
$transaction->owners()->firstOrCreate([
$data = $transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::SALES,
'system' => 'SHIPPING_PORTAL',
'owner_type' => $creditTransaction['owner_type'],
@@ -65,18 +62,19 @@ class CreateBankStatementTransactionOwnersProcessor
// Exchange Wallet Top Up
$creditTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::TOP_UP, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED], $keywords);
foreach ($creditTransactions as $creditTransaction) {
$transaction->owners()->firstOrCreate([
$isArray = is_array($creditTransaction);
$data = $transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::WALLET_TOP_UP,
'system' => 'EXCHANGE',
'owner_type' => Transaction::class,
'owner_id'=> $creditTransaction->id,
'owner_reference'=> $creditTransaction->owner->owner->reference,
'owner_id'=> $isArray ? $creditTransaction['owner_id'] : $creditTransaction->id,
'owner_reference'=> $isArray ? $creditTransaction['owner_reference'] : $creditTransaction->owner->owner->reference,
]);
}
$creditTransactions = $this->getTransactionsFromShippingPortal($transaction->amount, $this->getDateRange($transaction->posting_date, 1), 5, null);
$creditTransactions = $this->getTransactionsFromShippingPortal($transaction->amount, $this->getDateRange($transaction->posting_date, 1), [5,15], null);
foreach ($creditTransactions as $creditTransaction) {
$transaction->owners()->firstOrCreate([
$data = $transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::WALLET_TOP_UP,
'system' => 'SHIPPING_PORTAL',
'owner_type' => $creditTransaction['owner_type'],
@@ -87,7 +85,7 @@ class CreateBankStatementTransactionOwnersProcessor
// fpx charge refund
if($transaction->transaction_description === 'DUITNOW S/CHRG REFUND'){
$transaction->owners()->firstOrCreate([
$data = $transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::FPX_CHARGE_REFUND
]);
}
@@ -96,7 +94,7 @@ class CreateBankStatementTransactionOwnersProcessor
// INTERNAL_BANK_TRANSFER_IN
if(str_contains($transaction->transaction_description_2, 'CIEF WORLDWIDE')){
$transaction->owners()->firstOrCreate([
$data = $transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::INTERNAL_BANK_TRANSFER_IN
]);
}
@@ -121,7 +119,7 @@ class CreateBankStatementTransactionOwnersProcessor
->where('amount', '<=', (($transaction->amount * -1) + 0.01))->whereDate('created_at', '>=', $paymentDateStart)->whereDate('created_at', '<=', $paymentDateEnd)->get();
foreach ($debitTransactions as $debitTransaction) {
$transaction->owners()->firstOrCreate([
$data = $transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::SUPPLIER_PAYMENT,
'system' => 'EXCHANGE',
'owner_type' => Group::class,
@@ -136,12 +134,13 @@ class CreateBankStatementTransactionOwnersProcessor
// Exchange Wallet Withdrawal
$debitTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::DEBIT_NOTE, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED], $keywords);
foreach ($debitTransactions as $debitTransaction) {
$transaction->owners()->firstOrCreate([
$isArray = is_array($creditTransaction);
$data = $transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::WALLET_WITHDRAWAL,
'system' => 'EXCHANGE',
'owner_type' => Transaction::class,
'owner_id'=> $debitTransaction->id,
'owner_reference'=> $debitTransaction->owner->owner->marking,
'owner_id'=> $isArray ? $debitTransaction['owner_id'] : $debitTransaction->id,
'owner_reference'=> $isArray ? $debitTransaction['owner_reference'] : $debitTransaction->owner->owner->marking,
]);
}
@@ -149,46 +148,48 @@ class CreateBankStatementTransactionOwnersProcessor
// 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([
$data = $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([
$data = $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([
$data = $transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::BANK_CHARGE
]);
}
// CREDIT_CARD_PAYMENT
if(str_contains($transaction->transaction_description_2, 'VISA CARD')){
$transaction->owners()->firstOrCreate([
$data = $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([
$data = $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([
$data = $transaction->owners()->firstOrCreate([
'type' => StatementTransactionOwnerType::NON_OPERATIONAL
]);
}
}
if (isset($data) && $data->wasRecentlyCreated) $mapped = true;
$this->updateMappedRate($transaction, $mapped);
}
}
@@ -271,10 +272,8 @@ class CreateBankStatementTransactionOwnersProcessor
return $query->get();
} else {
$result = $this->getTransactionsFromExchange($amount, $dateRange, $type, $ownerType, $paymentMethod);
$transactionsId = Arr::pluck($result, 'owner_id');
$query = $model::whereIn('id', $transactionsId);
return $query->get();
return $result;
}
}
@@ -321,7 +320,7 @@ class CreateBankStatementTransactionOwnersProcessor
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]'.$paymentMethodFilter.',"created_after":"'.$dateRange['start_date'].'","created_before":"'.$dateRange['end_date'].'","amount_exceed":'.($amount - 0.01).',"amount_short":'.($amount + 0.01).',"type_in":['.$type.']}');
$response = $client->request('GET', $url.'?api-key=510acd13d8d24375cf038ad626c282565451461a9c2399357e0b65365300787e&filters={"order_by":{"column":"id","DESC":true},"status_in":[2]'.$paymentMethodFilter.',"created_after":"'.$dateRange['start_date'].'","created_before":"'.$dateRange['end_date'].'","amount_exceed":'.($amount - 0.01).',"amount_short":'.($amount + 0.01).',"type_in":'.json_encode($type).'}');
$body = $response->getBody();
$data = json_decode($body, true);
$payload = $data['payload'];
@@ -348,4 +347,11 @@ class CreateBankStatementTransactionOwnersProcessor
'end_date' => $nextDay,
];
}
private function updateMappedRate($transaction, $mapped) {
$statement = $transaction->statement;
$statement->total_rows = StatementTransaction::where('account_statement_id',$transaction->account_statement_id)->count();
$statement->mapped_rows = $mapped ? $statement->mapped_rows+1 : $statement->mapped_rows;
$statement->save();
}
}
@@ -0,0 +1,57 @@
<?php
namespace App\Classes\Modules\Bookings\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ExpireBookingPaymentControllerLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Expire Payment',
'message' => 'You have successfully expire the Payment'
];
}
/** @var FetchesBooking */
private $fetchesBooking;
/**
* DeletePurchaseOrderPdfLogic constructor.
* @param FetchesBooking $fetchesBooking
*/
public function __construct(fetchesBooking $fetchesBooking)
{
$this->fetchesBooking = $fetchesBooking;
}
/**
* @param Request $request
* @return JsonResponse
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function logic(Request $request) : JsonResponse
{
$booking = $this->fetchesBooking->execute(['id' => $request->route('id')]);
$payment = $booking->transactions()
->payments()->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])
->first();
$payment->status = ApprovalStatus::EXPIRED;
$payment->save();
return $this->response([]);
}
}
@@ -24,7 +24,7 @@ class GeneratesBookingMarking
*/
public function execute(): int {
$marking = mt_rand(20000, 99999);
$marking = mt_rand(100000, 999999);
return !$this->bookingMarkingExists->execute($marking) ? $marking : self::execute();
}
@@ -0,0 +1,94 @@
<?php
namespace App\Classes\Modules\Companies\ControllersLogic;
use App\Classes\ValueObjects\Constants\TransactionType;
use ZipArchive;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Classes\Exceptions\MalformedRequestException;
use Illuminate\Support\Facades\File;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Models\Company;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class BulkDownloadCustomerInvoicesLogic
{
/**
* @param Request $request
* @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function execute(Request $request)
{
try {
$company = Company::where('reference', $request->input('marking'))->first();
if (!$company) {
return response()->json([
'status' => 'Failed',
'message' => 'Customer not found.',
]);
}
$startDate = Carbon::createFromFormat('d-m-Y', $request->input('startDate'))->startOfDay();
$endDate = Carbon::createFromFormat('d-m-Y', $request->input('endDate'))->endOfDay();
$invoicebookings = $company->bookings()
->whereDate('created_at', '>=', $startDate)
->whereDate('created_at', '<=', $endDate)
->whereHas('transactions', function ($query) {
$query->where('transactions.type', TransactionType::INVOICE)
->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
})
->orderBy('created_at')
->get();
if ($invoicebookings->isEmpty()) {
return response()->json([
'status' => 'Failed',
'message' => 'No invoices found for this customer in the given date range.',
]);
}
$zipDirectory = storage_path('app/bulk_invoice'); // Update this with the actual directory path
if (!file_exists($zipDirectory)) {
mkdir($zipDirectory, 0755, true);
}
$zip_file = "{$zipDirectory}/invoices_{$request->input('startDate')}_to_{$request->input('endDate')}_{$company->reference}.zip";
$zip = new ZipArchive();
if ($zip->open($zip_file, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
foreach ($invoicebookings as $booking) {
$invoice_file = $booking->documents()->where('document_type', DocumentType::INVOICE)->whereNull('deleted_at')->first()->files()->first();
$zip->addFile(Storage::disk('documents')->path($invoice_file->file->file_info->original->file), 'invoice-' . $booking->created_at->format('d_m_Y') . '_' . $booking->marking . '.pdf');
}
$zip->close();
while (ob_get_level()) {
ob_end_clean();
}
return response()->download($zip_file);
} else {
return response()->json([
'status' => 'Error',
'message' => 'Failed to create the Zip archive.',
]);
}
} catch (\Exception $e) {
// Log the exception for debugging
Log::error('Error in BulkDownloadCustomerInvoicesLogic: ' . $e->getMessage());
return response()->json([
'status' => 'Error',
'message' => 'An error occurred while processing the request.',
]);
}
}
}
@@ -0,0 +1,78 @@
<?php
namespace App\Classes\Modules\Companies\ControllersLogic;
use ZipArchive;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\ValueObjects\Constants\DocumentType;
use App\Models\Company;
use App\Models\Group;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class BulkDownloadSupplierWhiteFormsLogic
{
/**
* @param Request $request
* @return void
* @throws MalformedRequestException
*/
public function execute(Request $request)
{
try {
$startDate = Carbon::createFromFormat('d-m-Y', $request->input('startDate'))->startOfDay();
$endDate = Carbon::createFromFormat('d-m-Y', $request->input('endDate'))->endOfDay();
$companyReference = Company::find($request->input('supplier'))->reference;
$groups = Group::where('issuer', $request->input('supplier'))
->whereDate('created_at', '>=', $startDate)
->whereDate('created_at', '<=', $endDate)
->orderBy('created_at', 'DESC')
->get();
if (count($groups) > 0) {
$zipDirectory = storage_path('app/bulk_whiteform'); // Update this with the actual directory path
if (!file_exists($zipDirectory)) {
mkdir($zipDirectory, 0755, true);
}
$zip_file = "{$zipDirectory}/currency_vendor_orders_{$request->input('startDate')}_to_{$request->input('endDate')}_{$companyReference}.zip";
$zip = new ZipArchive();
if ($zip->open($zip_file, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE)) {
foreach($groups as $group) {
$document = $group->documents()->where('document_type', DocumentType::CURRENCY_VENDOR_ORDER)->whereNull('deleted_at')->first()->files()->first();
$created_at = $group->created_at->format('Y-m-d');
$zip->addFile(Storage::disk('documents')->path($document->file->file_info->original->file), $created_at . "_" . $group->amount . "_" . $group->reference . '.pdf');
}
$zip->close();
while (ob_get_level()) {
ob_end_clean();
}
return response()->download($zip_file);
}
}
return response()->json([
'status' => 'Failed',
'message' => 'No white form found for this supplier in the given date range.',
]);
} catch (\Exception $e) {
// Log the exception for debugging
Log::error('Error in BulkDownloadSupplierWhiteFormsLogic: ' . $e->getMessage());
return response()->json([
'status' => 'Error',
'message' => 'An error occurred while processing the request.',
]);
}
}
}
@@ -0,0 +1,107 @@
<?php
namespace App\Classes\Modules\Exports\Services;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\Transaction;
use App\Models\Company;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class ExportsCustomersWalletTransactionHistory implements FromQuery, WithHeadings, WithHeadingRow, WithMapping, ShouldAutoSize
{
use Exportable;
private $request;
private $runningBalance = 0;
public function __construct(Request $request)
{
$this->request = $request;
}
public function headings(): array
{
return [
'Date',
'Description',
'Incoming',
'Outgoing',
'Balance',
];
}
/**
* @return \Illuminate\Support\Collection|mixed
*/
public function query()
{
$company = Company::where('reference', $this->request->route('marking'))->first();
$transactions = $company->wallets()->first()->transactions()->whereIn('transactions.status', [2, 3])->orderBy('id');
// dd($transactions->get()->toArray());
return $transactions;
}
/**
* @param Transaction $transaction
*
* @return array
*/
public function map($transaction): array
{
// dd($transaction);
$decimals = $this->request->route('is_precise') == 'true' ? 5 : 2;
$description = '';
switch ((int) $transaction->type) {
case 5:
$description = (float) $transaction->amount . ' Credit Top up';
break;
case 9:
$description = 'Credit Voucher for ' . $transaction->payment_reference;
break;
case 1:
$booking = Transaction::where('payment_reference', $transaction->bill_no)->first()->owner;
if (!$booking) {
$description = 'Payment for unknown booking, please contact tech support.';
break;
}
$marking = $booking->marking;
$description = 'Payment For booking refs' . $marking;
break;
case 11:
$description = 'Debit Voucher for ' . $transaction->payment_reference;
break;
}
$incoming = $outgoing = '';
if (in_array($transaction->type, [TransactionType::TOP_UP, TransactionType::CREDIT_NOTE])) {
$incoming = number_format($transaction->amount, $decimals, '.', ',');
$this->runningBalance += $transaction->amount;
}
if (in_array($transaction->type, [TransactionType::PAYMENT, TransactionType::DEBIT_NOTE])) {
$outgoing = number_format($transaction->amount, $decimals, '.', ',');
$this->runningBalance -= $transaction->amount;
}
return [
Carbon::parse($transaction->created_at)->format('d-m-Y h:i:s A'),
$description,
$incoming,
$outgoing,
number_format($this->runningBalance, $decimals, '.', ',')
];
}
}
@@ -0,0 +1,78 @@
<?php
namespace App\Classes\Modules\Exports\Services;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use App\Models\TransactionMappingLog;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
class ExportsImportedInvoiceMappeds implements FromQuery, WithHeadings, WithHeadingRow, WithMapping, ShouldAutoSize
{
use Exportable;
private $dateTime;
private $count;
private $counter = 1;
public function __construct(Request $request)
{
$this->dateTime = $request->input('date').' '.$request->input('time');
$this->count = 0;
}
public function headings(): array
{
return [
'No',
'Doc No',
'Date',
'Debtor Code',
'Debtor Name',
'Shipping Info',
'Net Total',
'Cancelled',
'Mapped Status',
'Mapped Reference No'
];
}
/**
* @return \Illuminate\Support\Collection|mixed
*/
public function query()
{
return TransactionMappingLog::where('imported_date', $this->dateTime);
}
/**
* @param Transaction $transaction
*
* @return array
*/
public function map($transaction): array
{
// dd($transaction);
$this->count += 1;
$data = $transaction->data;
return [
$this->count,
Arr::get($data,'doc_no'),
Arr::get($data,'date'),
Arr::get($data,'debtor_code'),
Arr::get($data,'debtor_name'),
Arr::get($data,'shipping_info'),
Arr::get($data,'net_total'),
Arr::get($data,'cancelled'),
Arr::get($data,'mapped_status'),
Arr::get($data,'mapped_result_reference'),
];
}
}
@@ -4,7 +4,6 @@ namespace App\Classes\Modules\Exports\Services;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\StatementTransactionOwnerType;
use App\Models\StatementTransactionOwner;
use App\Models\Transaction;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromQuery;
@@ -18,6 +17,8 @@ use App\Classes\Modules\Accounting\Processors\ListShippingPortalTransactions;
use App\Classes\ValueObjects\Constants\ShippingTransactionType;
use App\Classes\ValueObjects\Constants\TransactionType;
use Illuminate\Support\Facades\Log;
use App\Classes\General\Eloquent\ApplyFiltersToQuery;
use App\Models\StatementTransaction;
class ExportsInvoiceTransactions implements FromQuery, WithHeadings, WithHeadingRow, WithMapping, ShouldAutoSize
{
@@ -33,7 +34,7 @@ class ExportsInvoiceTransactions implements FromQuery, WithHeadings, WithHeading
public function headings(): array
{
return [
$header = [
'DocNo',
'DocDate',
'DebtorCode',
@@ -49,6 +50,7 @@ class ExportsInvoiceTransactions implements FromQuery, WithHeadings, WithHeading
'AccNo',
'DeptNo'
];
return $header;
}
/**
@@ -56,9 +58,10 @@ class ExportsInvoiceTransactions implements FromQuery, WithHeadings, WithHeading
*/
public function query()
{
return StatementTransactionOwner::whereNull('invoice_reference')
->whereIn('type', [StatementTransactionOwnerType::SALES, StatementTransactionOwnerType::WALLET_TOP_UP])
->whereIn('status', [ApprovalStatus::COMPLETED, ApprovalStatus::APPROVED]);
$data = (new ApplyFiltersToQuery())->execute(StatementTransaction::query(), json_decode($this->request->input('filter'), true));
if ($this->request->has('bankStatementTransactionId')) $data = $data->whereIn('id',json_decode($this->request->input('bankStatementTransactionId'), true));
return $data;
}
/**
@@ -68,11 +71,12 @@ class ExportsInvoiceTransactions implements FromQuery, WithHeadings, WithHeading
*/
public function map($transaction): array
{
$statementTransactionOwner = $transaction->owners()->whereIn('status', [ApprovalStatus::APPROVED])->first();
$logArray = [
'counter' => $this->counter,
'system' => $transaction->system,
'StatementTransactionOwner_id' => $transaction->id,
'transaction_table_id' => $transaction->owner_id,
'system' => $statementTransactionOwner->system,
'StatementTransactionOwner_id' => $statementTransactionOwner->id,
'transaction_table_id' => $statementTransactionOwner->owner_id,
];
$this->counter += 1;
$logArray = json_encode($logArray);
@@ -82,8 +86,8 @@ class ExportsInvoiceTransactions implements FromQuery, WithHeadings, WithHeading
$textToAppend = Carbon::now()->format('[Y-m-d H:i:s]') . ' ' . $logArray . PHP_EOL;
file_put_contents($filePath, $textToAppend, FILE_APPEND);
if ($transaction->system == 'EXCHANGE') {
$row = (App()->make($transaction->owner_type))->where('id', $transaction->owner_id)->first();
if ($statementTransactionOwner->system == 'EXCHANGE') {
$row = (App()->make($statementTransactionOwner->owner_type))->where('id', $statementTransactionOwner->owner_id)->first();
$company = $row->type === TransactionType::PAYMENT ? $row->owner->company : $row->owner->owner;
$booking = $row->owner;
@@ -106,15 +110,15 @@ class ExportsInvoiceTransactions implements FromQuery, WithHeadings, WithHeading
];
} else {
$row = (App()->make(ListShippingPortalTransactions::class))->execute([
'id' => $transaction->owner_id,
'id' => $statementTransactionOwner->owner_id,
'with_company' => true,
]);
if (empty($row) || $row[0]['status'] != 'success') {
$textToAppend = Carbon::now()->format('[Y-m-d H:i:s]') . ' Fetch Shipping Transaction Fail ' . json_encode([
'id' => $transaction->owner_id,
'id' => $statementTransactionOwner->owner_id,
'with_company' => true,
'StatementTransactionOwner_id' => $transaction->id,
'StatementTransactionOwner_id' => $statementTransactionOwner->id,
]) . PHP_EOL;
file_put_contents($errorFilePath, $textToAppend, FILE_APPEND);
@@ -123,7 +127,24 @@ class ExportsInvoiceTransactions implements FromQuery, WithHeadings, WithHeading
Log::info('Error in Exports Invoice Transactions ' . $this->counter);
return [];
return [
'Transaction Not Found',
$transaction->posting_date->format('m/d/Y H:m'),
$transaction->transaction_description.' - '.$transaction->transaction_description_2,
$statementTransactionOwner->system,
'',
'',
'',
'',
'',
'',
0,
$transaction->amount,
'',
'',
'',
''
];
}
$row = $row[0];
@@ -0,0 +1,132 @@
<?php
namespace App\Classes\Modules\Exports\Services;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Illuminate\Http\Request;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use App\Classes\General\Eloquent\ApplyFiltersToQuery;
use App\Models\StatementTransaction;
use App\Models\Company;
class ExportsReceiptTransactions implements FromQuery, WithHeadings, WithHeadingRow, WithMapping, ShouldAutoSize
{
use Exportable;
private $request;
private $counter = 1;
public function __construct(Request $request)
{
$this->request = $request;
}
public function headings(): array
{
$header = [
'DocNo',
'DocDate',
'DebtorCode',
'Description',
'DocNo2',
'ProjNo',
'DeptNo',
'CurrencyCode',
'ToHomeRate',
'ToDebtorRate',
'Note',
'PaymentMethod',
'ChequeNo',
'PaymentAmt',
'BankCharge',
'ToBankRate',
'BankChargeTaxType',
'BankChargeTaxRefNo',
'BankChargeProjNo',
'BankChargeDeptNo',
'PaymentBy',
'FloatDay',
'IsRCHQ',
'RCHQDate',
'KnockOffDocType',
'KnockOffDocNo',
'KnockOffAmt',
'',
];
return $header;
}
/**
* @return \Illuminate\Support\Collection|mixed
*/
public function query()
{
$data = (new ApplyFiltersToQuery())->execute(StatementTransaction::query(), json_decode($this->request->input('filter'), true));
if ($this->request->has('bankStatementTransactionId')) $data = $data->whereIn('id',json_decode($this->request->input('bankStatementTransactionId'), true));
return $data;
}
/**
* @param StatementTransaction $transaction
*
* @return array
*/
public function map($transaction): array
{
$statementTransactionOwner = $transaction->owners()->whereIn('status', [ApprovalStatus::APPROVED])->first();
$logArray = [
'counter' => $this->counter,
'system' => $statementTransactionOwner->system,
'StatementTransactionOwner_id' => $statementTransactionOwner->id,
'transaction_table_id' => $statementTransactionOwner->owner_id,
];
$this->counter += 1;
$logArray = json_encode($logArray);
$filePath = storage_path('logs/exports_receipt_transactions.log');
$errorFilePath = storage_path('logs/exports_receipt_transactions_error.log');
$textToAppend = Carbon::now()->format('[Y-m-d H:i:s]') . ' ' . $logArray . PHP_EOL;
file_put_contents($filePath, $textToAppend, FILE_APPEND);
$company = Company::where('name',$transaction->transaction_description_2)->first();
return [
'<<New>>',
Carbon::parse($transaction->posting_date)->format('d/m/Y'),
($company ? $company->debtor : null),
$transaction->transaction_description,
'',
'',
'',
'MYR',
1,
1,
'',
'MBB',
'',
$transaction->amount,
'',
1,
'',
'',
'',
'',
'',
'0',
'',
'',
'RI',
$transaction->transaction_ref,
$transaction->amount,
'',
];
}
}
@@ -0,0 +1,111 @@
<?php
namespace App\Classes\Modules\Transactions\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Bookings\Services\FetchesBooking;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\Modules\Transactions\Processors\CreatePurchaseOrderTransactionProcessor;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Http\Resources\TransactionResource;
use App\Models\Booking;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Facades\Excel;
class ImportPurchaseOrderTransactionLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Update Purchase Order',
'message' => 'You have successfully updated you booking\'s purchase order'
];
}
/** @var FetchesBooking */
private $fetchesBooking;
/** @var GeneratesTransactionBillNumber */
private $generatesTransactionBillNumber;
/** @var CreatePurchaseOrderTransactionProcessor */
private $createPurchaseOrderTransactionProcessor;
/**
* CreatePurchaseOrderTransactionLogic constructor.
* @param FetchesBooking $fetchesBooking
* @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
* @param CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor
*/
public function __construct(FetchesBooking $fetchesBooking, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatePurchaseOrderTransactionProcessor $createPurchaseOrderTransactionProcessor)
{
$this->fetchesBooking = $fetchesBooking;
$this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
$this->createPurchaseOrderTransactionProcessor = $createPurchaseOrderTransactionProcessor;
}
/**
* @param Request $request
* @param string $id
* @return JsonResponse
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function logic(Request $request, $id = '') : 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);
$products = $sheet->map(function ($row) {
Log::info($row);
$stockCode = $row[0];
$description = $row[1];
$quantity = $row[2];
$unit_price = $row[3];
return [
'stockCode' => $stockCode,
'description' => $description,
'quantity' => $quantity,
'unit_price' => $unit_price
];
})->all();
}
// dd($products);
/** @var Booking $booking */
$booking = $this->fetchesBooking->execute(['id' => $request->route('id') ?? $id]);
$billNumber = $this->generatesTransactionBillNumber->execute('PO-');
$total = collect($products)->sum(function($product){
return $product['quantity'] * floatval(str_replace(',', '', $product['unit_price']));
});
$object = new TransactionObject($billNumber, TransactionType::PURCHASE_ORDER, $booking->company->id, 1,
1, PaymentMethodType::CASH,
$total, $total, $booking->fix_currency_id, $booking->fix_currency_id,
1, 0, 0, null, ApprovalStatus::PENDING_SUBMISSION, $products);
$transaction = $this->createPurchaseOrderTransactionProcessor->execute($booking, $object);
return $this->resourceResponse(new TransactionResource($transaction));
}
}
@@ -5,8 +5,12 @@ namespace App\Classes\Modules\Transactions\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Transactions\Services\ListsTransactions;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Http\Resources\BookingResource;
use App\Http\Resources\WalletTransactionResource ;
use App\Models\Transaction;
use App\Models\Wallet;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -39,6 +43,36 @@ class ListWalletTransactionsLogic extends AbstractControllerLogic
$query = $this->listsTransactions->execute($this->listsTransactions->deserializeFilters($request->input('filters')));
if (str_contains($request->input('filters'), "owner_id") && $query->count() > 0) {
$wallet_total_incoming = Transaction::where('owner_type', $query->first()->owner_type)
->where('owner_id', $query->first()->owner_id)
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])
->whereIn('type', [TransactionType::TOP_UP, TransactionType::CREDIT_NOTE])
->sum('amount');
$wallet_total_outgoing = Transaction::where('owner_type', $query->first()->owner_type)
->where('owner_id', $query->first()->owner_id)
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])
->whereIn('type', [TransactionType::PAYMENT, TransactionType::DEBIT_NOTE])
->sum('amount');
$currentWalletBalance = $wallet_total_incoming - $wallet_total_outgoing;
$incoming = Transaction::where('owner_type', $query->first()->owner_type)
->where('owner_id', $query->first()->owner_id)
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])
->whereIn('type', [TransactionType::TOP_UP, TransactionType::CREDIT_NOTE])
->where('id', '>', $query->first()->id)
->sum('amount');
$outgoing = Transaction::where('owner_type', $query->first()->owner_type)
->where('owner_id', $query->first()->owner_id)
->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])
->whereIn('type', [TransactionType::PAYMENT, TransactionType::DEBIT_NOTE])
->where('id', '>', $query->first()->id)
->sum('amount');
$runningBalanceInReverse = $currentWalletBalance - $incoming + $outgoing;
$request['running_balance'] = $runningBalanceInReverse;
}
return $this->collectionResponse(WalletTransactionResource::collection($query));
}
@@ -34,7 +34,8 @@ class GeneratesTransactionBillNumber
$attempt = 0;
while ($attempt < 10) { // Retry up to 10 times
$billNumber = $prefix . $date->format('Y') . $date->format('m') . '-' . microtime(true);
// $billNumber = $prefix . $date->format('Y') . $date->format('m') . '-' . intval(microtime(true));
$billNumber = $prefix . $date->format('Y') . $date->format('m') . '-' . mt_rand(1000000, 9999999);
if (!$this->checksIfTransactionBillNumberExists->execute($billNumber)) {
return $billNumber;
}
@@ -8,9 +8,21 @@ final class SystemType {
public const SHIPPING_PORTAL = 'SHIPPING_PORTAL';
public const CNTR = 'CNTR';
public const LITE = 'LITE';
public const PROBASHI = 'PROBASHI';
public const PETS = 'PETS';
public const SYSTEM_NAMES = [
'exchange' => self::EXCHANGE,
'shipping_portal' => self::SHIPPING_PORTAL,
'izyim' => self::SHIPPING_PORTAL,
'lite' => self::LITE,
'cntr' => self::CNTR,
'probashi' => self::PROBASHI,
'pets' => self::PETS
];
}
@@ -0,0 +1,44 @@
<?php
namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
class DeleteBulkInvoiceFiles extends Command
{
protected $signature = 'delete:bulk-download-files';
protected $description = 'Delete all the bulk download files';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$directories = [
storage_path('app/bulk_invoice'),
storage_path('app/bulk_whiteform'),
];
foreach ($directories as $directory) {
$start = new Carbon();
$this->info(Carbon::now() . ' Start cleaning - ' . $directory);
if (File::isDirectory($directory)) {
File::cleanDirectory($directory);
$this->info('All files have been deleted.');
} else {
$this->info('Directory does not exist.');
}
$end = new Carbon();
$elapsedTime = $start->diff($end)->format('%H:%I:%S');
$this->info(Carbon::now() . ' Process ended. ElapsedTime: ' . $elapsedTime);
}
}
}
+3 -3
View File
@@ -39,9 +39,9 @@ class Kernel extends ConsoleKernel
->appendOutputTo(storage_path().'/logs/soft-delete-seasonal-segmant-company.log')
->withoutOverlapping();
$schedule->command('regenerateInvoice')
->everyMinute()
->appendOutputTo(storage_path().'/logs/regenerateInvoice.log')
$schedule->command('delete:bulk-download-files')
->hourly()
->appendOutputTo(storage_path().'/logs/delete-bulk-download-files.log')
->withoutOverlapping();
}
@@ -22,6 +22,7 @@ use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use DateTime;
use App\Models\StatementTransaction;
class BankStatementController extends Controller
{
@@ -88,7 +89,9 @@ class BankStatementController extends Controller
public function rerun()
{
CreateBankStatementTransactionOwners::dispatch();
foreach (StatementTransaction::doesntMapStatement()->get()->chunk(30) as $key => $transaction) {
CreateBankStatementTransactionOwners::dispatch($transaction);
}
return redirect()->back()->with('success', 'Rerun triggered successfully');
}
@@ -129,7 +132,7 @@ class BankStatementController extends Controller
->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
public function fetch(Request $request, ListBankStatementTransactionsLogic $logic): JsonResponse
{
return $logic->execute($request);
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Bookings;
use App\Classes\Modules\Bookings\ControllersLogic\ExpireBookingPaymentControllerLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ExpireBookingPaymentController
{
/**
* @param Request $request
* @param DeleteBookingPaymentControllerLogic $logic
* @return JsonResponse
*/
public function expire(Request $request, ExpireBookingPaymentControllerLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Companies;
use App\Classes\Modules\Companies\ControllersLogic\BulkDownloadCustomerInvoicesLogic;
use Illuminate\Http\Request;
class BulkDownloadCustomerInvoicesController
{
/**
* @param Request $request
* @param BulkDownloadCustomerInvoicesLogic $logic
* @return void
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function download(Request $request, BulkDownloadCustomerInvoicesLogic $logic) {
return $logic->execute($request);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Companies;
use App\Classes\Modules\Companies\ControllersLogic\BulkDownloadSupplierWhiteFormsLogic;
use Illuminate\Http\Request;
class BulkDownloadSupplierWhiteFormsController
{
/**
* @param Request $request
* @param BulkDownloadSupplierWhiteFormsLogic $logic
* @return void
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function download(Request $request, BulkDownloadSupplierWhiteFormsLogic $logic) {
return $logic->execute($request);
}
}
@@ -15,6 +15,9 @@ use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Maatwebsite\Excel\Excel;
use App\Classes\Modules\Exports\Services\ExportsImportedInvoiceMappeds;
use App\Models\TransactionMappingLog;
use App\Classes\Modules\Exports\Services\ExportsReceiptTransactions;
class ExportCustomersToExcelController
{
@@ -64,6 +67,13 @@ class ExportCustomersToExcelController
return $response;
}
public function receiptTransactions(Request $request){
$exportsTransactions = new ExportsReceiptTransactions($request);
$response = $exportsTransactions->download('receipt-transactions.xls', Excel::XLS, ['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']);
ob_end_clean();
return $response;
}
public function bookingTransactions(ExportsBookingTransactions $exportsBookingTransactions, Request $request){
$response = $exportsBookingTransactions->download('bookingTransactions.xls', Excel::XLS, ['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']);
ob_end_clean();
@@ -75,4 +85,10 @@ class ExportCustomersToExcelController
ob_end_clean();
return $response;
}
public function importedInvoiceMapped(ExportsImportedInvoiceMappeds $exportsImportedInvoiceMappeds, Request $request) {
$response = $exportsImportedInvoiceMappeds->download($request->input('fileName').'.xls', Excel::XLS, ['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']);
ob_end_clean();
return $response;
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Exports;
use App\Classes\Modules\Exports\Services\ExportsCustomersWalletTransactionHistory;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Maatwebsite\Excel\Excel;
class ExportCustomersWalletTransactionToExcelController
{
/**
* ExportCustomersWalletTransactionToExcelController constructor.
* @param Request $request
*/
public function __construct(Request $request)
{
$token = Auth::fromUser(User::find(1));
$request->headers->set('Authorization', 'Bearer ' . $token);
}
public function export(Request $request)
{
$exportsTransactions = new ExportsCustomersWalletTransactionHistory($request);
$filename = $request->route('marking') . '-wallet-' . ($request->route('is_precise') == 'true' ? 'precise-' : '') . 'transaction-history.xls';
$response = $exportsTransactions->download($filename, Excel::XLS, ['Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']);
ob_end_clean();
return $response;
}
}
@@ -2,33 +2,47 @@
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 Carbon\Carbon;
use App\Models\User;
use App\Models\Company;
use App\Models\Segment;
use App\Models\Transaction;
use Illuminate\Http\Request;
use App\Models\SeasonalSegment;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
use App\Models\TransactionMappingLog;
use App\Classes\ValueObjects\Constants\HttpStatus;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\Modules\Imports\Services\GenericImport;
use App\Classes\ValueObjects\Response\ApiResponseObject;
use App\Classes\Modules\Accounting\Processors\ChecksBillNumber;
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;
use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject;
use App\Classes\Modules\Segments\DataTransferObjects\SeasonalSegmentObject;
class ImportStatementInvoiceController
{
private $responseTitle;
private $responseMessage;
public function __construct() {
$this->responseTitle = 'Import Invoice Mapping';
$this->responseMessage = 'You have successfully imported invoice mapping';
}
/**
* @param Request $request
* @return array
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function import(Request $request)
public function import(Request $request) : JsonResponse
{
ini_set('memory_limit', '-1');
$importDate = date('Y-m-d H:i:s');
$object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
$file = json_decode($object->getFiles()[0])->file_info->original->file;
@@ -37,26 +51,37 @@ class ImportStatementInvoiceController
$excelRows = $import->rows;
$excelRows = $excelRows->toArray();
$data = [];
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
// }
$row['mapped_result_reference'] = null;
$row['mapped_status'] = 'failed';
$row['date'] = date('Y-m-d', strtotime($row['date']));
// 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');
foreach (['exchange','izyim'] as $system) {
$returnReference = $this->mappingTopUp($row, $system);
if ($returnReference) {
$row['mapped_result_reference'] = $returnReference;
$row['mapped_status'] = 'success';
}
}
} else {
$returnReference = $this->mappingExchange($row);
if ($returnReference) {
$row['mapped_result_reference'] = $returnReference;
$row['mapped_status'] = 'success';
}
}
TransactionMappingLog::create([
'imported_date'=>$importDate,
'data'=>$row,
]);
array_push($data, $row);
// if 5 digits -> exchange booking reference
// find transation
@@ -88,12 +113,53 @@ class ImportStatementInvoiceController
}
return $this->response(['data'=>$data,'importedDate'=>$importDate]);
}
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'
public function response(?array $data = []) : JsonResponse {
return (new ApiResponseObject($this->responseTitle,
$this->responseMessage,
HttpStatus::OK_WITH_MESSAGE, $data))->handler();
}
private function mappingTopUp(Array $row, String $system) {
try {
if ($data = (App()->make(ChecksBillNumber::class))->execute($row['shipping_info'], $system)) {
if ($system == 'izyim' && isset($data['owner_reference'])) return $data['owner_reference'];
if ($system == 'exchange') return $this->updateTransactionOwnerReference($data, $row['doc_no']);
}
} catch (\Throwable $th) {
return false;
}
}
private function mappingExchange(Array $row) {
$date = $row['date'];
$transactions = Transaction::getReceiverWithJoinStatementTransactionAndOwner($row)->select('transactions.*')->where('statement_transactions.amount', $row['net_total'])->whereRaw("DATE(posting_date) = '$date'")->get();
if ($transactions && $transactions->count() == 0) {
$transactions = Transaction::getReceiverWithJoinStatementTransactionAndOwner($row)->select('transactions.*')->where(DB::raw('FLOOR(statement_transactions.amount)'), floor($row['net_total']))->whereRaw("DATE(posting_date) = '$date'")->get();
}
if ($transactions && $transactions->count() == 1) {
foreach ($transactions as $key => $transaction) {
return $this->updateTransactionOwnerReference($transaction, $row['doc_no']);
}
}
return false;
}
public function updateTransactionOwnerReference($transaction, String $docNo) {
$transactionOwner = $transaction->transaction_owner;
if ($transactionOwner) {
$transactionOwner->update([
'invoice_reference'=>$docNo,
'status'=>ApprovalStatus::COMPLETED
]);
return $transactionOwner->owner_reference;
}
return false;
}
}
@@ -17,9 +17,22 @@ use App\Classes\Modules\Companies\Processors\AssignSegmentProcessor;
use App\Models\Company;
use App\Models\SeasonalSegment;
use App\Models\Transaction;
use App\Models\TransactionMappingLog;
use App\Classes\ValueObjects\Response\ApiResponseObject;
use App\Classes\ValueObjects\Constants\HttpStatus;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
class ImportStatementReceiptsController
{
private $responseTitle;
private $responseMessage;
public function __construct() {
$this->responseTitle = 'Import Receipt Mapping';
$this->responseMessage = 'You have successfully imported receipt mapping';
}
/**
* @param Request $request
* @return array
@@ -27,6 +40,7 @@ class ImportStatementReceiptsController
*/
public function import(Request $request)
{
$importDate = date('Y-m-d H:i:s');
$object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports');
$file = json_decode($object->getFiles()[0])->file_info->original->file;
@@ -35,16 +49,59 @@ class ImportStatementReceiptsController
$excelRows = $import->rows;
$excelRows = $excelRows->toArray();
$data = [];
foreach ($excelRows as $row) {
// if has date column
// $transactionDate = $this->changeExcelDate($row['date']);
}
$row['mapped_result_reference'] = null;
$row['mapped_status'] = 'failed';
$row['date'] = date('Y-m-d', strtotime($row['doc_date']));
$returnReference = $this->mappingExchange($row);
if ($returnReference) {
$row['mapped_result_reference'] = $returnReference;
$row['mapped_status'] = 'success';
}
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'
TransactionMappingLog::create([
'imported_date'=>$importDate,
'data'=>$row,
]);
array_push($data, $row);
}
return $this->response(['data'=>$data,'importedDate'=>$importDate]);
}
public function response(?array $data = []) : JsonResponse {
return (new ApiResponseObject($this->responseTitle,
$this->responseMessage,
HttpStatus::OK_WITH_MESSAGE, $data))->handler();
}
private function mappingExchange(Array $row) {
$date = $row['date'];
$transactions = Transaction::getReceiverWithJoinStatementTransactionAndOwner($row)->select('transactions.*')->where('statement_transactions.amount', $row['payment_amount'])->whereRaw("DATE(posting_date) = '$date'")->get();
if ($transactions && $transactions->count() == 0) {
$transactions = Transaction::getReceiverWithJoinStatementTransactionAndOwner($row)->select('transactions.*')->where(DB::raw('FLOOR(statement_transactions.amount)'), floor($row['payment_amount']))->whereRaw("DATE(posting_date) = '$date'")->get();
}
if ($transactions && $transactions->count() == 1) {
foreach ($transactions as $key => $transaction) {
return $this->updateTransactionOwnerReference($transaction, $row['doc_no']);
}
}
return false;
}
public function updateTransactionOwnerReference($transaction, String $docNo) {
$transactionOwner = $transaction->transaction_owner;
if ($transactionOwner) {
$transactionOwner->update([
'receipt_reference'=>$docNo,
'status'=>ApprovalStatus::COMPLETED
]);
return $transactionOwner->owner_reference;
}
return false;
}
}
@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Transactions;
use App\Classes\Modules\Transactions\ControllersLogic\ImportPurchaseOrderTransactionLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ImportPurchaseOrderTransactionController
{
/**
* @param Request $request
* @param ImportPurchaseOrderTransactionLogic $logic
* @return JsonResponse
*/
public function import(Request $request, ImportPurchaseOrderTransactionLogic $logic) : JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,35 @@
<?php
namespace App\Http\Resources;
use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;
class BankStatementDetailResource extends JsonResource
{
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
$transaction = $this->transaction;
$accountStatement = $transaction->statement;
return [
'id' => $this->id,
'date' => Carbon::parse($transaction->posting_date)->format('Y-m-d'),
'transaction_description_1' => $transaction->transaction_description,
'transaction_description_2' => $transaction->transaction_description_2,
'transaction_description_3' => $transaction->transaction_description_3,
'transaction_description_4' => $transaction->transaction_description_4,
'transaction_description_5' => $transaction->transaction_description_5,
'pay_for' => $transaction->transaction_description_2,
'system_references' => $this->system,
'amount' => $transaction->amount,
'account_statement_date_from' => Carbon::parse($accountStatement->date_from)->format('Y-m-d'),
'account_statement_date_to' => Carbon::parse($accountStatement->date_to)->format('Y-m-d'),
];
}
}
@@ -28,7 +28,7 @@ class BankStatementTransactionOwnerResource extends JsonResource
}
if($this->type === StatementTransactionOwnerType::WALLET_TOP_UP){
$referenceLink = route('booking.details', $this->owner_reference);
$referenceLink = route('wallet.details', $this->owner_reference);
}
}
}
@@ -16,6 +16,7 @@ class TransactionDetailResource extends JsonResource
{
return [
'id' => $this->id,
'stockCode' => $this->product_code,
'description' => $this->product_name,
'quantity' => $this->quantity,
+1 -1
View File
@@ -21,7 +21,7 @@ class WalletResource extends JsonResource
'currency_id' => $this->currency_id,
'amount' => (double) $this->amount,
'company_id' => (int) $this->owner->id,
'transactions' => $this->whenLoaded('transactions', WalletTransactionResource::collection($this->transactions()->whereIn('status', [2, 3])->orderBy('id', 'DESC')->get()), []),
// 'transactions' => $this->whenLoaded('transactions', WalletTransactionResource::collection($this->transactions()->whereIn('status', [2, 3])->orderBy('id', 'DESC')->get()), []),
'top_up_records' => $this->whenLoaded('transactions', WalletTransactionResource::collection($this->transactions()->whereNotIn('status', [0])->where('type', TransactionType::TOP_UP)->orderBy('id', 'DESC')->get()), []),
];
}
@@ -20,12 +20,15 @@ class WalletTransactionResource extends JsonResource
public function toArray($request)
{
$description = '';
$current_running_balance = $request['running_balance'];
switch((int) $this->type){
case 5:
$description = (double) $this->amount.' Credit Top up';
$request['running_balance'] = bcsub($request['running_balance'], $this->amount, 5);
break;
case 9:
$description = 'Credit Voucher for '.$this->payment_reference;
$request['running_balance'] = bcsub($request['running_balance'], $this->amount, 5);
break;
case 1:
$booking = Transaction::where('payment_reference', $this->bill_no)->first()->owner;
@@ -35,11 +38,13 @@ class WalletTransactionResource extends JsonResource
break;
}
$request['running_balance'] = bcadd($request['running_balance'], $this->amount, 5);
$marking = $booking->marking;
$description = 'Payment For booking refs.'.'<a href="'.route('booking.details', $marking).'">'.$marking.'</a>';
break;
case 11:
$description = 'Debit Voucher for '.$this->payment_reference;
$request['running_balance'] = bcadd($request['running_balance'], $this->amount, 5);
break;
}
@@ -53,6 +58,7 @@ class WalletTransactionResource extends JsonResource
'payment_method' => (float) $this->payment_method,
'issuer_name' => $this->issuerCompany->name,
'amount' => (double) $this->amount,
'running_balance' => (double) $current_running_balance,
'service_charge' => (double) $this->service_charge,
'tax' => (double) $this->tax,
'status' => (int) $this->status,
+2
View File
@@ -16,6 +16,8 @@ class AccountStatement extends Model
'total_amount',
'begin_balance',
'end_balance',
'total_rows',
'mapped_rows',
];
protected $casts = [
+13
View File
@@ -5,6 +5,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
class StatementTransaction extends Model
{
@@ -47,4 +48,16 @@ class StatementTransaction extends Model
{
return $this->hasMany(StatementTransactionOwner::class);
}
public function owner_status()
{
return $this->owners()->whereIn('status',[ApprovalStatus::APPROVED,ApprovalStatus::COMPLETED,ApprovalStatus::PENDING_VERIFICATION]);
}
public function scopeDoesntMapStatement($query)
{
$query->whereDoesntHave('owners', function($query){
return $query->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
})->orderBy('posting_date');
}
}
+12
View File
@@ -5,6 +5,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class StatementTransactionOwner extends Model
{
@@ -22,8 +23,19 @@ class StatementTransactionOwner extends Model
'status',
];
public function owner(): morphTo
{
return $this->morphTo();
}
public function transaction(): BelongsTo
{
return $this->belongsTo(StatementTransaction::class, 'statement_transaction_id', 'id');
}
public function scopeGetSiblingsOwner($query) {
$query->where('system', $this->system)
->where('owner_type', $this->owner_type)
->where('owner_id', $this->owner_id);
}
}
+35
View File
@@ -18,6 +18,7 @@ use Illuminate\Database\Eloquent\Relations\HasOneThrough;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Staudenmeir\EloquentHasManyDeep\HasTableAlias;
use App\Models\StatementTransactionOwner;
class Transaction extends AbstractModel implements Documentable, Transactionable, Voucherifiable
@@ -53,6 +54,14 @@ class Transaction extends AbstractModel implements Documentable, Transactionable
return $this->MorphOne(Transaction::class, 'owner')->where('type', TransactionType::CREDIT_NOTE);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function transaction_owner()
{
return $this->MorphOne(StatementTransactionOwner::class, 'owner','owner_type','owner_id');
}
/**
* @return BelongsTo
*/
@@ -69,6 +78,16 @@ class Transaction extends AbstractModel implements Documentable, Transactionable
return $this->BelongsTo( Company::class, 'issuer', 'id');
}
/**
* Get the user that owns the Transaction
*
* @return BelongsTo
*/
public function receiverCompany(): BelongsTo
{
return $this->belongsTo(Company::class, 'receiver', 'id');
}
/**
* @return BelongsTo
*/
@@ -202,6 +221,22 @@ class Transaction extends AbstractModel implements Documentable, Transactionable
return $query->whereIn('status', [ApprovalStatus::APPROVED]);
}
/**
* @param Builder $query
* @return Builder
*/
public function scopeGetReceiverWithJoinStatementTransactionAndOwner(Builder $query, Array $row) {
$query->join('companies',function($q) use ($row) {
$q->on('companies.id','=','transactions.receiver');
$q->where('debtor',$row['debtor_code']);
})
->join('statement_transaction_owners', function ($q) {
$q->on('statement_transaction_owners.owner_id','=','transactions.id');
$q->where('statement_transaction_owners.owner_type','=',Transaction::class);
})
->join('statement_transactions','statement_transactions.id','=','statement_transaction_owners.statement_transaction_id');
}
/**
* @return MorphMany
*/
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class TransactionMappingLog extends Model
{
protected $fillable = ['imported_by','data','imported_date'];
protected $casts = [
'data' => 'array',
];
public static function boot() {
parent::boot();
static::creating(function ($model) {
$model->imported_by = auth()->user()->id;
});
}
}
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTransactionMappingLogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('transaction_mapping_logs', function (Blueprint $table) {
$table->id();
$table->bigInteger('imported_by')->unsigned();
$table->foreign('imported_by')->references('id')->on('users');
$table->dateTime('imported_date')->nullable();
$table->text('data')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('transaction_mapping_logs');
}
}
@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddMappedRateToAccountStatementsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('account_statements', function (Blueprint $table) {
$table->integer('total_rows')->unsigned()->default(0)->after('end_balance');
$table->integer('mapped_rows')->unsigned()->default(0)->after('end_balance');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('account_statements', function (Blueprint $table) {
$table->dropColumn('total_rows');
$table->dropColumn('mapped_rows');
});
}
}
Binary file not shown.
@@ -77,6 +77,13 @@
import { required } from "vuelidate/lib/validators";
export default {
props:{
editMapped: {
type: Boolean,
default: false,
required: false
}
},
data(){
return {
error: '',
@@ -117,6 +124,8 @@
transaction_reference : this.transaction_reference
};
if (this.editMapped) this.parameters['editMapped'] = true;
this.submit(this.route('api.accounting.statement.details.update', this.data.id), 'put', this.section, true, true);
},
errorHandler(error){
@@ -11,6 +11,46 @@
<div class="col"><a :href="item.owners.pending_verification[0].reference_link" target="_blank">{{item.owners.pending_verification[0].reference}}</a></div>
</div>
</div>
<!-- Pending Export tab -->
<div class="col-5" v-if="item.owners.approved.length === 1">
<div class="row">
<div class="col">{{item.owners.approved[0].system}}</div>
<div class="col">{{ typeString(item.owners.approved[0].type) }}</div>
<div class="col">
<div class="col d-flex justify-content-between">
<a :href="item.owners.approved[0].reference_link" target="_blank">{{item.owners.approved[0].reference}}</a>
<div v-if="stage === 4">
<!-- revert button -->
<button class="btn btn-xs btn-outline-danger b-rad-none m-r-5 requestModal" data-type="approveCorrectMappingTransaction">
<i class="fa fa-times fa-fw"></i>
</button>
<modal-component class="animate__animated animate__fast animate__fadeIn" type="approveCorrectMappingTransaction">
<general-confirmation-form-component
:contentText="returnTextRevert(item.owners.approved[0].reference)"
modalType="confirm"
class="text-center"
:apiRoute="route('api.accounting.bankStatement.details.status.update', item.owners.approved[0].id, 'pending_verification')"
apiMethod="post"
:section="section"
>
</general-confirmation-form-component>
</modal-component>
<!-- edit button -->
<button class="btn btn-xs btn-outline-success b-rad-none m-r-5 requestModal" data-type="updateOwner">
<i class="fa fa-edit"></i>
</button>
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="updateOwner">
<edit-single-item-in-list-component :data="item" :section="section" editMapped="true"></edit-single-item-in-list-component>
</modal-component>
</div>
</div>
</div>
</div>
</div>
<div class="col-5" v-if="item.owners.pending_verification.length > 1">
<div class="row">
<div class="col">
@@ -52,7 +92,8 @@
</div>
</div>
</div>
<div class="col-5" v-if="!item.owners.pending_verification.length">
<div class="col-5" v-if="!item.owners.pending_verification.length && !item.owners.approved.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">
@@ -81,7 +122,13 @@
</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-success" v-if="item.owners.approved.length">{{ [6, 7, 8, 9, 10, 11, 12, 13, 14].includes(item.owners.approved[0].type) ? 'Miscellaneous' : 'Approved' }}</div>
<!-- Pending Export tab for checkbox here -->
<div class="col-1" v-if="item.owners.approved.length === 1">
<input type="checkbox" class="request_export_item" v-model="selectAll" :value="item.id">
</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">
@@ -117,6 +164,9 @@
section:{
type: String,
required: true
},
selectAll: {
type:Boolean
}
},
methods: {
@@ -154,6 +204,9 @@
},
returnTextWithVariable(variable) {
return "Are you sure you want to choose this mapping " + variable + "?";
},
returnTextRevert(variable) {
return "Are you sure you want to revert this mapping " + variable + "?";
}
},
mixins: [componentHandler]
@@ -0,0 +1,72 @@
<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-7">
<div class="row">
<div class="col">{{item.owners.approved[0].system}}</div>
<div class="col">{{ typeString(item.owners.approved[0].type) }}</div>
<div class="col"><a :href="item.owners.approved[0].reference_link" target="_blank">{{item.owners.approved[0].reference}}</a></div>
<div class="col">{{item.owners.approved[0].invoice_reference}}</div>
<div class="col">{{item.owners.approved[0].receipt_reference}}</div>
</div>
</div>
<div class="col-1">{{ item.amount }}</div>
</div>
</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';
}
},
},
mixins: [componentHandler]
}
</script>
@@ -0,0 +1,126 @@
<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 class="card">
<div class="card-header">
<h3>{{ componentTitle }}</h3>
<div class="text-right">
<button class="btn btn-xs btn-outline-success b-rad-none m-r-5" @click="downloadInvoiceMapped">
Download Invoices Mapped
</button>
</div>
</div>
<!-- /.card-header -->
<div class="card-body table-responsive p-0">
<table class="table table-hover">
<thead>
<tr>
<th v-for="item in tableHeaders">{{ item }}</th>
</tr>
</thead>
<tbody v-show="!isLoading">
<tr v-for="(item, index) in $store.getters.getListData(section)">
<td>{{index+1}}</td>
<td>{{item.doc_no }}</td>
<td>{{item.date}}</td>
<td>{{item.debtor_code }}}</td>
<td>{{item.debtor_name}}</td>
<td>{{item.shipping_info}}</td>
<td>{{item.net_total}}</td>
<td>{{item.cancelled}}</td>
<td>{{item.mapped_status}}</td>
<td>{{item.mapped_result_reference}}</td>
</tr>
</tbody>
</table>
</div>
<!-- /.card-body -->
<div class="card-footer">
</div>
</div>
</div>
<div class="col-12">
<pagination-component :section="section" class="mb-5" ref="pagination"></pagination-component>
</div>
</div>
</template>
<script>
export default {
props: {
files: {
required: true
},
type: {
type: String,
required: true,
},
section:{
type: String,
required: true
},
},
data() {
return {
isLoading: false,
importedDate: null,
}
},
computed: {
pendingQueue() {
return this.$store.getters.isInCompleteQueue(this.section);
}
},
watch: {
pendingQueue(inComplete, oldValue){
if(inComplete){
this.importInvoice();
}
},
},
created(){
this.appendComponentTitle();
this.appendComponentTableHeader();
this.$store.dispatch('updateListQueue', {'name': this.section});
},
methods: {
appendComponentTitle() {
this.componentTitle = this.section == 'importInvoiceMapping' ? 'Imported Invoices Mapped' : 'Imported Receipts Mapped';
},
appendComponentTableHeader() {
if (this.section == 'importInvoiceMapping') {
this.tableHeaders = ['No','Doc No','Date','Debtor Code','Debtor Name','Shipping Info','Net Total','Cancelled','Mapped Status','Mapped Reference No'];
} else {
this.tableHeaders = ['No','OR No','Date','Creditor Code','Creditor Name','Shipping Info','Net Total','Cancelled','Mapped Status','Mapped Reference No'];
}
},
importInvoice(){
this.isLoading = true;
this.parameters = {
files: this.files
};
this.submit(this.route('api.'+(this.section == 'importInvoiceMapping' ? 'import_invoices' : 'import_receipts')+'.upload'), 'post', this.section, true, false);
},
successHandler(response){
this.$store.dispatch('completeList', {'name': this.section, 'data': response.payload.data});
this.importedDate = response.payload.importedDate;
this.isLoading = false;
},
downloadInvoiceMapped() {
var arrDateTime = this.importedDate.split(" ");
const fileName = this.section == 'importInvoiceMapping' ? 'InvoiceMapped' : 'ReceiptMapped';
window.open(this.route('importedInvoiceMapped.export')+'?date='+arrDateTime[0]+'&time='+arrDateTime[1]+'&fileName='+fileName, '_blank');
},
}
}
</script>
@@ -0,0 +1,67 @@
<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-20 m-r-15 pointer bg-complete text-white" @click="getReportFilter()">Mapped Report</div>
</div>
</div>
</div>
</div>
</div>
<div class="row" v-if="step == 1">
<div class="col">
<div class="row">
<div class="col">
<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-7">
<div class="row">
<div class="col">System</div>
<div class="col">Type</div>
<div class="col">Reference</div>
<div class="col">Invoice Reference</div>
<div class="col">Receipt Reference</div>
</div>
</div>
<div class="col-1">Amount</div>
</div>
</div>
</div>
<list-component ref="TransactionsMappedList" section="TransactionsMappedSection" :endpoint="route('api.accounting.bank.transaction')" :options="filter">
<template slot="list" slot-scope="{data}">
<statement-transaction-mapped-component :data="data" :section="section"></statement-transaction-mapped-component>
</template>
</list-component>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
step: 0,
filter: {},
}
},
methods: {
getReportFilter() {
this.filter = {min_amount: 0, is_mapped: true, statement_transaction_owner_type_in: [1, 2], statement_transaction_owner_status_in: [2], statement_transaction_owner_invoice_or_receipt_ref_not_null: true, per_page: 100, order_by: {column: 'posting_date', DESC: true}};
this.step = 1
}
},
}
</script>
@@ -65,10 +65,12 @@
<thead>
<tr>
<th>ID</th>
<th>Date</th>
<th>Posting Date</th>
<th>Description 1</th>
<th>Pay For</th>
<th>System References</th>
<th>Description 2</th>
<th>Description 3</th>
<th>Description 4</th>
<th>Description 5</th>
<th>Amount</th>
<th>Action</th>
</tr>
@@ -76,10 +78,12 @@
<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.posting_date}}</td>
<td>{{item.transaction_description_1}}</td>
<td>{{item.transaction_description_2}}</td>
<td>{{item.transaction_description_3}}</td>
<td>{{item.transaction_description_4}}</td>
<td>{{item.transaction_description_5}}</td>
<td>{{item.amount}}</td>
<td>
<a href="#">
@@ -183,11 +187,7 @@ export default {
},
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: {
@@ -55,7 +55,7 @@
<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="row parentContainer" v-if="step > 0">
<div class="col">
<div class="row">
<div class="col">
@@ -68,11 +68,38 @@
class="text-center"
:apiRoute="route('api.accounting.statement_transaction.owner.groupApprove')"
apiMethod="post"
:params="filter"
:section="section"
>
</general-confirmation-form-component>
</modal-component>
</div>
<!-- filter for Pending Export tab -->
<div v-if="stage === 4">
<div class="row pt-3 pb-3">
<div class="col-5 col-md-3 ">
<validation-wrapper-component :validator="$v.parameters.startDate">
<label class="all-caps">Start Date</label>
<date-picker-component :parameters="parameters" v-model.lazy="parameters.startDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col-5 col-md-3 ">
<validation-wrapper-component :validator="$v.parameters.endDate">
<label class="all-caps">End Date</label>
<date-picker-component :parameters="parameters" v-model.lazy="parameters.endDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col-2 col-md-auto">
<div class="btn btn-lg btn-primary fs-11 w-100 h-100 d-flex justify-content-center align-items-center" @click="startMapping(4)">
<span>
Search
</span>
</div>
</div>
</div>
</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">
@@ -86,19 +113,23 @@
</div>
</div>
<div class="col-1">Amount</div>
<div class="col-1" v-if="stage === 4"></div>
<div class="col-1" v-if="stage === 4">Select <input type="checkbox" v-model="selectAll">
</div>
</div>
</div>
<list-component ref="bankTransactionsList" section="bankTransactionSection" :endpoint="route('api.accounting.bank.transaction')" :options="this.filter">
</div>
<list-component :key="stage" 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>
<statement-transaction-component :data="data" :section="section" :stage="stage" :selectAll="selectAll"></statement-transaction-component>
</template>
</list-component>
</div>
</div>
</div>
</div>
<div class="row" v-if="stage === 4">
<div class="row parentContainer" 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">
@@ -122,7 +153,7 @@
</file-input-component>
</div>
</div>
<div class="btn btn-lg btn-primary m-t-20" @click="importInvoice">Import Invoices</div>
<div class="btn btn-lg btn-primary m-t-20 requestModal" data-type="ModalImportInvoice" @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">
@@ -130,11 +161,19 @@
<a href="https://docs.google.com/presentation/d/1XwKcdBCpHQSCQmsgnUHsypcqk5kdc8uZMqkjW9JXiw4/edit?usp=sharing" target="_blank">Learn How to do this step?</a>
</div>
</div>
<div class="row">
<div class="col-12">
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="ModalImportInvoice">
<imported-invoice-mapped-component section="importInvoiceMapping" v-if="mappedTrue" :files="files"></imported-invoice-mapped-component>
</modal-component>
</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>
<div class="btn btn-lg btn-primary" @click="exportReceiptToAutoCount">Export Receipts To AutoCount</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">
@@ -153,7 +192,7 @@
</file-input-component>
</div>
</div>
<div class="btn btn-lg btn-primary m-t-20" @click="importReceipts">Import Receipts</div>
<div class="btn btn-lg btn-primary m-t-20 requestModal" data-type="ModalImportInvoice" @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">
@@ -161,6 +200,13 @@
<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 class="row">
<div class="col-12">
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="ModalImportInvoice">
<imported-invoice-mapped-component section="importReceiptMapping" v-if="mappedTrue" :files="files"></imported-invoice-mapped-component>
</modal-component>
</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">
@@ -182,6 +228,10 @@ export default {
data(){
return {
parameters: {
startDate: '',
endDate: '',
},
type: null,
stage: null,
exportStage: 0,
@@ -190,28 +240,57 @@ export default {
files: [],
parameters: {},
section: 'bankTransactionSection',
mappedTrue: false,
selectAll: false,
}
},
validations: {
parameters: {
startDate: {
required
},
endDate: {
required
},
},
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);
this.mappedTrue = true;
},
importReceipts(){
this.parameters = {
files: this.files
};
this.submit(this.route('api.import_receipts.upload'), 'post', this.section, true, false);
this.mappedTrue = true;
},
exportInvoiceToAutoCount(){
window.open(this.route('invoiceTransactions.export'), '_blank');
const checkedStatementTransactions = this.getCheckedStatementOwners();
let route = this.route('invoiceTransactions.export')+'?filter='+JSON.stringify(this.filter);
if (checkedStatementTransactions) {
route += '&bankStatementTransactionId='+checkedStatementTransactions;
}
window.open(route, '_blank');
},
exportReceiptToAutoCount(){
const checkedStatementTransactions = this.getCheckedStatementOwners();
this.filter['statement_transaction_owner_type_in'] = [3,4,5,7,13,14];
let route = this.route('receiptTransactions.export')+'?filter='+JSON.stringify(this.filter);
if (checkedStatementTransactions) {
route += '&bankStatementTransactionId='+checkedStatementTransactions;
}
window.open(route, '_blank');
},
getCheckedStatementOwners() {
let bankStatementTransactionId = [];
$('.request_export_item:checked').each(function() {
bankStatementTransactionId.push($(this).val());
});
return (bankStatementTransactionId.length > 0 ? JSON.stringify(bankStatementTransactionId) : null);
},
successHandler(){
this.step += 1;
@@ -233,10 +312,14 @@ export default {
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}}
this.filter = {min_amount: 0, is_mapped_false_or_mapped_but_status_in: [4], 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}}
if (typeof this.parameters.startDate != 'undefined' && this.parameters.startDate != '') this.filter = {...this.filter, ...{statement_transaction_posting_start: this.parameters.startDate}};
if (typeof this.parameters.endDate != 'undefined' && this.parameters.endDate != '') this.filter = {...this.filter, ...{statement_transaction_posting_end:this.parameters.endDate}};
break;
}
}
@@ -254,6 +337,11 @@ export default {
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}}
if (typeof this.parameters.startDate != 'undefined' && this.parameters.startDate != '') this.filter = {...this.filter, ...{statement_transaction_posting_start: this.parameters.startDate}};
if (typeof this.parameters.endDate != 'undefined' && this.parameters.endDate != '') this.filter = {...this.filter, ...{statement_transaction_posting_end:this.parameters.endDate}};
break;
}
}
@@ -6,7 +6,7 @@
<div class="col">
<div class="row p-b-5 b-b b-grey m-b-10 m-l-0 m-r-0">
<div class="col no-padding">
<h6>Transaction History</h6>
<h6>Account Statement - Transaction History</h6>
</div>
</div>
<div class="row" v-if="transaction">
@@ -0,0 +1,98 @@
<template>
<div class="row m-b-15">
<div class="col-12">
<div class="row">
<div class="col-12">
<div class="row m-l-0 m-r-0">
<div class="col mb-2 mb-md-0 p-l-3 p-r-3 p-md-0">
<validation-wrapper-component selectable :validator="$v.parameters.supplier">
<label>Supplier</label>
<selectable-component :disableOnFetch="true" :endpoint="route('api.company.list') + '?filters=' + JSON.stringify({'business_type': 3, 'status_in': [1, 2, 0]})" section="exportSupplierFilterSection" valueColumn="id" :labelColumn="['name']" v-model="parameters.supplier"></selectable-component>
</validation-wrapper-component>
</div>
<div class="col mb-2 mb-md-0 p-l-3 p-r-3 p-md-0">
<validation-wrapper-component :validator="$v.parameters.startDate">
<label class="all-caps">Start Date</label>
<date-picker-component :parameters="parameters" v-model.lazy="parameters.startDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col mb-2 mb-md-0 p-l-3 p-r-3 p-md-0">
<validation-wrapper-component :validator="$v.parameters.endDate">
<label class="all-caps">End Date</label>
<date-picker-component :parameters="parameters" v-model.lazy="parameters.endDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col-auto p-l-3 p-r-3 p-md-0">
<div class="btn btn-lg btn-primary fs-11 w-100 h-100 d-flex justify-content-center align-items-center" @click="bulkDownloadWhiteForm()">
<span>
Export
</span>
</div>
</div>
</div>
</div>
</div>
<div class="row align-items-center justify-content-center bg-white p-t-50 p-b-50 p-l-15 p-r-15 margin-15" v-if="returnData">
<div class="col-10">
<div class="row align-items-center justify-content-center hint-text">
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png" class="w-100 hint-text" /></div>
</div>
<div class="row text-center">
<div class="col">
<div class="row m-t-20">
<div class="col">
<p class="all-caps no-margin fs-11" :class="[{'text-danger': returnData.status == 'Error'}]" style="letter-spacing: 2px;">{{ returnData.message }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import componentHandler from "../../../general/mixins/componentHandler";
import { required } from "vuelidate/lib/validators";
export default {
data(){
return {
returnData: null,
parameters: {
startDate: '',
endDate: '',
supplier: ''
},
}
},
validations: {
parameters: {
supplier: {
required
},
startDate: {
required
},
endDate: {
required
},
}
},
methods: {
bulkDownloadWhiteForm(){
this.submit(this.route('api.suppliers.white_forms'), 'post', this.section, false, false);
},
successHandler(response) {
this.isLoading = false;
if (response.message) {
this.returnData = response;
} else {
this.returnData = null;
}
},
},
mixins: [componentHandler]
};
</script>
@@ -0,0 +1,121 @@
<template>
<div class="row">
<div class="col">
<div class="row">
<div class="col-8 p-l-0">
<small class="all-caps muted fs-10">Export Payment Transactions CSV</small>
<download-billing-with-dates-component section="paymentsReportSection" ></download-billing-with-dates-component>
</div>
</div>
<div class="row">
<div class="col-8 p-l-0">
<small class="all-caps muted fs-10">Download Zip White Form</small>
<download-supplier-white-form-component section="paymentsReportSection" ></download-supplier-white-form-component>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6">
<div class="row">
<div class="col p-l-0">
<small class="all-caps muted fs-10">Filter Supplier</small>
</div>
</div>
<div class="row">
<div class="col padding-0">
<validation-wrapper-component selectable :validator="$v.filterSupplier">
<label>Supplier</label>
<selectable-component :disableOnFetch="true" :endpoint="route('api.company.list') + '?filters=' + JSON.stringify({'business_type': 3, 'status_in': [1, 2, 0]})" section="supplierFilterSection" valueColumn="id" :labelColumn="['name']" v-model="filterSupplier"></selectable-component>
</validation-wrapper-component>
</div>
<div class="col-2 p-l-0">
<button type="button" class="btn btn-lg btn-complete fs-11 h-100 d-flex justify-content-center align-items-center" @click="reset()">Reset</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col bg-white p-t-15 p-b-15">
<div class="row no-margin">
<div class="col-12 col-md-4">
<div class="row m-b-15 p-b-10 b-b b-grey">
<div class="col">
<small class="all-caps muted fs-10">Complete Payments</small>
</div>
</div>
<div class="row">
<div class="col">
<list-component key="2" section="paymentVerificationSection" :endpoint="route('api.transaction.list')" :options="{'type': 1, 'status': 3, 'payment_method_not_in': [4]}">
<template slot="list" slot-scope="{data}">
<payment-verification-component section="paymentVerificationSection" :data="data" :no_action="true"></payment-verification-component>
</template>
</list-component>
</div>
</div>
</div>
<div class="col-12 col-md-4">
<div class="row m-b-15 p-b-10 b-b b-grey">
<div class="col">
<small class="all-caps muted fs-10">Complete Currency Orders</small>
</div>
</div>
<div class="row">
<div class="col">
<list-component :key="currencyOrderKey" section="completeTransactionGroupsListSection" :options="{'per_page': 20, 'status': 3, ...this.filterSupplier && {'issuer_in': [this.filterSupplier]}}" :endpoint="route('api.transaction.group.list')">
<template slot="list" slot-scope="{data}">
<transaction-group-component section="transactionGroupsListSection" :data="data"></transaction-group-component>
</template>
</list-component>
<!-- <list-component key="2" section="currencyOrdersListSection" :options="{'per_page': 20, 'document_type_in': ['CURRENCY_VENDOR_ORDER'], 'status': 2, 'with_company': true}" :endpoint="route('api.document.list')">
<template slot="list" slot-scope="{data}">
<currency-order-component section="currencyOrdersListSection" :data="data"></currency-order-component>
</template>
</list-component> -->
</div>
</div>
</div>
<div class="col-12 col-md-4">
<div class="row m-b-15 p-b-10 b-b b-grey">
<div class="col">
<small class="all-caps muted fs-10">Open Currency Orders</small>
</div>
</div>
<div class="row">
<div class="col">
<list-component :key="currencyOrderKey" section="transactionGroupsListSection" :options="{'per_page': 20, 'status_in': [0, 1, 2], ...this.filterSupplier && {'issuer_in': [this.filterSupplier]}}" :endpoint="route('api.transaction.group.list')">
<template slot="list" slot-scope="{data}">
<transaction-group-component section="transactionGroupsListSection" :data="data"></transaction-group-component>
</template>
</list-component>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
filterSupplier: "",
currencyOrderKey: 1,
}
},
validations: {
filterSupplier: {}
},
watch: {
filterSupplier() {
this.currencyOrderKey += 1;
}
},
methods: {
reset() {
this.filterSupplier = ""
}
}
}
</script>
File diff suppressed because one or more lines are too long
@@ -289,6 +289,22 @@
<regenerate-booking-invoices-component :data="booking" :section="section" class="text-center"></regenerate-booking-invoices-component>
</modal-component>
</div>
<div class="row m-t-10" v-if="$store.getters.isSuperAdmin">
<div class="col-sm col-md-auto">
<div class="btn btn-sm btn block all-caps b-rad-none btn-danger pointer requestModal" data-type="deleteTransfer">Delete Transfer</div>
</div>
<modal-component class="animate__animated animate__fast animate__fadeIn" type="deleteTransfer">
<general-confirmation-form-component
contentText="Are you sure you want to delete this Order?"
modalType="delete"
class="text-center"
:apiRoute="route('api.booking.payment.expire', booking.id)"
apiMethod="post"
:section="section"
>
</general-confirmation-form-component>
</modal-component>
</div>
</div>
<div class="col-12 col-sm-12 col-md-5 mt-3 mt-sm-0">
<booking-payment-quotation-component :data="booking" :section="section"></booking-payment-quotation-component>
@@ -0,0 +1,104 @@
<template>
<div class="row">
<div class="col">
<div class="row m-b-5" @keyup.enter="submitSearch">
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.marking">
<label class="all-caps">Marking</label>
<!-- <input type="text" class="form-control" v-model="parameters.marking" :class="[{ 'not-allowed': hasMarkingParameter }]" :disabled="hasMarkingParameter"> -->
<input type="text" class="form-control" v-model="parameters.marking" :class="[{ 'not-allowed': hasMarking }]" :disabled="hasMarking">
</validation-wrapper-component>
</div>
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.startDate">
<label class="all-caps">Start Date</label>
<date-picker-component v-model.lazy="parameters.startDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.endDate">
<label class="all-caps">End Date</label>
<date-picker-component v-model.lazy="parameters.endDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col col-md-auto d-flex justify-content-center align-items-center">
<button type="button" class="btn btn-lg btn-primary fs-11 w-100"
@click="submitSearch()">Download</button>
</div>
</div>
<loading-component style="height: 100px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
<div class="row align-items-center justify-content-center bg-white p-t-50 p-b-50 p-l-15 p-r-15 margin-15" v-if="!isLoading && returnData">
<div class="col-10">
<div class="row align-items-center justify-content-center hint-text">
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png" class="w-100 hint-text" /></div>
</div>
<div class="row text-center">
<div class="col">
<div class="row m-t-20">
<div class="col">
<p class="all-caps no-margin fs-11" :class="[{'text-danger': returnData.status == 'Error'}]" style="letter-spacing: 2px;">{{ returnData.message }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { required } from "vuelidate/lib/validators";
export default {
data() {
return {
key: 1,
returnData: null,
isLoading: false,
hasMarking: false,
parameters: {
marking: null,
startDate: null,
endDate: null,
},
}
},
validations: {
parameters: {
marking: { required },
startDate: { required },
endDate: { required },
}
},
created() {
const urlSearchParams = new URLSearchParams(window.location.search);
if (urlSearchParams.has('marking')) {
this.parameters.marking = urlSearchParams.get('marking');
}
this.hasMarkingParameter();
},
methods: {
submitSearch() {
this.isLoading = true;
this.submit(this.route('api.customers.invoices'), 'post', this.section, false, false);
},
errorHandler(error) {
this.isLoading = false;
console.log(error);
},
successHandler(response) {
this.isLoading = false;
if (response.message) {
this.returnData = response;
} else {
this.returnData = null;
}
},
hasMarkingParameter() {
const urlSearchParams = new URLSearchParams(window.location.search);
this.hasMarking = urlSearchParams.has('marking');
},
},
}
</script>
@@ -99,6 +99,7 @@
updateFilters(filters){
this.filters = filters;
this.setDecoratorDefault();
this.$store.dispatch('updateListQueue', {'name': this.section, 'page': 1, 'filters': this.filters});
this.submit(this.endpoint + '?page=1&filters=' + JSON.stringify(this.filters), 'get', this.section, false, false)
},
successHandler(response){
@@ -15,7 +15,7 @@
<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 class="btn btn-sm btn-block b-rad-none" :class="[{'btn-danger': modalType !== 'confirm'}, {'btn-success': modalType === 'confirm'}]" @click="submitForm()">{{ buttonText }}</div>
</div>
</div>
</div>
@@ -32,6 +32,10 @@
type: String,
required: true
},
params: {
type: Array,
required: false
},
modalType: {
type: String,
default: 'confirm'
@@ -50,6 +54,10 @@
},
},
methods: {
submitForm() {
if (this.params) this.parameters = this.params;
return this.submit(this.apiRoute, this.apiMethod, this.section, true, true);
}
},
mixins: [componentHandler, ModalFormHandler]
@@ -1,5 +1,5 @@
<template>
<select class="form-control">
<select class="form-control" :disabled="this.options.length === 0 && this.disableOnFetch">
<option disabled></option>
</select>
</template>
@@ -13,6 +13,10 @@
},
value: {
required: false
},
disableOnFetch: {
type: Boolean,
default: false
}
},
mounted(){
@@ -1,5 +1,5 @@
<template>
<select-component :options="$store.getters.getSelectableList(section)" :value="value" @input="(val) => {$emit('input', val)}"></select-component>
<select-component :disableOnFetch="disableOnFetch" :options="$store.getters.getSelectableList(section)" :value="value" @input="(val) => {$emit('input', val)}"></select-component>
</template>
<script>
@@ -23,6 +23,10 @@
valueColumn: {
type: String,
required: true
},
disableOnFetch: {
type: Boolean,
default: false
}
},
created(){
@@ -0,0 +1,111 @@
<template>
<div class="row">
<div class="col">
<div class="row m-b-5" @keyup.enter="submitSearch">
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.reference_no">
<label class="all-caps">Booking Reference</label>
<input type="text" class="form-control" v-model="parameters.reference_no">
</validation-wrapper-component>
</div>
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.startDate">
<label class="all-caps">Start Date</label>
<date-picker-component v-model.lazy="parameters.startDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col p-r-0">
<validation-wrapper-component :validator="$v.parameters.endDate">
<label class="all-caps">End Date</label>
<date-picker-component v-model.lazy="parameters.endDate"></date-picker-component>
</validation-wrapper-component>
</div>
<div class="col col-md-auto d-flex justify-content-center align-items-center">
<button type="button" class="btn btn-lg btn-primary fs-11 w-100" @click="submitSearch()">Search</button>
</div>
</div>
<div class="row">
<div class="col">
<div class="row padding-10">
<div class="col-3 fs-10">Date</div>
<div class="col fs-10">Description</div>
<div class="col-2 fs-10 text-center">Incoming</div>
<div class="col-2 fs-10 text-center">Outgoing</div>
<div class="col-2 fs-10 text-right">Balance</div>
</div>
<list-component :key="key" section="topUp" :endpoint="route('api.transaction.wallet.list')" :options="options">
<template slot="list" slot-scope="{data}">
<customer-wallet-transaction-component :data="data" :deciamls="deciamls"></customer-wallet-transaction-component>
</template>
</list-component>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
deciamls: {
type: Number,
required: true
},
showingTransactionCount: {
required: true
},
wallet_id: {
type: Number,
required: true
}
},
data(){
return {
key: 1,
parameters: {
reference_no: null,
startDate: null,
endDate: null,
},
options: {
status_in: [2, 3],
owner_type: 'App\\Models\\Wallet',
owner_id: this.wallet_id,
per_page: this.showingTransactionCount
}
}
},
validations: {
parameters: {
reference_no: { },
startDate: { },
endDate: { },
}
},
watch: {
showingTransactionCount() {
this.options.per_page = this.showingTransactionCount;
this.key ++;
}
},
methods: {
submitSearch() {
delete this.options.with_booking_marking_like;
delete this.options.created_after_or_equal;
delete this.options.created_before_or_equal;
if (this.parameters.reference_no) {
this.options.with_booking_marking_like = this.parameters.reference_no
}
if (this.parameters.startDate) {
this.options.created_after_or_equal = this.parameters.startDate
}
if (this.parameters.endDate) {
this.options.created_before_or_equal = this.parameters.endDate
}
this.key ++;
},
},
}
</script>
@@ -1,174 +0,0 @@
<template>
<div class="row">
<div class="col">
<loading-component style="height: 200px; top: 0;" key="1" color="success"
v-show="isLoading"></loading-component>
<div class="row" v-if="company">
<div class="col-8">
<div class="row p-b-5 b-b b-grey m-b-10 m-l-0 m-r-0">
<div class="col no-padding">
<h6>Transaction History</h6>
</div>
</div>
<div class="row" v-if="company.wallet">
<div class="col">
<div class="row padding-10">
<div class="col-3 fs-10">Date</div>
<div class="col fs-10">Description</div>
<div class="col-2 fs-10 text-center">Incoming</div>
<div class="col-2 fs-10 text-center">Outgoing</div>
<div class="col-2 fs-10 text-right">Balance</div>
</div>
<div class="row bg-white padding-10 m-b-10 rounded"
v-for="(item, index) in company.wallet.transactions" v-bind:key="item.id" :data="item">
<div class="col-3 fs-12">{{ item.created_at }}</div>
<div class="col fs-12">
<span v-html="item.description"></span>
<a target=_blank v-if="[9, 11].includes(item.type)"
:href="route('transaction.credit_note.download', item.id)">
<i class="fa fa-download fs-11 m-l-5 text-secondary hover-primary"></i>
</a>
</div>
<div class="col-2 text-success text-center">
{{ [5, 9].includes(parseFloat(item.type)) ? (Math.round((parseFloat(item.amount) + Number.EPSILON) * 100000) / 100000).toLocaleString('en-US', { minimumFractionDigits: 5, maximumFractionDigits: 5 }) : '' }}
</div>
<div class="col-2 text-danger text-center">
{{ [1, 11].includes(parseFloat(item.type)) ? '- ' + (Math.round((parseFloat(item.amount) + Number.EPSILON) * 100000) / 100000).toLocaleString('en-US', { minimumFractionDigits: 5, maximumFractionDigits: 5 }) : '' }}
</div>
<div class="col-2 text-right">{{ remainingBalance(index) }}</div>
</div>
</div>
</div>
<div class="row align-items-center justify-content-center p-t-50 p-b-50"
v-if="!company.wallet || !company.wallet.transactions.length">
<div class="col-10">
<div class="row align-items-center justify-content-center hint-text">
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png"
class="w-100 hint-text"></div>
</div>
<div class="row text-center">
<div class="col">
<div class="row m-t-20">
<div class="col">
<p class="all-caps no-margin fs-11" style="letter-spacing: 2px;">Nothing To Show
Here</p>
</div>
</div>
<div class="row m-t-5 align-items-center justify-content-center">
<div class="col">
<small class="fs-9 muted all-caps font-lato" style="letter-spacing: 2px">There
is no results found, Try adjusting your filters to find what you are looking
for.</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-3 m-l-15">
<wallet-component :data="company" :creditable=true></wallet-component>
<div class="row m-t-20">
<div class="col">
<div class="row m-b-10">
<div class="col">
<div class="font-head fs-10 all-caps">Top Up Records</div>
</div>
</div>
<div class="row" v-if="company.wallet">
<div class="col">
<wallet-top-up-history-component v-for="item in company.wallet.top_up_records"
v-bind:key="item.id" :data="item"></wallet-top-up-history-component>
</div>
</div>
<div class="row align-items-center justify-content-center p-t-50 p-b-50"
v-if="!company.wallet || !company.wallet.top_up_records.length">
<div class="col-10">
<div class="row align-items-center justify-content-center hint-text">
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png"
class="w-100 hint-text"></div>
</div>
<div class="row text-center">
<div class="col">
<div class="row m-t-20">
<div class="col">
<p class="all-caps no-margin fs-11" style="letter-spacing: 2px;">Nothing
To Show Here</p>
</div>
</div>
<div class="row m-t-5 align-items-center justify-content-center">
<div class="col">
<small class="fs-9 muted all-caps font-lato"
style="letter-spacing: 2px">There is no results found, Try adjusting
your filters to find what you are looking for.</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
id: {
type: Number,
required: true
}
},
data() {
return {
section: 'customerTransactionSection',
isLoading: true,
company: null,
attention: false
}
},
computed: {
pendingQueue() {
return this.$store.getters.isInCompleteQueue(this.section);
}
},
watch: {
pendingQueue(inComplete) {
if (inComplete) {
this.fetchCompany();
}
}
},
created() {
this.$store.dispatch('updateListQueue', { 'name': this.section });
},
methods: {
fetchCompany() {
this.isLoading = true;
this.submit(route('api.company.show', this.id), 'get', this.section, false, false)
},
remainingBalance(index) {
let tempBalance = 0;
if (this.company.wallet) {
let transactions = this.company.wallet.transactions.slice().reverse();
transactions.slice(0, transactions.length - index).map(function (transaction) {
[1, 11].includes(transaction.type) ? tempBalance -= (transaction.amount) : tempBalance += (transaction.amount);
return tempBalance
}, 0);
}
return (Math.round((tempBalance + Number.EPSILON) * 100000) / 100000).toLocaleString('en-US', { minimumFractionDigits: 5, maximumFractionDigits: 5 });
},
successHandler(response) {
this.isLoading = false;
this.company = response.payload.data;
}
}
}
</script>
@@ -2,53 +2,33 @@
<div class="row">
<div class="col">
<loading-component style="height: 200px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
<div class="row" v-if="company">
<div v-if="!isLoading">
<div class="row">
<div class="col-8">
<div class="row p-b-5 b-b b-grey m-b-10 m-l-0 m-r-0">
<div class="col no-padding">
<h6>Transaction History</h6>
<h6>Wallet Transaction History</h6>
</div>
</div>
<div class="row" v-if="company.wallet">
<div class="row m-t-10 m-b-10">
<div class="col">
<div class="row padding-10">
<div class="col-3 fs-10">Date</div>
<div class="col fs-10">Description</div>
<div class="col-2 fs-10 text-center">Incoming</div>
<div class="col-2 fs-10 text-center">Outgoing</div>
<div class="col-2 fs-10 text-right">Balance</div>
</div>
<div class="row bg-white padding-10 m-b-10 rounded" v-for="(item, index) in company.wallet.transactions" v-bind:key="item.id" :data="item">
<div class="col-3 fs-12">{{item.created_at}}</div>
<div class="col fs-12"><span v-html="item.description"></span> <a target=”_blank” v-if="[9,11].includes(item.type) " :href="route('transaction.credit_note.download', item.id)"><i class="fa fa-download fs-11 m-l-5 text-secondary hover-primary"></i></a></div>
<div class="col-2 text-success text-center">{{[5, 9].includes(parseFloat(item.type)) ? (Math.round((parseFloat(item.amount) + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : ''}}</div>
<div class="col-2 text-danger text-center">{{[1, 11].includes(parseFloat(item.type)) ? '- ' + (Math.round((parseFloat(item.amount) + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : ''}}</div>
<div class="col-2 text-right">{{remainingBalance(index)}}</div>
</div>
</div>
</div>
<div class="row align-items-center justify-content-center p-t-50 p-b-50" v-if="!company.wallet || !company.wallet.transactions.length">
<div class="col-10">
<div class="row align-items-center justify-content-center hint-text">
<div class="col-4 hint-text"><img src="/images/not-found-illustration.png" class="w-100 hint-text"></div>
</div>
<div class="row text-center">
<div class="col">
<div class="row m-t-20">
<div class="col">
<p class="all-caps no-margin fs-11" style="letter-spacing: 2px;">Nothing To Show Here</p>
</div>
</div>
<div class="row m-t-5 align-items-center justify-content-center">
<div class="col">
<small class="fs-9 muted all-caps font-lato" style="letter-spacing: 2px">There is no results found, Try adjusting your filters to find what you are looking for.</small>
<div class="d-flex align-items-center h-100">
<span class="btn btn-md fs-11 bg-primary text-white fs-12 m-r-5" :class="[{'bg-primary-darker': showingPreciseAmount}]" @click="showingPreciseAmount=!showingPreciseAmount">{{ showingPreciseAmount ? 'Showing Precise Wallet Transaction' : 'Show Precise Wallet Transaction'}}</span>
<a v-if="company" :href="route('wallet.details-export', company.reference, showingPreciseAmount)" target="_blank" class="btn btn-md btn-primary fs-11"><i class="fa fa-download m-r-5"></i>{{ showingPreciseAmount ? 'Download Precise Transaction' : 'Download Transaction'}}</a>
</div>
</div>
<div class="col-2">
<validation-wrapper-component selectable :validator="$v.showingTransactionCount">
<label>Showing Rows</label>
<select-component :options="[5, 10, 20, 30, 50]" v-model="showingTransactionCount"></select-component>
</validation-wrapper-component>
</div>
</div>
<div v-show="!showingPreciseAmount">
<customer-transaction-component :deciamls="2" :wallet_id="wallet_id" :showingTransactionCount="showingTransactionCount"></customer-transaction-component>
</div>
<div v-show="showingPreciseAmount">
<customer-transaction-component :deciamls="5" :wallet_id="wallet_id" :showingTransactionCount="showingTransactionCount"></customer-transaction-component>
</div>
</div>
<div class="col-3 m-l-15">
@@ -92,6 +72,7 @@
</div>
</div>
</div>
</div>
</template>
<script>
export default {
@@ -99,6 +80,10 @@ export default {
id: {
type: Number,
required: true
},
wallet_id: {
type: Number,
required: true
}
},
data(){
@@ -106,6 +91,8 @@ export default {
section: 'customerTransactionSection',
isLoading: true,
company: null,
showingPreciseAmount: false,
showingTransactionCount: 10,
attention: false
}
},
@@ -121,6 +108,9 @@ export default {
}
}
},
validations: {
showingTransactionCount: { },
},
created(){
this.$store.dispatch('updateListQueue', {'name': this.section});
},
@@ -129,19 +119,6 @@ export default {
this.isLoading = true;
this.submit(route('api.company.show', this.id), 'get', this.section, false, false)
},
remainingBalance(index) {
let tempBalance = 0;
if(this.company.wallet){
let transactions = this.company.wallet.transactions.slice().reverse();
transactions.slice(0, transactions.length - index).map(function(transaction) {
[1, 11].includes(transaction.type) ? tempBalance -= (transaction.amount) : tempBalance += (transaction.amount);
return tempBalance
}, 0);
}
return (Math.round((tempBalance + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
},
successHandler(response){
this.isLoading = false;
this.company = response.payload.data;
@@ -0,0 +1,35 @@
<template>
<div class="row bg-white padding-10 m-b-10 rounded">
<div class="col-3 fs-12">{{data.created_at}}</div>
<div class="col fs-12"><span v-html="data.description"></span> <a target=”_blank” v-if="[9,11].includes(data.type) " :href="route('transaction.credit_note.download', data.id)"><i class="fa fa-download fs-11 m-l-5 text-secondary hover-primary"></i></a></div>
<div class="col-2 text-success text-center">{{[5, 9].includes(parseFloat(data.type)) ? formatValue(data.amount) : ''}}</div>
<div class="col-2 text-danger text-center">{{[1, 11].includes(parseFloat(data.type)) ? '- ' + (formatValue(data.amount)) : ''}}</div>
<div class="col-2 text-right">{{formatValue(data.running_balance)}}</div>
</div>
</template>
<script>
import componentHandler from '../../../general/mixins/componentHandler';
export default {
props: {
data: {
required: true,
type: Object
},
deciamls: {
type: Number,
required: true
},
},
methods: {
formatValue(value) {
if (this.deciamls != 2) {
return (Math.round((parseFloat(value) + Number.EPSILON) * 100000) / 100000).toLocaleString('en-US', { minimumFractionDigits: 5, maximumFractionDigits: 5 });
}
return (Math.round((parseFloat(value) + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
}
},
mixins: [componentHandler],
}
</script>
@@ -35,7 +35,7 @@
<div class="col-auto">
<div class="btn btn-xs p-l-15 p-r-20 b-rad-none font-heading btn-rounded bg-white" @click="reload = true"><i class="fa fa-plus fs-8 m-r-5"></i> Reload</div>
</div>
<div class="col-auto p-l-0" v-if="!mini">
<div class="col-auto p-l-0" v-if="!mini && data.wallet">
<a class="text-white fs-10" :href="route('wallet.details', data.reference)" target="_blank">Transaction History<i class="fa fa-angle-right p-l-5"></i></a>
</div>
</div>
@@ -52,11 +52,20 @@
<wallet-top-up-form-component :data="data" :amount="(!data.wallet ? amount : (Math.round((((amount - data.wallet.amount) < '0.00' ? '0.00' : (amount - data.wallet.amount)) + Number.EPSILON) * 100) / 100).toFixed(2))" :creditable="creditable"></wallet-top-up-form-component>
</div>
</div>
<div class="row m-t-20" v-if="$store.getters.isAdmin">
<div class="row m-t-10" v-if="$store.getters.isAdmin">
<div class="col">
<div class="row m-t-10" v-if="$store.getters.isSuperAdmin">
<div class="col">
<a :href="route('account.statment', data.reference)">View Account Statement<i class="fa fa-angle-right p-l-5"></i></a>
</div>
</div>
<div class="row m-t-10">
<div class="col">
<a :href="downloadInvoiceUrl()">Download All Invoices<i class="fa fa-angle-right p-l-5"></i></a>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -79,6 +88,11 @@
default: false
}
},
methods: {
downloadInvoiceUrl(){
return this.route('customers.invoices') + '?marking=' + this.data.reference;
},
},
data(){
return {
reload: false,
+20
View File
@@ -13,6 +13,25 @@ export default {
let statusCode = response.status,
success = response.ok;
if (response.headers.get("content-type") === "application/zip") {
const fileName = response.headers.get('Content-Disposition').split('filename=')[1].replaceAll('"', '');
response.blob().then(response => {
if (!success) {
this.openModal();
errorNotification ? this.$store.dispatch('createNotification', { title: response.title, message: response.message, type: 'error' }) : null;
this.errorHandler(response, statusCode); return;
}
successNotification ? this.$store.dispatch('createNotification', { title: response.title, message: response.message, type: 'success' }) : null;
const link = document.createElement('a');
link.href = window.URL.createObjectURL(response);
link.download = fileName.trim();
link.click();
this.successHandler(response)
});
} else {
response.json().then(response => {
if (!success) {
@@ -26,6 +45,7 @@ export default {
});
}
}).catch((error) => {
console.log(error);
this.$store.dispatch('createNotification', {title: 'Unexpected Error', message: 'An unexpected error has occurred. Try again!', type: 'error'});
@@ -125,7 +125,7 @@
<div class="row justify-content-end">
<div class="col">
<div class="row fs-12 text-center">
<div class="col p-t-20 p-b-20 bg-master-lighter tabButton" tab-name="complete">
<div class="col p-t-20 p-b-20 bg-master-lighter tabButton" tab-name="ReportMapped">
<div class="row justify-content-center m-b-5">
<div class="col-auto">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
@@ -312,6 +312,14 @@
</list-component>
</div>
</div>
{{-- tab for report & Analysis --}}
<div class="row tabsContainer tabContent" tab-name="ReportMapped">
<div class="col">
<report-transactions-mapped-component></report-transactions-mapped-component>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,8 @@
@extends('layouts.base_portal')
@section('inner_content')
<div class="row" v-if="$store.getters.isAdmin">
<div class="col">
<bulk-download-invoice-component section="BulkDownloadInvoiceComponent"></bulk-download-invoice-component>
</div>
</div>
@endsection
+1 -64
View File
@@ -1,67 +1,4 @@
@extends('layouts.base_portal')
@section('inner_content')
<div class="row">
<div class="col-12 col-md-6 no-padding">
<download-billing-with-dates-component section="paymentsReportSection" ></download-billing-with-dates-component>
</div>
</div>
<div class="row">
<div class="col bg-white p-t-15 p-b-15">
<div class="row no-margin">
<div class="col-12 col-md-4">
<div class="row m-b-15 p-b-10 b-b b-grey">
<div class="col">
<small class="all-caps muted fs-10">Complete Payments</small>
</div>
</div>
<div class="row">
<div class="col">
<list-component key="2" section="paymentVerificationSection" :endpoint="route('api.transaction.list')" :options="{'type': 1, 'status': 3, 'payment_method_not_in': [4]}">
<template slot="list" slot-scope="{data}">
<payment-verification-component section="paymentVerificationSection" :data="data" :no_action="true"></payment-verification-component>
</template>
</list-component>
</div>
</div>
</div>
<div class="col-12 col-md-4">
<div class="row m-b-15 p-b-10 b-b b-grey">
<div class="col">
<small class="all-caps muted fs-10">Complete Currency Orders</small>
</div>
</div>
<div class="row">
<div class="col">
<list-component key="2" section="completeTransactionGroupsListSection" :options="{'per_page': 20, 'status': 3}" :endpoint="route('api.transaction.group.list')">
<template slot="list" slot-scope="{data}">
<transaction-group-component section="transactionGroupsListSection" :data="data"></transaction-group-component>
</template>
</list-component>
{{--<list-component key="2" section="currencyOrdersListSection" :options="{'per_page': 20, 'document_type_in': ['CURRENCY_VENDOR_ORDER'], 'status': 2, 'with_company': true}" :endpoint="route('api.document.list')">--}}
{{--<template slot="list" slot-scope="{data}">--}}
{{--<currency-order-component section="currencyOrdersListSection" :data="data"></currency-order-component>--}}
{{--</template>--}}
{{--</list-component>--}}
</div>
</div>
</div>
<div class="col-12 col-md-4">
<div class="row m-b-15 p-b-10 b-b b-grey">
<div class="col">
<small class="all-caps muted fs-10">Open Currency Orders</small>
</div>
</div>
<div class="row">
<div class="col">
<list-component key="2" section="transactionGroupsListSection" :options="{'per_page': 20, 'status_in': [0, 1, 2]}" :endpoint="route('api.transaction.group.list')">
<template slot="list" slot-scope="{data}">
<transaction-group-component section="transactionGroupsListSection" :data="data"></transaction-group-component>
</template>
</list-component>
</div>
</div>
</div>
</div>
</div>
</div>
<payment-component></payment-component>
@endsection
@@ -69,109 +69,8 @@
<br>
<br>
<table class="line-table" style="overflow: wrap" autosize="1">
<!-- Table Header -->
<thead>
<tr>
<th width="5%">No</th>
<th class="stock-code" width="10%">Stock Code</th>
<th class="description">Description</th>
<th width="10%">Quantity</th>
<th width="15%">Unit Price (RM)</th>
<th width="10%">Total Amount<br>(RM)</th>
</tr>
</thead>
<tbody>
@php
$subtotal = "0";
$voucherDiscount = $voucher_redemption ? bcmul((string)$voucher_redemption->value, "-1", 5) : "0";
$displayedSubtotal = "0";
$exactTotal = "0";
@endphp
@include('pages.pdfs.purchase_order_table')
@foreach ($po_order_transaction->transactionDetails as $key => $transaction_detail)
@php
$exactUnitPrice = bcdiv($transaction_detail->price, $transaction->currency_rate, 5);
$itemTotal = bcmul($exactUnitPrice, $transaction_detail->quantity, 5);
// Round half to even for displayed item total
$displayedItemTotal = round(bcmul($exactUnitPrice, $transaction_detail->quantity, 2), 2, PHP_ROUND_HALF_EVEN);
$displayedSubtotal = bcadd($displayedSubtotal, $displayedItemTotal, 2);
$subtotal = bcadd($subtotal, $itemTotal, 5);
$exactTotal = bcadd($exactTotal, $displayedItemTotal, 5);
@endphp
<tr>
<td width="5%" class="center top">{{ $key + 1 }}</td>
<td class="stock-code top" width="10%">{{ $transaction_detail->product_code }}</td>
<td class="description">{{ $transaction_detail->product_name }}</td>
<td width="10%" class="center top">{{ $transaction_detail->quantity }}</td>
<td width="15%" class="center top">
{{ number_format($exactUnitPrice, 2) }}
</td>
<td width="20%" class="right top">
{{ number_format($itemTotal, 2) }}
</td>
</tr>
@endforeach
</tbody>
<tfoot>
@php
$subtotalWithDiscount = bcsub($subtotal, $voucherDiscount, 5);
@endphp
<tr class="subtotal">
<td colspan="4"></td>
<td class="right middle">Subtotal</td>
<td class="right middle">{{ number_format($subtotal, 2) }}</td>
</tr>
<tr class="billingcharges">
<td colspan="4"></td>
<td class="right">Service Charges</td>
<td class="right">{{ number_format($transaction->service_charge, 2) }}</td>
</tr>
@if($voucher_redemption)
<tr class="voucher">
<td colspan="4"></td>
<td class="right middle">Voucher ({{ $voucher_redemption->voucher->code }})</td>
<td class="right middle">-{{ number_format($voucherDiscount, 2) }}</td>
</tr>
@endif
@if($transaction->tax > 0)
<tr class="billingcharges">
<td colspan="4"></td>
<td class="right">Tax</td>
<td class="right">{{ number_format($transaction->tax, 2) }}</td>
</tr>
@endif
@php
// Calculate the totals with 5 decimal places
$expectedTotal = bcadd(bcadd(bcadd($subtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
// Calculate the displayed totals with 2 decimal places
$displayedTotal = bcadd(bcadd(bcadd($displayedSubtotal, $transaction->service_charge, 2), $transaction->tax, 2), $voucherDiscount, 2);
// Calculate the discrepancy
$discrepancy = bcsub($expectedTotal, $displayedTotal, 5);
// Calculate the final total
$total = bcadd($expectedTotal, $discrepancy, 5);
@endphp
<tr>
<td colspan="4"></td>
<td class="right middle">Adjustment</td>
<td class="right middle">{{number_format($discrepancy, 5)}}</td>
</tr>
<tr>
<td colspan="4"></td>
<td class="right middle">Total</td>
<td class="total right middle">
{{ number_format($total, 2) }}
</td>
</tr>
</tfoot>
</table>
<htmlpagefooter name="page-footer">
<table width="100%">
<tr>
+2 -91
View File
@@ -58,97 +58,8 @@
<br>
<!-- Invoice Table -->
<table class="line-table" style="overflow: wrap" autosize="1">
<!-- Table Header -->
<thead>
<tr>
<th width="5%">No</th>
<th class="stock-code" width="10%">Stock Code</th>
<th class="description">Description</th>
<th width="10%">Quantity</th>
<th width="15%">Unit Price (RM)</th>
<th width="10%">Total Amount<br>(RM)</th>
</tr>
</thead>
<tbody>
@php
$subtotal = "0";
$voucherDiscount = $voucher_redemption ? bcmul((string)$voucher_redemption->value, "-1", 2) : "0";
$displayedSubtotal = 0;
@endphp
@include('pages.pdfs.purchase_order_table')
@foreach ($po_order_transaction->transactionDetails as $key => $transaction_detail)
@php
$exactUnitPrice = bcdiv($transaction_detail->price, $transaction->currency_rate, 7);
$itemTotal = bcmul($exactUnitPrice, $transaction_detail->quantity, 5);
$displayedItemTotal = bcmul($exactUnitPrice, $transaction_detail->quantity, 2);
$displayedSubtotal = bcadd($displayedSubtotal, $displayedItemTotal, 2);
$subtotal = bcadd($subtotal, $itemTotal, 5);
@endphp
<tr>
<td width="5%" class="center top">{{ $key + 1 }}</td>
<td class="stock-code top" width="10%">{{ $transaction_detail->product_code }}</td>
<td class="description">{{ $transaction_detail->product_name }}</td>
<td width="10%" class="center top">{{ $transaction_detail->quantity }}</td>
<td width="15%" class="center top">
{{ number_format($exactUnitPrice, 2) }}
</td>
<td width="20%" class="right top">
{{ number_format($itemTotal, 2) }}
</td>
</tr>
@endforeach
</tbody>
<tfoot>
@php
$subtotalWithDiscount = bcsub($subtotal, $voucherDiscount, 5);
@endphp
<tr class="subtotal">
<td colspan="4"></td>
<td class="right middle">Subtotal</td>
<td class="right middle">{{ number_format($subtotal, 2) }}</td>
</tr>
<tr class="billingcharges">
<td colspan="4"></td>
<td class="right">Service Charges</td>
<td class="right">{{ number_format($transaction->service_charge, 2) }}</td>
</tr>
@if($voucher_redemption)
<tr class="voucher">
<td colspan="4"></td>
<td class="right middle">Voucher ({{ $voucher_redemption->voucher->code }})</td>
<td class="right middle">-{{ number_format($voucherDiscount, 2) }}</td>
</tr>
@endif
@if($transaction->tax > 0)
<tr class="billingcharges">
<td colspan="4"></td>
<td class="right">Tax</td>
<td class="right">{{ number_format($transaction->tax, 2) }}</td>
</tr>
@endif
@php
$displayedTotal = bcadd(bcadd(bcadd($subtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
$expectedTotal = bcadd(bcadd(bcadd($subtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
$discrepancy = bcsub($displayedTotal, $expectedTotal, 5);
$total = bcadd(bcadd(bcadd($subtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
@endphp
<tr>
<td colspan="4"></td>
<td class="right middle">Adjustment</td>
<td class="right middle">{{number_format($discrepancy, 5)}}</td>
</tr>
<tr>
<td colspan="4"></td>
<td class="right middle">Total</td>
<td class="total right middle">
{{ number_format($total, 2) }}
</td>
</tr>
</tfoot>
</table>
<br>
<div class="note">
<strong>Note:</strong> All items purchased are subject to our Terms & Conditions. Please refer to our official website for more information.
@@ -158,6 +69,6 @@
Please transfer the payment to:<br>
Bank: Maybank Berhad<br>
Account Name: CIEF Worldwide Sdn Bhd<br>
Account No: 564892103405<br>
Account No: 568603010762<br>
</div>
@endsection
@@ -76,109 +76,8 @@
<br>
<table class="line-table" style="overflow: wrap" autosize="1">
<!-- Table Header -->
<thead>
<tr>
<th width="5%">No</th>
<th class="stock-code" width="10%">Stock Code</th>
<th class="description">Description</th>
<th width="10%">Quantity</th>
<th width="15%">Unit Price (RM)</th>
<th width="10%">Total Amount<br>(RM)</th>
</tr>
</thead>
<tbody>
@php
$subtotal = "0";
$voucherDiscount = $voucher_redemption ? bcmul((string)$voucher_redemption->value, "-1", 5) : "0";
$displayedSubtotal = "0";
$exactTotal = "0";
@endphp
@include('pages.pdfs.purchase_order_table')
@foreach ($po_order_transaction->transactionDetails as $key => $transaction_detail)
@php
$exactUnitPrice = bcdiv($transaction_detail->price, $transaction->currency_rate, 5);
$itemTotal = bcmul($exactUnitPrice, $transaction_detail->quantity, 5);
// Round half to even for displayed item total
$displayedItemTotal = round(bcmul($exactUnitPrice, $transaction_detail->quantity, 2), 2, PHP_ROUND_HALF_EVEN);
$displayedSubtotal = bcadd($displayedSubtotal, $displayedItemTotal, 2);
$subtotal = bcadd($subtotal, $itemTotal, 5);
$exactTotal = bcadd($exactTotal, $displayedItemTotal, 5);
@endphp
<tr>
<td width="5%" class="center top">{{ $key + 1 }}</td>
<td class="stock-code top" width="10%">{{ $transaction_detail->product_code }}</td>
<td class="description">{{ $transaction_detail->product_name }}</td>
<td width="10%" class="center top">{{ $transaction_detail->quantity }}</td>
<td width="15%" class="center top">
{{ number_format($exactUnitPrice, 2) }}
</td>
<td width="20%" class="right top">
{{ number_format($itemTotal, 2) }}
</td>
</tr>
@endforeach
</tbody>
<tfoot>
@php
$subtotalWithDiscount = bcsub($subtotal, $voucherDiscount, 5);
@endphp
<tr class="subtotal">
<td colspan="4"></td>
<td class="right middle">Subtotal</td>
<td class="right middle">{{ number_format($subtotal, 2) }}</td>
</tr>
<tr class="billingcharges">
<td colspan="4"></td>
<td class="right">Service Charges</td>
<td class="right">{{ number_format($transaction->service_charge, 2) }}</td>
</tr>
@if($voucher_redemption)
<tr class="voucher">
<td colspan="4"></td>
<td class="right middle">Voucher ({{ $voucher_redemption->voucher->code }})</td>
<td class="right middle">-{{ number_format($voucherDiscount, 2) }}</td>
</tr>
@endif
@if($transaction->tax > 0)
<tr class="billingcharges">
<td colspan="4"></td>
<td class="right">Tax</td>
<td class="right">{{ number_format($transaction->tax, 2) }}</td>
</tr>
@endif
@php
// Calculate the totals with 5 decimal places
$expectedTotal = bcadd(bcadd(bcadd($subtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
// Calculate the displayed totals with 2 decimal places
$displayedTotal = bcadd(bcadd(bcadd($displayedSubtotal, $transaction->service_charge, 2), $transaction->tax, 2), $voucherDiscount, 2);
// Calculate the discrepancy
$discrepancy = bcsub($expectedTotal, $displayedTotal, 5);
// Calculate the final total
$total = bcadd($expectedTotal, $discrepancy, 5);
@endphp
<tr>
<td colspan="4"></td>
<td class="right middle">Adjustment</td>
<td class="right middle">{{number_format($discrepancy, 5)}}</td>
</tr>
<tr>
<td colspan="4"></td>
<td class="right middle">Total</td>
<td class="total right middle">
{{ number_format($total, 2) }}
</td>
</tr>
</tfoot>
</table>
<htmlpagefooter name="page-footer">
<table width="100%">
<tr>
@@ -0,0 +1,92 @@
<table class="line-table" style="overflow: wrap" autosize="1">
<!-- Table Header -->
<thead>
<tr>
<th width="5%">No</th>
<th class="stock-code" width="10%">Stock Code</th>
<th class="description">Description</th>
<th width="10%">Quantity</th>
<th width="15%">Unit Price (RM)</th>
<th width="10%">Total Amount<br>(RM)</th>
</tr>
</thead>
<tbody>
@php
$subtotal = "0";
$voucherDiscount = $voucher_redemption ? bcmul((string)$voucher_redemption->value, "-1", 2) : "0";
$displayedSubtotal = 0;
@endphp
@foreach ($po_order_transaction->transactionDetails as $key => $transaction_detail)
@php
$exactUnitPrice = bcdiv($transaction_detail->price, $transaction->currency_rate, 7);
$displayUnitPrice = round($exactUnitPrice, 2);
$itemTotal = bcmul($exactUnitPrice, $transaction_detail->quantity, 5);
$displayedItemTotal = round(bcmul($displayUnitPrice, $transaction_detail->quantity, 7), 2);
$displayedSubtotal = bcadd($displayedSubtotal, $displayedItemTotal, 2);
$subtotal = bcadd($subtotal, $itemTotal, 5);
@endphp
<tr>
<td width="5%" class="center top">{{ $key + 1 }}</td>
<td class="stock-code top" width="10%">{{ $transaction_detail->product_code }}</td>
<td class="description">{{ $transaction_detail->product_name }}</td>
<td width="10%" class="center top">{{ $transaction_detail->quantity }}</td>
<td width="15%" class="center top">
{{ number_format($displayUnitPrice, 2) }}
</td>
<td width="20%" class="right top">
{{ number_format($displayedItemTotal, 2) }}
</td>
</tr>
@endforeach
</tbody>
<tfoot>
@php
$subtotalWithDiscount = bcsub($subtotal, $voucherDiscount, 5);
@endphp
<tr class="subtotal">
<td colspan="4"></td>
<td class="right middle">Subtotal</td>
<td class="right middle">{{ number_format($displayedSubtotal, 2) }}</td>
</tr>
<tr class="billingcharges">
<td colspan="4"></td>
<td class="right">Service Charges</td>
<td class="right">{{ number_format($transaction->service_charge, 2) }}</td>
</tr>
@if($voucher_redemption)
<tr class="voucher">
<td colspan="4"></td>
<td class="right middle">Voucher ({{ $voucher_redemption->voucher->code }})</td>
<td class="right middle">-{{ number_format($voucherDiscount, 2) }}</td>
</tr>
@endif
@if($transaction->tax > 0)
<tr class="billingcharges">
<td colspan="4"></td>
<td class="right">Tax</td>
<td class="right">{{ number_format($transaction->tax, 2) }}</td>
</tr>
@endif
@php
$displayedTotal = bcadd(bcadd(bcadd($displayedSubtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
$expectedTotal = bcadd(bcadd(bcadd($subtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
$discrepancy = bcsub($expectedTotal, $displayedTotal, 5);
$total = bcadd(bcadd(bcadd($subtotal, $transaction->service_charge, 5), $transaction->tax, 5), $voucherDiscount, 5);
@endphp
<tr>
<td colspan="4"></td>
<td class="right middle">Adjustment</td>
<td class="right middle">{{number_format($discrepancy, 5)}}</td>
</tr>
<tr>
<td colspan="4"></td>
<td class="right middle">Total</td>
<td class="total right middle">
{{ number_format($total, 2) }}
</td>
</tr>
</tfoot>
</table>
@@ -1,5 +1,5 @@
<div class="row">
<div class="col">
<customer-transaction-section-component :id="{{$id}}"></customer-transaction-section-component>
<customer-transaction-section-component :id="{{$id}}" :wallet_id="{{$wallet_id}}"></customer-transaction-section-component>
</div>
</div>
+1 -1
View File
@@ -10,7 +10,7 @@ Route::group(['prefix' => 'accounting', 'as' => 'accounting.', 'namespace' => 'A
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::post('bankStatement/{id}/details/{status}', 'ApproveDuplicateBankStatementDetailsStatusController@update')->where('status', 'approve|reject|pending_verification')->name('bankStatement.details.status.update');
Route::group(['prefix' => 'statement_transaction', 'as' => 'statement_transaction.'], function () {
Route::post('/owner/group-approve', 'GroupApproveStatementTransactionController@approve')->name('owner.groupApprove');
+2
View File
@@ -32,6 +32,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
Route::post('/import/upload-honey-trap', 'Imports\ImportHoneyTrapController@import')->name('honey_trap.upload');
Route::post('/import/upload-import-invoices', 'Imports\ImportStatementInvoiceController@import')->name('import_invoices.upload');
Route::post('/import/upload-import-receipt', 'Imports\ImportStatementReceiptsController@import')->name('import_receipts.upload');
Route::post('/customers/invoices', 'Companies\BulkDownloadCustomerInvoicesController@download')->name('customers.invoices');
Route::post('/supplier/white-form/bulk-download', 'Companies\BulkDownloadSupplierWhiteFormsController@download')->name('suppliers.white_forms');
require __DIR__ . '/company.php';
+1
View File
@@ -19,6 +19,7 @@ Route::group(['prefix' => 'booking', 'as' => 'booking.', 'namespace' => 'Booking
Route::post('create', 'CreateBookingPaymentController@create')->name('create');
Route::post('{payment_id}/verification/create', 'CreatePaymentVerificationController@create')->name('verification.create');
Route::put('/{payment_id}/approval/{status}', 'ApprovePaymentVerificationController@approve')->where('status', 'approve|reject')->name('approval');
Route::post('delete', 'ExpireBookingPaymentController@expire')->name('expire');
});
Route::group(['prefix' => '{id}/refund', 'as' => 'refund.'], function () {
+1
View File
@@ -16,6 +16,7 @@ Route::group(['prefix' => 'transactions', 'namespace' => 'Transactions', 'as' =>
route::delete('{id}/bill/delete', 'DeletePaymentProofDocumentController@delete')->name('bill.delete');
Route::post('booking/{id}/details/update', 'CreatePurchaseOrderTransactionController@create')->name('po.create');
Route::post('booking/{id}/details/import', 'ImportPurchaseOrderTransactionController@import')->name('po.import');
Route::get('bulk/po/{issuer_id}/{start_date}/{end_date}', 'CreateBulkPurchaseOrderTransactionController@create')->name('po.bulk.create');
+11 -6
View File
@@ -141,6 +141,10 @@ Route::get('/purchase_orders', function () {
return view('pages.purchase_orders');
})->name('purchase_orders');
Route::get('/customers/invoices', function () {
return view('pages.customer_invoices_bulk_download');
})->name('customers.invoices');
Route::get('/support', function () {
return view('pages.customer_support', [
'marking' => null,
@@ -227,14 +231,13 @@ Route::get('/fix_bills', function () {
})->name('products.random');
Route::get('/wallet/{marking}/details', function ($marking) {
$id = \App\Models\Company::where('reference', '=', $marking)->first()->id;
return view('pages.wallet.index', ['id' => $id]);
$company = \App\Models\Company::where('reference', '=', $marking)->first();
$id = $company->id;
$wallet_id = $company->wallets()->first()->id;
return view('pages.wallet.index', ['id' => $id, 'wallet_id' => $wallet_id]);
})->name('wallet.details');
Route::get('/wallet/{marking}/details-precise', function ($marking) {
$id = \App\Models\Company::where('reference', '=', $marking)->first()->id;
return view('pages.wallet.precise', ['id' => $id]);
})->name('wallet.details-precise');
Route::get('/wallet/{marking}/{is_precise}/export', 'Exports\ExportCustomersWalletTransactionToExcelController@export')->name('wallet.details-export');
Route::get('/wallets', function () {
return view('pages.wallet.wallets');
@@ -258,6 +261,8 @@ Route::get('/export/payment-transactions/f614e339d7058904a831aad742e24d55', 'Exp
Route::get('/export/wallet-transactions/f614e339d7058904a831aad742e24d55', 'Exports\ExportCustomersToExcelController@walletTransactions')->name('walletTransactions.export');
Route::get('/export/booking-transactions', 'Exports\ExportCustomersToExcelController@bookingTransactions')->name('export.transactions.booking');
Route::get('/export/invoice-transactions/f614e339d7058904a831aad742e24d55', 'Exports\ExportCustomersToExcelController@invoiceTransactions')->name('invoiceTransactions.export');
Route::get('/export/receipt-transactions/f614e339d7058904a831aad742e24d55', 'Exports\ExportCustomersToExcelController@receiptTransactions')->name('receiptTransactions.export');
Route::get('/export/imported-invoice-mapped', 'Exports\ExportCustomersToExcelController@importedInvoiceMapped')->name('importedInvoiceMapped.export');
Route::get('/products', function (\App\Classes\Modules\Exports\Services\ExportsProducts $exportsProducts) {
$bookings = Booking::where(function($query){