Mapping of bank statement records with exchange and shipping portal + UI to edit the details

This commit is contained in:
Dillon
2023-04-11 04:14:53 +08:00
parent 7ca23edb9b
commit 1954cfa63c
25 changed files with 1326 additions and 19 deletions
@@ -0,0 +1,19 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class DateIn implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereIn('date', $value);
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class HasAccountStatementId implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereHas('statementTransaction', function ($query) use ($value) {
$query->where('account_statement_id', $value);
});
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class PayFor implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('pay_for', $value);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class PayForIn implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->whereIn('pay_for', $value);
}
}
@@ -0,0 +1,40 @@
<?php
namespace App\Classes\Jobs;
use App\Classes\Modules\Accounting\Processors\CreateBankStatementDetailsProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\AccountStatement;
class CreateBankStatementDetails implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** @var AccountStatement $statement*/
private $statement;
/**
* CreateBankStatementDetails constructor.
* @param AccountStatement $statement
*/
public function __construct(AccountStatement $statement)
{
$this->statement = $statement;
}
public function handle()
{
(App()->make(CreateBankStatementDetailsProcessor::class))->execute($this->statement);
}
public function delay($delay)
{
// Add delay in seconds to the job
$this->delay = $delay;
return $this;
}
}
@@ -0,0 +1,52 @@
<?php
namespace App\Classes\Modules\Accounting\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Accounting\Services\ListsBankStatementDetails;
use App\Http\Resources\BankStatementDetailResource;
use ErrorException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ListBankStatementDetailsLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Retrieved Bank Statement Details',
'message' => 'You have successfully retrieved a Bank Statement Details'
];
}
/** @var ListsBankStatementDetails */
private $listsBankStatementDetails;
/**
* ListBankStatementDetailsLogic constructor.
* @param ListsBankStatementDetails $listsBankStatementDetails
*/
public function __construct(ListsBankStatementDetails $listsBankStatementDetails)
{
$this->listsBankStatementDetails = $listsBankStatementDetails;
}
/**
* @param Request $request
* @return JsonResponse
* @throws ErrorException
*/
public function logic(Request $request) : JsonResponse
{
$query = $this->listsBankStatementDetails->execute($this->listsBankStatementDetails->deserializeFilters($request->input('filters')));
return $this->collectionResponse(BankStatementDetailResource::collection($query));
}
}
@@ -0,0 +1,68 @@
<?php
namespace App\Classes\Modules\Accounting\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Accounting\Services\UpdatesBankStatementDetails;
use App\Classes\Modules\Accounting\Services\FetchesBankStatementDetails;
use App\Classes\Modules\Accounting\Standards\Rules\CanUpdateCompany;
use App\Classes\Modules\Accounting\DataTransferObjects\BankStatementDetailObject;
use App\Http\Resources\BankStatementDetailResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use ErrorException;
class UpdateBankStatementDetailLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Updated Bank Statement Transactions Details',
'message' => 'You have successfully updated the Bank Statement Transactions Details'
];
}
// /** @var CanUpdateCompany */
// private $canUpdateCompany;
/** @var UpdatesBankStatementDetails */
private $updatesBankStatementDetails;
/** @var FetchesBankStatementDetails */
private $fetchesBankStatementDetails;
/**
* UpdateBankStatementDetailLogic constructor.
* @param UpdatesBankStatementDetails $updatesBankStatementDetails
* @param FetchesBankStatementDetails $fetchesBankStatementDetails
*/
public function __construct(UpdatesBankStatementDetails $updatesBankStatementDetails, FetchesBankStatementDetails $fetchesBankStatementDetails)
{
$this->updatesBankStatementDetails = $updatesBankStatementDetails;
$this->fetchesBankStatementDetails = $fetchesBankStatementDetails;
}
/**
* @param Request $request
* @return JsonResponse
* @throws ErrorException
*/
public function logic(Request $request) : JsonResponse
{
$object = new BankStatementDetailObject($request->input('pay_for'), $request->input('system_references'));
// $this->canUpdateCompany->passes($object);
$query = $this->fetchesBankStatementDetails->execute(['id' => $request->route('id')]);
$query = $this->updatesBankStatementDetails->execute($query, $object);
return $this->resourceResponse(new BankStatementDetailResource($query));
}
}
@@ -0,0 +1,46 @@
<?php
namespace App\Classes\Modules\Accounting\DataTransferObjects;
use App\Classes\General\Interfaces\DataTransferObject;
class BankStatementDetailObject implements DataTransferObject
{
/** @var string */
private $pay_for;
/** @var string */
private $system_references;
// /** @var int|null */
// private $type;
/**
* BankStatementDetailObject constructor.
* @param string $pay_for
* @param string $system_references
* @param int|null $type
*/
public function __construct(string $pay_for, string $system_references)
{
$this->pay_for = $pay_for;
$this->system_references = $system_references;
}
/**
* @return string
*/
public function getPayFor(): string
{
return $this->pay_for;
}
/**
* @return string
*/
public function getSystemReferences(): string
{
return $this->system_references;
}
}
@@ -0,0 +1,218 @@
<?php
namespace App\Classes\Modules\Accounting\Processors;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\AccountStatement;
use App\Models\Booking;
use App\Models\Company;
use App\Models\Group;
use App\Models\StatementTransactionsDetail;
use App\Models\Transaction;
use App\Models\Wallet;
use Illuminate\Support\Facades\Log;
use DateTime;
class CreateBankStatementDetailsProcessor
{
/**
* @param $transactions
* @return true
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(AccountStatement $statement) {
$transactions = $statement->transactions();
$transactions = $transactions->get();
$headers = [
'Date',
'Bank',
'Description',
'Credit',
'Debit',
'Pay For',
'System',
'System Reference',
'Human Reference',
'Multiple',
'Match?',
'System Amount'
];
$branches = [
0 => 'MBB Cyber',
1 => 'MBB SS2',
];
$yes = 'Yes';
$no = 'No';
$table = '<table><tr><th>'.implode('</th><th>', $headers).'</th></tr>';
$count = 0;
foreach ($transactions as $row) {
$isExist = StatementTransactionsDetail::where('statement_transactions_id', $row->id)->first();
if ($isExist) {
continue;
}
$count++;
$credit = 0.00;
$debit = 0.00;
$date = new DateTime($row['posting_date']);
$description = $row['transaction_description_2'];
if($row['amount'] < 0){
$debit = (float) $row['amount'];
}
else{
$credit = (float) $row['amount'];
}
$creditTransactions = [];
$debitTransactions = [];
$system = [];
$systemReference = null;
$systemAmount = null;
if($credit){
$creditTransactions = $this->getTransactions($date, $credit, TransactionType::PAYMENT, Booking::class, PaymentMethodType::WALLET, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
foreach ($creditTransactions as $transaction) {
$systemReference[] = $transaction->owner instanceof Booking ? $transaction->owner->marking : $transaction->bill_no;
$systemAmount[] = $transaction->amount;
$system[] = 'EXCHANGE';
}
$creditTransactions = $this->getTransactions($date, $credit, TransactionType::TOP_UP, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
foreach ($creditTransactions as $transaction) {
$systemReference[] = $transaction->owner instanceof Booking ? $transaction->owner->marking : $transaction->bill_no;
$systemAmount[] = $transaction->amount;
$system[] = 'EXCHANGE';
}
$creditTransactions = $this->getTransactionsFromShippingPortal($credit, $this->getDateRange($row['posting_date']));
foreach ($creditTransactions as $transaction) {
$systemReference[] = $transaction['order']['reference'];
$systemAmount[] = $transaction['amount'];
$system[] = 'SHIPPING';
}
}
if($debit){
$debitTransactions = $this->getTransactions($date, $debit, null, null, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED], Group::class);
if(!count($debitTransactions)) {
foreach (['YSN', 'HCK', 'ATVANTIC', 'HIGH HILL'] as $reference){
if(str_contains($description, $reference)) {
$paymentDate = date('Y-m-d', strtotime('+1 day', strtotime($row['posting_date']))); //$date->addDays(1)->format('Y-m-d');
if($reference = 'ATVANTIC'){
$paymentDate = date('Y-m-d', strtotime($row['posting_date']));//$date->format('Y-m-d');
}
$issuer = Company::where('name', 'like', '%'.$reference.'%')->get()->pluck('id');
$debitTransactions = Group::whereIn('issuer', $issuer)->whereDate('created_at', $paymentDate)->get();
break;
}
}
}
foreach ($debitTransactions as $transaction) {
$systemReference[] = $transaction->reference;
$systemAmount[] = $transaction->amount;
$system[] = 'EXCHANGE';
}
}
$multiple = count($creditTransactions) + count($debitTransactions) > 1 ? $yes : $no;
$systemReference = $systemReference ? implode(',', $systemReference) : null;
$systemAmount = $systemAmount ? implode(',', $systemAmount) : null;
$system = $system ? implode(',', $system) : null;
$matches = $systemReference == $row['remarkreferences'] ? $yes : $no;
$statementTransactionsDetail = new StatementTransactionsDetail([
'date' => $date,
'statement_transactions_id' => $row->id,
// 'description' => is_null($description) ? "" : $description,
// 'credit' => $credit,
// 'debit' => $debit,
'pay_for' => is_null($system) ? "" : $system,
'system_references' => is_null($systemReference) ? "" : $systemReference,
// 'remark_references' => is_null($row['remarkreferences']) ? "" : $row['remarkreferences'],
// 'is_multiple' => $multiple == "Yes" ? 1 : 0,
// 'is_matches' => $matches == "Yes" ? 1 : 0,
'system_amounts'=> is_null($systemAmount) ? "" : $systemAmount,
]);
$statementTransactionsDetail->save();
// if($count == 10){
// break;
// }
}
}
private function getTransactions($date, $amount, $type, $ownerType, $paymentMethod, $statuses, $model = Transaction::class) {
$query = $model::whereIn('status', $statuses)
->where(function ($query) use ($ownerType, $paymentMethod, $type) {
if ($ownerType) {
$query->where('owner_type', $ownerType);
}
if ($paymentMethod) {
$query->where('payment_method', '!=', $paymentMethod);
}
if ($type) {
$query->where('type', $type);
}
})
->whereDate('created_at', $date->format('Y-m-d'))
->where('amount', '>', ($amount - 0.01))
->where('amount', '<', ($amount + 0.01));
return $query->get();
}
private function getTransactionsFromShippingPortal($amount, $dateRange){
try{
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://izyim.cief-malaysia.com/public/api/v1/list?api-key=510acd13d8d24375cf038ad626c282565451461a9c2399357e0b65365300787e&filters={"order_by":{"column":"id","DESC":true},"status_in":[2],"type":2,"created_after":"'.$dateRange['start_date'].'","created_before":"'.$dateRange['end_date'].'","amount_exceed":'.($amount - 0.01).',"amount_short":'.($amount + 0.01).'}');
$body = $response->getBody();
$data = json_decode($body, true);
$payload = $data['payload'];
$transactions2 = $payload['data'];
return $transactions2;
}catch(\Exception $exception){
Log::error($exception);
return [];
}
}
private function getDateRange(string $dateStr) {
// Create a DateTime object from the input string
$date = strtotime($dateStr);
// Get the first day of the month
$today = date('Y-m-d', $date);
// Get the first day of the next month
$nextDay = date('Y-m-d', strtotime('+1 day', $date));
return [
'start_date' => $today,
'end_date' => $nextDay,
];
}
}
@@ -0,0 +1,34 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractFetchRecord;
use Illuminate\Database\Eloquent\Builder;
use App\Models\StatementTransactionsDetail;
class FetchesBankStatementDetails extends AbstractFetchRecord
{
/** @var StatementTransactionsDetail */
private $repository;
/**
* FetchesBankStatementDetails constructor.
* @param StatementTransactionsDetail $repository
*/
public function __construct(StatementTransactionsDetail $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
public function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractListRecord;
use Illuminate\Database\Eloquent\Builder;
use App\Models\StatementTransactionsDetail;
class ListsBankStatementDetails extends AbstractListRecord
{
/** @var StatementTransactionsDetail */
private $repository;
/**
* ListsBankStatementDetails constructor.
* @param StatementTransactionsDetail $repository
*/
public function __construct(StatementTransactionsDetail $repository)
{
$this->repository = $repository;
}
/**
* @return Builder
*/
public function getRepository(): Builder
{
return $this->repository->newQuery();
}
}
@@ -0,0 +1,25 @@
<?php
namespace App\Classes\Modules\Accounting\Services;
use App\Classes\General\Eloquent\AbstractUpdateRecord;
use App\Classes\Modules\Accounting\DataTransferObjects\BankStatementDetailObject;
use App\Models\StatementTransactionsDetail;
class UpdatesBankStatementDetails extends AbstractUpdateRecord
{
/**
* @param StatementTransactionsDetail $model
* @param BankStatementDetailsObject $object
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(StatementTransactionsDetail $model, BankStatementDetailObject $object)
{
$model->system_references = $object->getSystemReferences();
$model->pay_for = $object->getPayFor();
return $this->handler($model);
}
}
@@ -2,15 +2,32 @@
namespace App\Http\Controllers\Accounting;
use App\Classes\Jobs\CreateBankStatementDetails;
use App\Classes\Modules\Accounting\ControllersLogic\ListBankStatementDetailsLogic;
use App\Classes\Modules\Accounting\ControllersLogic\UpdateBankStatementDetailLogic;
use App\Classes\Modules\Imports\Services\BankStatementImport;
use App\Http\Controllers\Controller;
use DateTime;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Maatwebsite\Excel\Facades\Excel;
use App\Classes\Modules\Imports\Services\ImportsBankRecord;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\TransactionType;
use App\Models\StatementAccount;
use App\Models\AccountStatement;
use App\Models\StatementTransaction;
use App\Models\Booking;
use App\Models\Company;
use App\Models\Group;
use App\Models\StatementTransactionsDetail;
use App\Models\Transaction;
use App\Models\Wallet;
use App\Http\Controllers\Controller;
use Maatwebsite\Excel\Facades\Excel;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use DateTime;
use PhpOffice\PhpSpreadsheet\Shared\Date;
class BankStatementController extends Controller
{
@@ -44,6 +61,7 @@ class BankStatementController extends Controller
public function import(Request $request)
{
$request->validate([
'file' => 'required|mimes:csv,txt'
]);
@@ -83,17 +101,21 @@ class BankStatementController extends Controller
'end_balance' => $endBalance,
]);
$existingStatement = AccountStatement::where('date_from', $dateFrom)
->where('date_to', $dateTo)
->where('statement_account_id', $account->id)
->first();
// $existingStatement = AccountStatement::where('date_from', $dateFrom)
// ->where('date_to', $dateTo)
// ->where('statement_account_id', $account->id)
// ->first();
if ($existingStatement) {
return redirect()->back()->with('error', 'This statement has already been imported.');
}
// // if ($existingStatement) {
// // return redirect()->back()->with('error', 'This statement has already been imported.');
// // }
$account->statements()->save($statement);
CreateBankStatementDetails::dispatch($statement)->delay(30);
// $account = null;
$newTransactions = $sheet->map(function ($row) use ($statement, $account) {
$transactionRef = $row[15];
$amount = $row[17] !== '-' ? ((float) str_replace(',', '', $row[17])) : (-((float) str_replace(',', '', $row[16])));
@@ -141,13 +163,17 @@ class BankStatementController extends Controller
return $transaction;
});
// dd(json_encode($newTransactions));
return redirect()->back()->with('success', 'Statement imported successfully.')->with('newTransactions', $newTransactions);
}
public function show(AccountStatement $statement, Request $request)
{
$transactions = $statement->transactions();
// $account = $statement->account();
// dd(json_encode($account->where('id', '>=', 1)->first()));
// dd(json_encode($transactions->where('id', '>=', 1)->first()));
if ($request->get('transaction_filter')) {
$transactionFilter = $request->get('transaction_filter');
$transactions = $transactions->where('transaction_description', 'LIKE', "%$transactionFilter%");
@@ -163,9 +189,11 @@ class BankStatementController extends Controller
$transactions = $transactions->where('amount', '<=', $toAmountFilter);
}
$transactions = $transactions->paginate(100);
// $transactions = $transactions->paginate(100);
$transactions = $transactions->get();
echo $this->process3_merged($transactions);
return view('pages.accounting.bank-statements.show', compact('statement', 'transactions'));
//return view('pages.accounting.bank-statements.show', compact('statement', 'transactions'));
}
public function download(AccountStatement $statement)
@@ -176,4 +204,233 @@ class BankStatementController extends Controller
$csvExporter->build($transactions, ['transaction_date', 'transaction_time', 'posting_date', 'transaction_description', 'transaction_ref', 'debit', 'credit'])
->download($statement->date_from->format('Y-m-d') . '_' . $statement->date_to->format('Y-m-d') . '_statement.csv');
}
public function fetch(Request $request, ListBankStatementDetailsLogic $logic): JsonResponse
{
return $logic->execute($request);
}
public function update(Request $request, UpdateBankStatementDetailLogic $logic): JsonResponse
{
return $logic->execute($request);
}
private function process3_merged($transactions){
// $statement = $transactions[0]->statement();
// dd(json_encode($statement->first()));
$headers = [
'Date',
'Bank',
'Description',
'Credit',
'Debit',
'Pay For',
'System',
'System Reference',
'Human Reference',
'Multiple',
'Match?',
'System Amount'
];
$branches = [
0 => 'MBB Cyber',
1 => 'MBB SS2',
];
$yes = 'Yes';
$no = 'No';
$table = '<table><tr><th>'.implode('</th><th>', $headers).'</th></tr>';
$count = 0;
foreach ($transactions as $row) {
$isExist = StatementTransactionsDetail::where('statement_transactions_id', $row->id)->first();
if ($isExist) {
continue;
}
$count++;
$credit = 0.00;
$debit = 0.00;
// dd(json_encode($row['posting_date']));
$date = new DateTime($row['posting_date']);
$description = $row['transaction_description_2'];
if($row['amount'] < 0){
$debit = (float) $row['amount'];
}
else{
$credit = (float) $row['amount'];
}
$creditTransactions = [];
$debitTransactions = [];
$system = '';
$systemReference = null;
$systemAmount = null;
if($credit){
$creditTransactions = $this->getTransactions($date, $credit, TransactionType::PAYMENT, Booking::class, PaymentMethodType::WALLET, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
foreach ($creditTransactions as $transaction) {
$systemReference[] = $transaction->owner instanceof Booking ? $transaction->owner->marking : $transaction->bill_no;
$systemAmount[] = $transaction->amount;
$system[] = 'EXCHANGE';
}
$creditTransactions = $this->getTransactions($date, $credit, TransactionType::TOP_UP, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]);
foreach ($creditTransactions as $transaction) {
$systemReference[] = $transaction->owner instanceof Booking ? $transaction->owner->marking : $transaction->bill_no;
$systemAmount[] = $transaction->amount;
$system[] = 'EXCHANGE';
}
$creditTransactions = $this->getTransactionsFromShippingPortal($credit, $this->getDateRange($row['posting_date']));
foreach ($creditTransactions as $transaction) {
$systemReference[] = $transaction['order']['reference'];
$systemAmount[] = $transaction['amount'];
$system[] = 'SHIPPING';
}
}
if($debit){
$debitTransactions = $this->getTransactions($date, $debit, null, null, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED], Group::class);
if(!count($debitTransactions)) {
foreach (['YSN', 'HCK', 'ATVANTIC', 'HIGH HILL'] as $reference){
if(str_contains($description, $reference)) {
$paymentDate = date('Y-m-d', strtotime('+1 day', strtotime($row['posting_date']))); //$date->addDays(1)->format('Y-m-d');
if($reference = 'ATVANTIC'){
$paymentDate = date('Y-m-d', strtotime($row['posting_date']));//$date->format('Y-m-d');
}
$issuer = Company::where('name', 'like', '%'.$reference.'%')->get()->pluck('id');
$debitTransactions = Group::whereIn('issuer', $issuer)->whereDate('created_at', $paymentDate)->get();
break;
}
}
}
foreach ($debitTransactions as $transaction) {
$systemReference[] = $transaction->reference;
$systemAmount[] = $transaction->amount;
$system[] = 'EXCHANGE';
}
}
$multiple = count($creditTransactions) + count($debitTransactions) > 1 ? $yes : $no;
$systemReference = $systemReference ? implode(',', $systemReference) : null;
$systemAmount = $systemAmount ? implode(',', $systemAmount) : null;
$matches = $systemReference == $row['remarkreferences'] ? $yes : $no;
$table .= '<tr>
<td>'.$date->format('d-m-Y').'</td>
<td>branch</td>
<td>'.$description.'</td>
<td>'.$credit.'</td>
<td>'.$debit.'</td>
<td>'.$row['pay_for'].'</td>
<td>'.$system.'</td>
<td>'.$systemReference.'</td>
<td>'.$row['remarkreferences'].'</td>
<td>'.$multiple.'</td>
<td>'.$matches.'</td>
<td>'.$systemAmount.'</td>
</tr>';
//AccountStatement
// $row->statement()->first()->id)
$statementTransactionsDetail = new StatementTransactionsDetail([
'date' => $date,
'statement_transactions_id' => $row->id,
'description' => is_null($description) ? "" : $description,
'credit' => $credit,
'debit' => $debit,
'pay_for' => $system,
'system_references' => is_null($systemReference) ? "" : $systemReference,
'remark_references' => is_null($row['remarkreferences']) ? "" : $row['remarkreferences'],
'is_multiple' => $multiple == "Yes" ? 1 : 0,
'is_matches' => $matches == "Yes" ? 1 : 0,
'system_amounts'=> is_null($systemAmount) ? "" : $systemAmount,
]);
$statementTransactionsDetail->save();
if($count == 10){
break;
}
}
$table .= '</table>';
return $table;
}
private function getTransactions($date, $amount, $type, $ownerType, $paymentMethod, $statuses, $model = Transaction::class) {
$query = $model::whereIn('status', $statuses)
->where(function ($query) use ($ownerType, $paymentMethod, $type) {
if ($ownerType) {
$query->where('owner_type', $ownerType);
}
if ($paymentMethod) {
$query->where('payment_method', '!=', $paymentMethod);
}
if ($type) {
$query->where('type', $type);
}
})
->whereDate('created_at', $date->format('Y-m-d'))
->where('amount', '>', ($amount - 0.01))
->where('amount', '<', ($amount + 0.01));
return $query->get();
}
private function getDateRange(string $dateStr) {
// Create a DateTime object from the input string
$date = strtotime($dateStr);
// Get the first day of the month
$today = date('Y-m-d', $date);
// Get the first day of the next month
$nextDay = date('Y-m-d', strtotime('+1 day', $date));
return [
'start_date' => $today,
'end_date' => $nextDay,
];
}
private function getTransactionsFromShippingPortal($amount, $dateRange){
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://izyim.cief-malaysia.com/public/api/v1/list?api-key=510acd13d8d24375cf038ad626c282565451461a9c2399357e0b65365300787e&filters={"order_by":{"column":"id","DESC":true},"status_in":[2],"type":2,"created_after":"'.$dateRange['start_date'].'","created_before":"'.$dateRange['end_date'].'","amount_exceed":'.($amount - 0.01).',"amount_short":'.($amount + 0.01).'}');
$body = $response->getBody();
$data = json_decode($body, true);
$payload = $data['payload'];
$transactions2 = $payload['data'];
// $filters = [
// ['field' => 'created_at', 'value' => '2023-03-01 08:07:00'],
// ];
// $transactions2 = $this->getTransactions3($transactions2, $filters);
return $transactions2;
}
}
@@ -0,0 +1,37 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class BankStatementDetailResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'account_number' => $this->statementTransaction->statement->account->number,
'account_type' => $this->statementTransaction->statement->account->type,
'account_name' => $this->statementTransaction->statement->account->name,
'account_statement_id' => $this->statementTransaction->statement->id,
'account_statement_date_from' => $this->statementTransaction->statement->date_from,
'account_statement_date_to' => $this->statementTransaction->statement->date_to,
'date' => $this->date,
'description' => $this->description,
'amount' => $this->statementTransaction->amount,
'pay_for' => $this->pay_for,
'system_references' => $this->system_references,
'transaction_description_1' => $this->statementTransaction->transaction_description,
'transaction_description_2' => $this->statementTransaction->transaction_description_2,
'transaction_description_3' => $this->statementTransaction->transaction_description_3,
'transaction_description_4' => $this->statementTransaction->transaction_description_4,
'transaction_description_5' => $this->statementTransaction->transaction_description_5,
];
}
}
+7 -2
View File
@@ -10,7 +10,7 @@ class StatementTransaction extends Model
use HasFactory;
protected $fillable = [
'statement_id',
'account_statement_id',
'transaction_date',
'posting_date',
'transaction_description',
@@ -31,11 +31,16 @@ class StatementTransaction extends Model
public function statement()
{
return $this->belongsTo(AccountStatement::class, 'statement_id', 'id');
return $this->belongsTo(AccountStatement::class, 'account_statement_id', 'id');
}
public function owner()
{
return $this->morphTo()->nullable();
}
public function statementDetail()
{
return $this->hasMany(StatementTransactionsDetail::class);
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class StatementTransactionsDetail extends Model
{
use HasFactory;
protected $fillable = [
'date',
'statement_transactions_id',
// 'description',
// 'credit',
// 'debit',
'pay_for',
'system_references',
// 'remark_references',
// 'is_multiple',
// 'is_matches',
'system_amounts',
];
public function statementTransaction()
{
return $this->belongsTo(StatementTransaction::class, 'statement_transactions_id', 'id');
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
<?php
return [
'base_url' => env('PERFEXCRM_BASE_URL', 'http://192.168.1.100:8084'), //cief todo: Update crm api domain here
'base_url' => env('PERFEXCRM_BASE_URL', 'http://192.168.1.101:8084'), //cief todo: Update crm api domain here
'api_key' => env('PERFEXCRM_API_KEY', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiZXhjaGFuZ2Utc2hpcHBpbmciLCJuYW1lIjoiRXhjaGFuZ2UgYW5kIFNoaXBwaW5nIFBvcnRhbCIsIkFQSV9USU1FIjoxNjc1MDg2Mzc4fQ.SGAHWl5stcxQwp55TBGeMRVTdlLeWQIbsvJh5glyVvs'),
'is_enabled' => env('PERFEXCRM_IS_ENABLED', 'true'),
];
@@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStatementTransactionsDetailsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('statement_transactions_details', function (Blueprint $table) {
$table->id();
$table->date('date');
$table->unsignedBigInteger('statement_transactions_id');
$table->foreign('statement_transactions_id')->references('id')->on('statement_transactions');
// $table->string('description');
// $table->decimal('credit', 8, 2);
// $table->decimal('debit', 8, 2);
$table->string('pay_for');
$table->string('system_references');
// $table->string('remark_references');
// $table->boolean('is_multiple')->default(false);
// $table->boolean('is_matches')->default(false);
$table->string('system_amounts');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('statement_transactions_details');
}
}
@@ -0,0 +1,90 @@
<template>
<div class="row">
<div class="col-12">
<validation-wrapper-component :validator="$v.pay_for">
<label>Pay For</label>
<input type="text" class="form-control" v-model="pay_for" >
</validation-wrapper-component>
</div>
<div class="col-12">
<validation-wrapper-component :validator="$v.system_references">
<label>System References</label>
<input type="text" class="form-control" v-model="system_references" >
</validation-wrapper-component>
</div>
<div class="col-12">
<div class="form-group row">
<label>Date: {{ item.date }}</label>
</div>
<div class="form-group row">
<label>Transaction Description 1: {{ item.transaction_description_1 }}</label>
</div>
<div class="form-group row">
<label>Transaction Description 2: {{ item.transaction_description_2 }}</label>
</div>
<div class="form-group row">
<label>Transaction Description 3: {{ item.transaction_description_3 }}</label>
</div>
<div class="form-group row">
<label>Transaction Description 4: {{ item.transaction_description_4 }}</label>
</div>
<div class="form-group row">
<label>Transaction Description 5: {{ item.transaction_description_5 }}</label>
</div>
</div>
<div class="col-12">
<div class="row">
<div class="col p-r-5">
<div data-dismiss="modal" class="btn btn-sm btn-default bg-master-lighter btn-block b-rad-none">Cancel</div>
</div>
<div class="col p-l-5">
<div data-dismiss="modal" class="btn btn-sm btn-success btn-block b-rad-none" @click="submitForm()">Update</div>
</div>
</div>
</div>
</div>
</template>
<script>
import componentHandler from '../../../general/mixins/componentHandler';
// import ModalFormHandler from '../../../general/mixins/modalFormHandler';
import { required } from "vuelidate/lib/validators";
// import FormHandler from '../../../general/mixins/formHandler';
export default {
data(){
return {
error: '',
pay_for: "",
system_references: ""
}
},
created() {
this.pay_for = this.item.pay_for,
this.system_references = this.item.system_references
},
validations: {
pay_for: { required },
system_references: { required }
},
watch: {
'data': function() {
this.pay_for = this.item.pay_for;
this.system_references = this.item.system_references;
}
},
methods: {
submitForm(){
this.parameters = {pay_for : this.pay_for, system_references: this.system_references};
this.item.pay_for = this.pay_for;
this.item.system_references = this.system_references;
this.submit(this.route('api.accounting.statement.details.update', this.data.id), 'put', this.section, true, true);
},
successHandler(){
// this.closeModal();
// this.formHandler('');
},
},
mixins: [componentHandler]
}
</script>
@@ -0,0 +1,227 @@
<template>
<div class="row h-100 parentContainer">
<div class="col-12" style="min-height: 20px;">
<loading-component style="height: 20px; top: 0;" key="1" color="success" v-show="isLoading"></loading-component>
</div>
<div class="col-12">
<div id="accordion">
<div class="card">
<div class="card-header" id="headingOne">
<h5 class="mb-0">
<button class="btn btn-link" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Filters
</button>
</h5>
</div>
<div id="collapseOne" class="collapse" aria-labelledby="headingOne" data-parent="#accordion">
<div class="card-group" style="margin-bottom: 0px;">
<div class="card">
<div class="card-header">
<h3 class="card-title">Pay For</h3>
<div class="card-tools">
<div class="form-check" v-for="(match, index) in matches">
<input class="form-check-input" type="checkbox" :value="match" :id="'match'+index" v-model="selected.matches">
<label class="form-check-label" :for="'match' + index">
{{ match }}
</label>
</div>
</div>
</div>
</div>
<div class="card" style="margin-top: 0px">
<div class="card-header">
<h3 class="card-title">Date</h3>
<div class="form-check" v-for="(day, index) in days" :key="day">
<input class="form-check-input" type="checkbox" :id="'day'+index" :value="day" v-model="selected.days">
<label class="form-check-label" :for="'day'+index">
{{ day }}
</label>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-end mb-2">
<button type="button" class="btn btn-sm btn-primary" @click="fetchList(true)">
<i class="fa fa-plus-square"></i>
Filter
</button>
</div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 v-if="$store.getters.getListData(section)[0] !== undefined" class="card-title">{{ $store.getters.getListData(section)[0].account_name }} - {{ $store.getters.getListData(section)[0].account_number }},{{ $store.getters.getListData(section)[0].account_type }} </h3><br/>
<h3 v-if="$store.getters.getListData(section)[0] !== undefined" class="card-title">{{ new Date($store.getters.getListData(section)[0].account_statement_date_from).toDateString() }} -> {{ new Date($store.getters.getListData(section)[0].account_statement_date_to).toDateString() }}</h3>
</div>
<!-- /.card-header -->
<div class="card-body table-responsive p-0">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Date</th>
<th>Description 1</th>
<th>Pay For</th>
<th>System References</th>
<th>Amount</th>
<th>Action</th>
</tr>
</thead>
<tbody v-show="!isLoading">
<tr v-for="item in $store.getters.getListData(section)" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.date}}</td>
<td>{{item.transaction_description_1 | truncate(30, '...')}}</td>
<td>{{item.pay_for}}</td>
<td>{{item.system_references}}</td>
<td>{{item.amount}}</td>
<td>
<a href="#">
<i class="fa fa-edit blue requestModal" data-type="editSingleItem" @click="editModal({item})"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
<!-- /.card-body -->
<div class="card-footer">
</div>
</div>
<!-- /.card -->
</div>
<div class="col-12">
<modal-component class="animate__animated animate__fast animate__fadeIn" styleType="fill-in" type="editSingleItem">
<edit-single-item-in-list-component v-if="data != null" :data="data" :section="'editSingleItem'" class="text-left"></edit-single-item-in-list-component>
</modal-component>
</div>
<div class="col-12">
<pagination-component :section="section" class="mb-5" ref="pagination"></pagination-component>
</div>
</div>
</template>
<script>
import EditSingleItemInListComponent from "../../accounting/elements/EditSingleItemInListComponent";
export default {
components: {EditSingleItemInListComponent},
props: {
statement: {
type: Number,
required: true
}
},
data(){
return {
data: null,
section: 'statementTransactionsDetails',
isLoading: false,
matches: ['exchange', 'shipping', 'none'],
selected: {
matches: [],
days: []
},
filters: {'per_page': 10, order_by: {column: 'id', DESC: true}, 'has_account_statement_id': this.statement},
page: 1,
days: [],
}
},
computed: {
pendingQueue () {
return this.$store.getters.isInCompleteQueue(this.section);
},
},
watch: {
pendingQueue(inComplete, oldValue){
if(inComplete){
this.fetchList();
}
},
selected: {
handler: function () {
//this.filters = {'per_page': 10, order_by: {column: 'id', DESC: true}};
//pay_for
if(this.selected.matches.toString() != ""){
const newArr = this.selected.matches.slice();
const index = newArr.indexOf('none');
if(index > -1){
newArr.splice(index, 1, '');
}
const newObject = {pay_for_in: newArr};
this.filters = {...this.filters, ...newObject};
}
else{
delete this.filters.pay_for_in;
}
//date
if(this.selected.days.toString() != ""){
const newArr = this.selected.days.slice();
const newObject = {date_in: newArr};
this.filters = {...this.filters, ...newObject};
}
else{
delete this.filters.date_in;
}
},
deep: true
}
},
created(){
this.setDecoratorDefault();
this.$store.dispatch('updateListQueue', {'name': this.section, 'page': 1, 'filters': this.filters});
},
methods: {
fetchList(isUpdate = false){
this.isLoading = true;
if(!isUpdate){
let listDecorators = this.$store.getters.getListDetails(this.section);
this.page = listDecorators.page;
}
this.submit(route('api.accounting.statement.details', this.statement) + '?page=' + this.page + '&filters=' + JSON.stringify(this.filters), 'get', this.section, false, false);
},
successHandler(response){
this.$store.dispatch('completeList', {'name': this.section, 'data': response.payload.data});
this.$refs.pagination.makePagination(response.payload.meta, response.payload.links + '&filters=' + JSON.stringify(this.filters));
this.generateDays();
this.isLoading = false;
},
editModal(param){
this.data = param.item;
},
generateDays(){
if(this.$store.getters.getListData(this.section)[0] !== undefined)
{
const startDate = new Date(this.$store.getters.getListData(this.section)[0].account_statement_date_from);
const endDate = new Date(this.$store.getters.getListData(this.section)[0].account_statement_date_to);
const days = [];
for (let d = startDate; d <= endDate; d.setDate(d.getDate() + 1)) {
const formattedDate = this.formatDate(d);
days.push(formattedDate);
}
this.days = days;
}
},
formatDate(date) {
const year = date.getFullYear();
const month = ('0' + (date.getMonth() + 1)).slice(-2);
const day = ('0' + date.getDate()).slice(-2);
return `${year}-${month}-${day}`;
},
},
}
</script>
@@ -0,0 +1,8 @@
@extends('layouts.base_portal')
@section('inner_content')
<div class="row">
<div class="col">
<statement-transactions-details-component :statement="{{$statement}}"></statement-transactions-details-component>
</div>
</div>
@endsection
@@ -78,7 +78,7 @@
<td>{{ $statement->begin_balance }}</td>
<td>{{ $statement->end_balance }}</td>
<td>
<a href="{{ route('statements.show', $statement) }}" class="btn btn-primary btn-sm">View</a>
<a href="{{ route('statements.transactions.details', $statement) }}" class="btn btn-primary btn-sm">View</a>
</td>
</tr>
@endforeach
+10
View File
@@ -0,0 +1,10 @@
<?php
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'accounting', 'as' => 'accounting.', 'namespace' => 'Accounting'], function () {
Route::group(['prefix' => 'statements/{id}', 'as' => 'statement.'], function () {
Route::get('/details', 'BankStatementController@fetch')->name('details');
Route::put('/details/update', 'BankStatementController@update')->name('details.update');
});
});
+2
View File
@@ -54,6 +54,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
require __DIR__ . '/wallet.php';
require __DIR__ . '/accounting.php';
// require __DIR__ . '/rate.php';
// require __DIR__ . '/receipt.php';
+4
View File
@@ -557,9 +557,13 @@ Route::get('/currency-rate-history', function () {
Route::get('/statements', [BankStatementController::class, 'index'])->name('statements.index');
Route::post('/statements/import', [BankStatementController::class, 'import'])->name('statements.import');
Route::get('/statements/{statement}/details', function ($statement) {
return view('pages.accounting.bank-statements.details', ['statement' => $statement]);
})->name('statements.transactions.details');
Route::get('/statements/{statement}', [BankStatementController::class, 'show'])->name('statements.show');
Route::get('/statements/{statement}/download', 'StatementController@download')->name('statements.download');
Route::get('/bank-record', 'Imports\ImportBankRecordController@import');