diff --git a/app/Classes/General/Eloquent/Filters/DateIn.php b/app/Classes/General/Eloquent/Filters/DateIn.php new file mode 100644 index 00000000..b1704eb4 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/DateIn.php @@ -0,0 +1,19 @@ +whereIn('date', $value); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/HasAccountStatementId.php b/app/Classes/General/Eloquent/Filters/HasAccountStatementId.php new file mode 100644 index 00000000..05cfe9d8 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/HasAccountStatementId.php @@ -0,0 +1,22 @@ +whereHas('statementTransaction', function ($query) use ($value) { + $query->where('account_statement_id', $value); + }); + } +} diff --git a/app/Classes/General/Eloquent/Filters/IsMapped.php b/app/Classes/General/Eloquent/Filters/IsMapped.php new file mode 100644 index 00000000..12282d3b --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/IsMapped.php @@ -0,0 +1,20 @@ +whereHas('owners') : $builder->whereDoesntHave('owners'); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/IsMappedWithMultiple.php b/app/Classes/General/Eloquent/Filters/IsMappedWithMultiple.php new file mode 100644 index 00000000..c9c92438 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/IsMappedWithMultiple.php @@ -0,0 +1,24 @@ +withCount(['owners' => function ($query){ + $query->where('status', ApprovalStatus::PENDING_VERIFICATION); + }]); + return $value ? $query->having('owners_count', '>', 1) : $query->having('owners_count', '=', 1); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/MaxAmount.php b/app/Classes/General/Eloquent/Filters/MaxAmount.php new file mode 100644 index 00000000..3a0476c8 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/MaxAmount.php @@ -0,0 +1,20 @@ +where('amount', '<=', $value); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/MinAmount.php b/app/Classes/General/Eloquent/Filters/MinAmount.php new file mode 100644 index 00000000..52b72636 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/MinAmount.php @@ -0,0 +1,20 @@ +where('amount', '>=', $value); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/OwnerId.php b/app/Classes/General/Eloquent/Filters/OwnerId.php new file mode 100644 index 00000000..eac7e32d --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/OwnerId.php @@ -0,0 +1,20 @@ +where('owner_id', $value); + } + +} \ No newline at end of file diff --git a/app/Classes/General/Eloquent/Filters/PayFor.php b/app/Classes/General/Eloquent/Filters/PayFor.php new file mode 100644 index 00000000..b8a2ba75 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/PayFor.php @@ -0,0 +1,19 @@ +where('pay_for', $value); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/PayForIn.php b/app/Classes/General/Eloquent/Filters/PayForIn.php new file mode 100644 index 00000000..3fe98f00 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/PayForIn.php @@ -0,0 +1,19 @@ +whereIn('pay_for', $value); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/StatementTransactionAccountId.php b/app/Classes/General/Eloquent/Filters/StatementTransactionAccountId.php new file mode 100644 index 00000000..b07bede9 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/StatementTransactionAccountId.php @@ -0,0 +1,22 @@ +whereHas('account', function ($query) use ($value) { + $query->where('statement_accounts.id', $value); + }); + } +} diff --git a/app/Classes/General/Eloquent/Filters/StatementTransactionOwnerStatusIn.php b/app/Classes/General/Eloquent/Filters/StatementTransactionOwnerStatusIn.php new file mode 100644 index 00000000..f2dfe546 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/StatementTransactionOwnerStatusIn.php @@ -0,0 +1,22 @@ +whereHas('owners', function ($query) use ($value) { + return $query->whereIn('statement_transaction_owners.status', $value); + }); + } + +} diff --git a/app/Classes/General/Eloquent/Filters/StatementTransactionOwnerTypeIn.php b/app/Classes/General/Eloquent/Filters/StatementTransactionOwnerTypeIn.php new file mode 100644 index 00000000..e23d0bcc --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/StatementTransactionOwnerTypeIn.php @@ -0,0 +1,22 @@ +whereHas('owners', function ($query) use ($value) { + return $query->whereIn('type', $value); + }); + } + +} diff --git a/app/Classes/Jobs/CreateBankStatementTransactionOwners.php b/app/Classes/Jobs/CreateBankStatementTransactionOwners.php new file mode 100644 index 00000000..e7797210 --- /dev/null +++ b/app/Classes/Jobs/CreateBankStatementTransactionOwners.php @@ -0,0 +1,27 @@ +make(CreateBankStatementTransactionOwnersProcessor::class))->execute(); + } + + public function delay($delay) + { + // Add delay in seconds to the job + $this->delay = $delay; + return $this; + } +} diff --git a/app/Classes/Jobs/UpdatePerfexCRM.php b/app/Classes/Jobs/UpdatePerfexCRM.php index 059779e1..833fb365 100644 --- a/app/Classes/Jobs/UpdatePerfexCRM.php +++ b/app/Classes/Jobs/UpdatePerfexCRM.php @@ -10,7 +10,8 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use App\Classes\Modules\PerfexCRM\DataTransferObjects\UpdatePerfexCRMObject; use App\Classes\Modules\PerfexCRM\DataTransferObjects\UpdatePerfexCRMInvoiceObject; - +use Illuminate\Support\Facades\Log; +use PhpOffice\PhpSpreadsheet\Calculation\Logical\Boolean; class UpdatePerfexCRM implements ShouldQueue { @@ -19,38 +20,39 @@ class UpdatePerfexCRM implements ShouldQueue /** @var UpdatePerfexCRMObject */ private $updatePerfexCRMObject; - /** @var UpdatePerfexCRMInvoice */ - private $nextJob; - - /** @var */ + /** @var */ private $transaction; + /** @var Boolean|null */ + private $shouldCreateInvoice; + /** - * UpdatePerfexCRM constructor. * @param UpdatePerfexCRMObject $updatePerfexCRMObject - * @param UpdatePerfexCRMInvoice $nextJob * @param $transaction + * @param bool|null $shouldCreateInvoice */ - public function __construct(UpdatePerfexCRMObject $updatePerfexCRMObject, $transaction, $nextJob) + public function __construct(UpdatePerfexCRMObject $updatePerfexCRMObject, $transaction, ?bool $shouldCreateInvoice = false) { $this->updatePerfexCRMObject = $updatePerfexCRMObject; - $this->nextJob = $nextJob; $this->transaction = $transaction; + $this->shouldCreateInvoice = $shouldCreateInvoice; } + public function handle() { $result = (App()->make(UpdatePerfexCRMProcessor::class))->execute($this->updatePerfexCRMObject); - /** @var UpdatePerfexCRMInvoice $nextJob*/ - if($this->nextJob != null && $this->transaction != null){ - $updatePerfexCRMInvoiceOject = new UpdatePerfexCRMInvoiceObject( + if($this->transaction != null && $this->shouldCreateInvoice){ + $updatePerfexCRMInvoiceObject = new UpdatePerfexCRMInvoiceObject( $this->updatePerfexCRMObject->getContactEmail(), $this->updatePerfexCRMObject->getProjectName(), $result->projectId, $this->transaction, - true, + true ); - $this->nextJob::dispatch($updatePerfexCRMInvoiceOject); + + UpdatePerfexCRMInvoice::dispatch($updatePerfexCRMInvoiceObject); + } } } diff --git a/app/Classes/Jobs/UpdatePerfexCRMInvoice.php b/app/Classes/Jobs/UpdatePerfexCRMInvoice.php index 0d7b093e..ee395033 100644 --- a/app/Classes/Jobs/UpdatePerfexCRMInvoice.php +++ b/app/Classes/Jobs/UpdatePerfexCRMInvoice.php @@ -30,7 +30,7 @@ class UpdatePerfexCRMInvoice implements ShouldQueue /** * CreatePerfexCRMSingleTask constructor. - * @param UpdatePerfexCRMInvoiceObject $createTaskPerfexCRMObject + * @param UpdatePerfexCRMInvoiceObject $updatePerfexCRMInvoiceObject */ public function __construct(UpdatePerfexCRMInvoiceObject $updatePerfexCRMInvoiceObject) { @@ -65,7 +65,8 @@ class UpdatePerfexCRMInvoice implements ShouldQueue $invoiceId = $result->payload['id']; } else { // Log::error(json_encode('UpdatePerfexCRMInvoice CreatePerfexCRMInvoiceProcessor failed')); - Helper::debugLoggerForPerfexCRM('UpdatePerfexCRMInvoice CreatePerfexCRMInvoiceProcessor failed'); + $log['message'] = 'UpdatePerfexCRMInvoice CreatePerfexCRMInvoiceProcessor failed'; + Helper::debugLoggerForPerfexCRM($log); } } else{ diff --git a/app/Classes/Jobs/UpdatePerfexCRMPrelude.php b/app/Classes/Jobs/UpdatePerfexCRMPrelude.php index 25afdc4d..3b05ac63 100644 --- a/app/Classes/Jobs/UpdatePerfexCRMPrelude.php +++ b/app/Classes/Jobs/UpdatePerfexCRMPrelude.php @@ -21,50 +21,28 @@ class UpdatePerfexCRMPrelude implements ShouldQueue /** @var UpdatePerfexCRMObject */ private $updatePerfexCRMObject; - /** @var UpdatePerfexCRM */ - private $nextJob1; - - /** @var UpdatePerfexCRMInvoice */ - private $nextJob2; + /** @var Boolean|null */ + private $shouldCreateInvoice; /** - * UpdatePerfexCRMPrelude constructor. * @param Transaction $transaction * @param UpdatePerfexCRMObject $updatePerfexCRMObject - * @param UpdatePerfexCRM $nextJob1 - * @param UpdatePerfexCRMInvoice $nextJob2 + * @param bool|null $shouldCreateInvoice */ - public function __construct(Transaction $transaction, UpdatePerfexCRMObject $updatePerfexCRMObject, $nextJob1, $nextJob2) + public function __construct(Transaction $transaction, UpdatePerfexCRMObject $updatePerfexCRMObject, ?bool $shouldCreateInvoice = false) { $this->transaction = $transaction; $this->updatePerfexCRMObject = $updatePerfexCRMObject; - $this->nextJob1 = $nextJob1; - $this->nextJob2 = $nextJob2; + $this->shouldCreateInvoice = $shouldCreateInvoice; } + public function handle() { $serviceTypeName = $this->transaction->owner->company->services()->where('id', $this->transaction->owner->service_id)->first()->name; $booking = $this->transaction->booking; - $bank = $booking->bank; - $bankDetails = $this->transaction->owner->service_id == 4 ? - [ - '1688_username' => $bank->account_no, - '1688_password' => $bank->holder_name, - 'payment_pin' => $bank->bank_branch, - ]: - [ - 'id' => $bank->id, - 'type' => $bank->type, - 'reference' => $bank->reference, - 'bank_name' => $bank->bank_name, - 'bank_branch' => $bank->bank_branch, - 'holder_name' => $bank->holder_name, - 'account_no' => $bank->account_no, - 'country_id' => $bank->country_id, - 'default' => $bank->default, - 'status' => $bank->status, - ]; + $bankDetails = $this->generateBankDetails($booking->bank); + $data = [ 'amount' => number_format($this->transaction->amount, 2, '.', ''), 'currency' => $this->transaction->currency_id == 1 ? "MYR" : "CNY", @@ -75,14 +53,36 @@ class UpdatePerfexCRMPrelude implements ShouldQueue 'link_autocount_or' => 'https://docs.google.com/spreadsheets/d/1Q3rJBGQ9Bo04HZp5WR9zbLp7pIW2kfDYsabxG7JON-4/edit#gid=2013983170', ]; - $result = $this->replacePlaceholders($this->updatePerfexCRMObject->getTasks(), $data); $this->updatePerfexCRMObject->setTasks($result); - /** @var UpdatePerfexCRM $nextJob1 */ - /** @var UpdatePerfexCRMInvoice $nextJob2 */ - $this->nextJob1::dispatch($this->updatePerfexCRMObject, $this->transaction, $this->nextJob2); + + UpdatePerfexCRM::dispatch($this->updatePerfexCRMObject, $this->transaction, $this->shouldCreateInvoice); } + + private function generateBankDetails($bank): array + { + return $this->transaction->owner->service_id == 4 ? + [ + '1688_username' => $bank->account_no, + '1688_password' => $bank->holder_name, + 'payment_pin' => $bank->bank_branch, + ]: + [ + 'id' => $bank->id, + 'type' => $bank->type, + 'reference' => $bank->reference, + 'bank_name' => $bank->bank_name, + 'bank_branch' => $bank->bank_branch, + 'holder_name' => $bank->holder_name, + 'account_no' => $bank->account_no, + 'country_id' => $bank->country_id, + 'default' => $bank->default, + 'status' => $bank->status, + ]; + } + + function replacePlaceholders($template, $data, $prefix = '') { foreach ($template as $key => $value) { diff --git a/app/Classes/Modules/Accounting/ControllersLogic/ApproveDuplicateBankStatementDetailsStatusLogic.php b/app/Classes/Modules/Accounting/ControllersLogic/ApproveDuplicateBankStatementDetailsStatusLogic.php new file mode 100644 index 00000000..e7d1a7c9 --- /dev/null +++ b/app/Classes/Modules/Accounting/ControllersLogic/ApproveDuplicateBankStatementDetailsStatusLogic.php @@ -0,0 +1,283 @@ + 'Update Duplicate Bank Statement Details Status', + 'message' => 'You have successfully updated the Statement Transation Status' + ]; + } + + /** @var UpdatesBankStatementTransactionOwnerStatus */ + private $updatesBankStatementTransactionOwnerStatus; + + /** + * @param UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus + */ + public function __construct(UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus) + { + $this->updatesBankStatementTransactionOwnerStatus = $updatesBankStatementTransactionOwnerStatus; + } + +// /** +// * Perform logic for approving or rejecting a statement transaction owner and handle matching records. +// * +// * @param Request $request The request object containing route parameters and data. +// * @return JsonResponse The JSON response indicating the result of the logic. +// */ +// public function logic(Request $request): JsonResponse +// { +// // Determine the status based on the 'status' route parameter +// $status = $request->route('status') == 'approve' ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED; +// +// // Find the statement transaction owner based on the 'id' route parameter +// $owner = StatementTransactionOwner::find($request->route('id')); +// +// // Execute an update action to change the status of the owner +// $this->updatesBankStatementTransactionOwnerStatus->execute($owner, $status); +// +// // If the status is 'approved', reject all other owners with the same system, owner type, and owner ID +// if ($status === ApprovalStatus::APPROVED) { +// StatementTransactionOwner::where('system', $owner->system) +// ->where('owner_type', $owner->owner_type) +// ->where('owner_id', $owner->owner_id) +// ->where('id', '!=', $owner->id) +// ->update(['status' => ApprovalStatus::REJECTED]); +// } +// +// // Find all siblings (owners with the same statement transaction ID) +// $siblings = StatementTransactionOwner::where('statement_transaction_id', $owner->statement_transaction_id) +// ->where('id', '!=', $owner->id) +// ->get(); +// +// // Process each sibling +// foreach ($siblings as $sibling) { +// +// // If the status is 'approved', reject the sibling and save the changes +// if ($status === ApprovalStatus::APPROVED) { +// $sibling->status = ApprovalStatus::REJECTED; +// $sibling->save(); +// } +// +// // Find all twins (owners with the same system, owner type, and owner ID) +// $twins = StatementTransactionOwner::where('system', $sibling->system) +// ->where('owner_type', $sibling->owner_type) +// ->where('owner_id', $sibling->owner_id) +// ->where('id', '!=', $sibling->id) +// ->get(); +// +// // Process each twin +// foreach ($twins as $twin) { +// // Find all owners with the same statement transaction ID as the twin +// $owners = StatementTransactionOwner::where('statement_transaction_id', $twin->statement_transaction_id) +// ->where('id', '!=', $twin->id) +// ->get(); +// +// // If there is only one owner (the twin itself), execute an update action to change its status to 'approved' +// if (count($owners) === 1) { +// $this->updatesBankStatementTransactionOwnerStatus->execute($twin, ApprovalStatus::APPROVED); +// } +// } +// } +// +// // Find all remaining matching owners for the related transaction +// $remainingMatches = StatementTransactionOwner::where('system', $owner->system) +// ->where('owner_type', $owner->owner_type) +// ->where('owner_id', $owner->owner_id) +// ->where('status', ApprovalStatus::PENDING_VERIFICATION) // Consider only pending owners +// ->get(); +// +// // Process each remaining match +// foreach ($remainingMatches as $match) { +// // Find all owners with the same statement transaction ID as the match +// $owners = StatementTransactionOwner::where('statement_transaction_id', $match->statement_transaction_id) +// ->where('id', '!=', $match->id) +// ->get(); +// +// // If there is only one owner (the match itself), execute an update action to change its status to 'approved' +// if (count($owners) === 1) { +// $this->updatesBankStatementTransactionOwnerStatus->execute($match, ApprovalStatus::APPROVED); +// } +// } +// +// // Return an empty response +// return $this->response([]); +// } + + /** + * Perform logic for approving or rejecting a statement transaction owner and handle matching records. + * + * @param Request $request The request object containing route parameters and data. + * @return JsonResponse The JSON response indicating the result of the logic. + */ + public function logic(Request $request): JsonResponse + { + // Determine the approval status + $status = $this->getApprovalStatus($request); + + // Find the statement transaction owner + $owner = $this->getOwner($request); + + // Update owner status + $this->updateOwnerStatus($owner, $status); + + // If the status is 'approved', handle the approval process + if ($status === ApprovalStatus::APPROVED) { + $this->handleApprovedStatus($owner); + } + + // Check and approve remaining matches if any + $this->checkAndApproveRemainingMatches($owner); + + // Return an empty response + return $this->response([]); + } + + private function getApprovalStatus(Request $request): int + { + return $request->route('status') == 'approve' ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED; + } + + private function getOwner(Request $request): StatementTransactionOwner + { + return StatementTransactionOwner::find($request->route('id')); + } + + private function updateOwnerStatus(StatementTransactionOwner $owner, int $status): void + { + $this->updatesBankStatementTransactionOwnerStatus->execute($owner, $status); + } + + private function handleApprovedStatus(StatementTransactionOwner $owner): void + { + // Reject all other owners with the same system, owner type, and owner ID + $this->rejectOtherOwners($owner); + + // Find all siblings and process them + $siblings = $this->getSiblings($owner); + $this->processSiblings($siblings); + } + + private function rejectOtherOwners(StatementTransactionOwner $owner): void + { + StatementTransactionOwner::where('system', $owner->system) + ->where('owner_type', $owner->owner_type) + ->where('owner_id', $owner->owner_id) + ->where('id', '!=', $owner->id) + ->update(['status' => ApprovalStatus::REJECTED]); + } + + private function getSiblings(StatementTransactionOwner $owner): Collection + { + return StatementTransactionOwner::where('statement_transaction_id', $owner->statement_transaction_id) + ->where('id', '!=', $owner->id) + ->get(); + } + + private function processSiblings(Collection $siblings): void + { + foreach ($siblings as $sibling) { + $this->processSibling($sibling); + } + } + + private function processSibling(StatementTransactionOwner $sibling): void + { + // Reject the sibling and save the changes + $sibling->status = ApprovalStatus::REJECTED; + $sibling->save(); + + // Find all twins and process them + $twins = $this->getTwins($sibling); + $this->processTwins($twins); + } + + private function getTwins(StatementTransactionOwner $sibling): Collection + { + return StatementTransactionOwner::where('system', $sibling->system) + ->where('owner_type', $sibling->owner_type) + ->where('owner_id', $sibling->owner_id) + ->where('id', '!=', $sibling->id) + ->get(); + } + + private function processTwins(Collection $twins): void + { + foreach ($twins as $twin) { + $this->processTwin($twin); + } + } + + private function processTwin(StatementTransactionOwner $twin): void + { + // Find all owners with the same statement transaction ID as the twin + $owners = $this->getOwners($twin); + + // If there is only one owner (the twin itself), approve it + if ($owners->count() === 1) { + $this->updateOwnerStatus($twin, ApprovalStatus::APPROVED); + } + } + + private function getOwners(StatementTransactionOwner $transactionOwner): Collection + { + return StatementTransactionOwner::where('statement_transaction_id', $transactionOwner->statement_transaction_id) + ->where('id', '!=', $transactionOwner->id) + ->get(); + } + + private function checkAndApproveRemainingMatches(StatementTransactionOwner $owner): void + { + // Find all remaining matching owners for the related transaction + $remainingMatches = $this->getRemainingMatches($owner); + + // Process each remaining match + foreach ($remainingMatches as $match) { + $this->processRemainingMatch($match); + } + } + + private function getRemainingMatches(StatementTransactionOwner $owner): Collection + { + return StatementTransactionOwner::where('system', $owner->system) + ->where('owner_type', $owner->owner_type) + ->where('owner_id', $owner->owner_id) + ->where('status', ApprovalStatus::PENDING_VERIFICATION) + ->get(); + } + + private function processRemainingMatch(StatementTransactionOwner $match): void + { + // Find all owners with the same statement transaction ID as the match + $owners = $this->getOwners($match); + + // If there is only one owner (the match itself), approve it + if ($owners->count() === 1) { + $this->updateOwnerStatus($match, ApprovalStatus::APPROVED); + } + + } + +} diff --git a/app/Classes/Modules/Accounting/ControllersLogic/GroupApproveStatementTransactionLogic.php b/app/Classes/Modules/Accounting/ControllersLogic/GroupApproveStatementTransactionLogic.php new file mode 100644 index 00000000..3b220146 --- /dev/null +++ b/app/Classes/Modules/Accounting/ControllersLogic/GroupApproveStatementTransactionLogic.php @@ -0,0 +1,86 @@ + 'Group Approve Statement Transaction', + 'message' => 'You have successfully approveed a group of Statement Transation' + ]; + } + + /** @var ListsBankStatementTransactions */ + private $listsBankStatementTransactions; + + /** @var UpdatesBankStatementTransactionOwnerStatus */ + private $updatesBankStatementTransactionOwnerStatus; + + /** @var UpdatesTransactionStatus */ + private $updatesTransactionStatus; + + /** + * @param ListsBankStatementTransactions $listsBankStatementTransactions + * @param UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus + * @param UpdatesTransactionStatus $updatesTransactionStatus + */ + public function __construct(ListsBankStatementTransactions $listsBankStatementTransactions, UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus, UpdatesTransactionStatus $updatesTransactionStatus) + { + $this->listsBankStatementTransactions = $listsBankStatementTransactions; + $this->updatesBankStatementTransactionOwnerStatus = $updatesBankStatementTransactionOwnerStatus; + $this->updatesTransactionStatus = $updatesTransactionStatus; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws MalformedRequestException + */ + public function logic(Request $request): JsonResponse + { + + $filters = [ + "min_amount" => 0, + "is_mapped" => true, + "is_mapped_with_multiple" => false, + "statement_transaction_owner_type_in" => [1, 2], + "statement_transaction_owner_status_in" => [1] + ]; + $statementTransactions = $this->listsBankStatementTransactions->execute($filters); + + foreach ($statementTransactions as $statementTransaction) { + $owners = $statementTransaction->owners; + + if (count($owners)) { + $this->updatesBankStatementTransactionOwnerStatus->execute($owners->first(), ApprovalStatus::APPROVED); + + // automatically approve payment if pending verification + +// if ($statementTrasactionOwner->owner->type !== StatementTransactionOwnerType::SALES) continue; +// if ($statementTrasactionOwner->owner->status === ApprovalStatus::PENDING_VERIFICATION ) { +// $this->updatesTransactionStatus->execute($statementTrasactionOwner->owner, ApprovalStatus::APPROVED); +// } + } + } + + return $this->response([]); + } +} diff --git a/app/Classes/Modules/Accounting/ControllersLogic/ImportBankStatementLogic.php b/app/Classes/Modules/Accounting/ControllersLogic/ImportBankStatementLogic.php new file mode 100644 index 00000000..0355c554 --- /dev/null +++ b/app/Classes/Modules/Accounting/ControllersLogic/ImportBankStatementLogic.php @@ -0,0 +1,147 @@ + 'Import Bank Statement Transactions Details', + 'message' => 'You have successfully updated the Bank Statement Transactions Details' + ]; + } + + + /** + * @param Request $request + * @return JsonResponse + * @throws MalformedRequestException + */ + public function logic(Request $request) : JsonResponse + { + + $files = $request->file('files'); + + $object = new DocumentObject('', $request->input('files'), '', ApprovalStatus::APPROVED, 'imports'); + foreach ($object->getFiles() as $file){ + $collection = Excel::toCollection(null, json_decode($file)->file_info->original->file, null, null, true); + + $sheet = $collection->first()->skip(1); + + $statementDetails = $sheet->first(); + + $accountNumber = $statementDetails[0]; + $accountType = $statementDetails[1]; + $accountName = $statementDetails[2]; + $accountCurrency = $statementDetails[3]; + $dateFrom = carbon::parse(str_replace(' MY (UTC+08:00)', '', $statementDetails[4])); + $dateTo = carbon::parse(str_replace(' MY (UTC+08:00)', '', $statementDetails[5])); + $totalDebit = $statementDetails[6]; + $totalCredit = $statementDetails[7]; + $beginBalance = $statementDetails[8]; + $endBalance = $statementDetails[9]; + $account = StatementAccount::updateOrCreate( + ['number' => $accountNumber], + [ + 'type' => $accountType, + 'name' => $accountName, + 'currency' => $accountCurrency, + ] + ); + + $statement = AccountStatement::where('date_from', $dateFrom) + ->where('date_to', $dateTo) + ->where('total_amount', $totalDebit ?: $totalCredit,) + ->where('begin_balance', $beginBalance) + ->where('end_balance', $endBalance)->first(); + + + if(!$statement){ + $statement = new AccountStatement([ + 'date_from' => $dateFrom, + 'date_to' => $dateTo, + 'total_amount' => $totalDebit ?: $totalCredit, + 'begin_balance' => $beginBalance, + 'end_balance' => $endBalance, + ]); + } + + $account->statements()->save($statement); + +// CreateBankStatementTransactionOwners::dispatch($statement); + + + $sheet->map(function ($row) use ($statement, $account) { + $transactionRef = $row[15]; + $amount = $row[17] !== '-' ? ((float) str_replace(',', '', $row[17])) : (-((float) str_replace(',', '', $row[16]))); + $transactionDate = $row[10] !== '-' ? carbon::parse(str_replace(' MY (UTC+08:00)', '', $row[10]) . $row[11]) : null; + $postingDate = carbon::createFromFormat('d/M/Y H:i', str_replace(' MY (UTC+08:00)', '', $row[12]) . str_replace(' MY (UTC+08:00)', '', $row[13])); + $transactionDescription = is_numeric($row[14]) ? (int) sprintf('%.2f', $row[14]) : $row[14]; + $tellerId = $row[19]; + $branchChannel = $row[20]; + $transactionCode = $row[21]; + $endBalance = $row[22]; + $description2 = $row[25]; + $description3 = $row[26]; + $description4 = $row[27]; + $description5 = $row[28]; + $transaction = new StatementTransaction([ + 'transaction_ref' => $transactionRef, + 'amount' => $amount, + 'transaction_date' => $transactionDate, + 'posting_date' => $postingDate, + 'transaction_description' => $transactionDescription, + 'teller_id' => $tellerId, + 'branch_channel' => $branchChannel, + 'transaction_code' => $transactionCode, + 'end_balance' => $endBalance, + 'transaction_description_2' => $description2, + 'transaction_description_3' => $description3, + 'transaction_description_4' => $description4, + 'transaction_description_5' => $description5, + ]); + + // Check if the transaction already exists for this statement + $existingTransaction = StatementTransaction::where('transaction_ref', $transactionRef) + ->where('posting_date', $postingDate) + ->where('amount', $amount) + ->where('transaction_description', $transactionDescription) + ->where('teller_id', $tellerId) + ->where('branch_channel', $branchChannel) + ->where('transaction_code', $transactionCode) + ->where('end_balance', $endBalance) + ->first(); + + if (!$existingTransaction) { + $statement->transactions()->save($transaction); + } + + return $transaction; + }); + } + + + + return $this->response([]); + + } + +} diff --git a/app/Classes/Modules/Accounting/ControllersLogic/ListBankStatementDetailsLogic.php b/app/Classes/Modules/Accounting/ControllersLogic/ListBankStatementDetailsLogic.php new file mode 100644 index 00000000..10cffba1 --- /dev/null +++ b/app/Classes/Modules/Accounting/ControllersLogic/ListBankStatementDetailsLogic.php @@ -0,0 +1,52 @@ + '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)); + } + +} diff --git a/app/Classes/Modules/Accounting/ControllersLogic/ListBankStatementTransactionsLogic.php b/app/Classes/Modules/Accounting/ControllersLogic/ListBankStatementTransactionsLogic.php new file mode 100644 index 00000000..3d2b8273 --- /dev/null +++ b/app/Classes/Modules/Accounting/ControllersLogic/ListBankStatementTransactionsLogic.php @@ -0,0 +1,53 @@ + 'Retrieved Bank Statement Transactions', + 'message' => 'You have successfully retrieved a Bank Statement Transactions' + ]; + } + + + /** @var ListsBankStatementTransactions */ + private $listsBankStatementTransactions; + + /** + * @param ListsBankStatementTransactions $listsBankStatementTransactions + */ + public function __construct(ListsBankStatementTransactions $listsBankStatementTransactions) + { + $this->listsBankStatementTransactions = $listsBankStatementTransactions; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws ErrorException + */ + public function logic(Request $request) : JsonResponse + { + $query = $this->listsBankStatementTransactions->execute($this->listsBankStatementTransactions->deserializeFilters($request->input('filters'))); + + return $this->collectionResponse(BankStatementTransactionResource::collection($query)); + } + +} diff --git a/app/Classes/Modules/Accounting/ControllersLogic/UpdateBankStatementDetailLogic.php b/app/Classes/Modules/Accounting/ControllersLogic/UpdateBankStatementDetailLogic.php new file mode 100644 index 00000000..ff7806d4 --- /dev/null +++ b/app/Classes/Modules/Accounting/ControllersLogic/UpdateBankStatementDetailLogic.php @@ -0,0 +1,137 @@ + 'Updated Bank Statement Transactions Details', + 'message' => 'You have successfully updated the Bank Statement Transactions Details' + ]; + } + + /** @var FetchesBankStatementTransaction */ + private $fetchesBankStatementTransaction; + + + /** @var ChecksBillNumber */ + private $checksBillNumber; + + /** + * @param FetchesBankStatementTransaction $fetchesBankStatementTransaction + * @param ChecksBillNumber $checksBillNumber + */ + public function __construct(FetchesBankStatementTransaction $fetchesBankStatementTransaction, ChecksBillNumber $checksBillNumber) + { + $this->fetchesBankStatementTransaction = $fetchesBankStatementTransaction; + $this->checksBillNumber = $checksBillNumber; + } + + public function logic(Request $request): JsonResponse + { + $bankStatementTransaction = $this->fetchesBankStatementTransaction->execute(['id' => $request->route('id')]); + + $transactionReference = $request->input('transaction_reference'); + $systemReference = $request->input('system_references'); + + $owner_type = $owner_id = $owner_reference = $statementTransactionOwnerType = $system = null; + + $salesSystems = ['lite', 'cntr', 'probashi', 'pets']; + $allowedSystems = array_merge($salesSystems, ['exchange', 'izyim']); + + if (!in_array($systemReference, $allowedSystems)) { + throw new MalformedRequestException('System Reference not allowed'); + } + + switch ($request->input('pay_for')) { + case 'sales': + $owner_reference = $transactionReference; + $statementTransactionOwnerType = StatementTransactionOwnerType::SALES; + break; + + case 'top_up': + $owner_reference = $transactionReference; + $statementTransactionOwnerType = StatementTransactionOwnerType::WALLET_TOP_UP; + break; + + case 'internal_bank_transfer': + $statementTransactionOwnerType = StatementTransactionOwnerType::INTERNAL_BANK_TRANSFER_IN; + break; + + case 'others': + $owner_reference = $transactionReference; + $statementTransactionOwnerType = StatementTransactionOwnerType::NON_OPERATIONAL; + break; + + default: + throw new MalformedRequestException('Transaction Type Not Allowed'); + } + + if (in_array($systemReference, ['exchange', 'izyim'])) { + $transaction = $this->checksBillNumber->execute($transactionReference, $systemReference); + + if($systemReference === 'exchange') { + $owner_type = Transaction::class; + $owner_id = $transaction->id; + if($transaction->type === TransactionType::TOP_UP) { + $owner_reference = $transaction->owner->owner->reference; + } + + if($transaction->type === TransactionType::PAYMENT && $transaction->owner_type === Booking::class) { + $owner_reference = $transaction->owner->marking; + } + + } + + if($systemReference === 'izyim') { + $transaction = $transaction[0]; + $owner_type = Transaction::class; + $owner_id = $transaction['owner_id']; + $owner_reference = $transaction['owner_reference']; + + } + } + + $system = $systemReference != null ? SystemType::SYSTEM_NAMES[$systemReference] : ''; + + $this->createBankStatementTransactionOwner($bankStatementTransaction, $statementTransactionOwnerType, $system, $owner_type, $owner_id, $owner_reference); + + return $this->response([]); + } + + private function createBankStatementTransactionOwner($bankStatementTransaction, $statementTransactionOwnerType, $system, $owner_type, $owner_id, $owner_reference) + { + $ownerData = [ + 'type' => $statementTransactionOwnerType, + 'system' => $system, + 'owner_type' => $owner_type, + 'owner_id' => $owner_id, + 'owner_reference' => $owner_reference, + ]; + + $bankStatementTransaction->owners()->firstOrCreate($ownerData); + + } + + +} diff --git a/app/Classes/Modules/Accounting/ControllersLogic/UpdateStatementTransactionStatusLogic.php b/app/Classes/Modules/Accounting/ControllersLogic/UpdateStatementTransactionStatusLogic.php new file mode 100644 index 00000000..3a006e9e --- /dev/null +++ b/app/Classes/Modules/Accounting/ControllersLogic/UpdateStatementTransactionStatusLogic.php @@ -0,0 +1,81 @@ + 'Update Statement Transaction Status', + 'message' => 'You have successfully updated the Statement Transation Status' + ]; + } + + /** @var FetchesBankStatementTransaction */ + private $fetchesBankStatementTransaction; + + /** @var UpdatesBankStatementTransactionOwnerStatus */ + private $updatesBankStatementTransactionOwnerStatus; + + /** @var UpdatesTransactionStatus */ + private $updatesTransactionStatus; + + /** + * UpdateAnnouncementLogic constructor. + * @param FetchesBankStatementTransaction $fetchesBankStatementTransaction + * @param UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus + * @param UpdatesTransactionStatus $updatesTransactionStatus + */ + public function __construct( + FetchesBankStatementTransaction $fetchesBankStatementTransaction, + UpdatesBankStatementTransactionOwnerStatus $updatesBankStatementTransactionOwnerStatus, + UpdatesTransactionStatus $updatesTransactionStatus + ) { + $this->fetchesBankStatementTransaction = $fetchesBankStatementTransaction; + $this->updatesBankStatementTransactionOwnerStatus = $updatesBankStatementTransactionOwnerStatus; + $this->updatesTransactionStatus = $updatesTransactionStatus; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws \App\Classes\Exceptions\AccessForbiddenException + * @throws \App\Classes\Exceptions\MalformedRequestException + * @throws \App\Classes\Exceptions\RequestValidationException + */ + public function logic(Request $request): JsonResponse + { + $statementTrasaction = $this->fetchesBankStatementTransaction->execute(['id' => $request->route('id')]); + + $statementTrasactionOwner = $statementTrasaction->owners->first(); + + $this->updatesBankStatementTransactionOwnerStatus->execute($statementTrasactionOwner, $request->route('status') == 'approve' ? ApprovalStatus::APPROVED : ApprovalStatus::REJECTED); + + // todo-new: approve payments status, need to check the owner(if system is shipping, need to api with shipping portal) + // if ($request->route('status') == 'approve') { + // if ($statementTrasactionOwner->transaction->type === StatementTransactionOwnerType::SALES) { + // if ($statementTrasactionOwner->owner->status === ApprovalStatus::PENDING_VERIFICATION) { + // $this->updatesTransactionStatus->execute($statementTrasactionOwner->owner, ApprovalStatus::APPROVED); + // } + // } + // } + + return $this->resourceResponse(new BankStatementTransactionResource($statementTrasaction)); + } +} diff --git a/app/Classes/Modules/Accounting/DataTransferObjects/BankStatementTransactionObject.php b/app/Classes/Modules/Accounting/DataTransferObjects/BankStatementTransactionObject.php new file mode 100644 index 00000000..30bac5d3 --- /dev/null +++ b/app/Classes/Modules/Accounting/DataTransferObjects/BankStatementTransactionObject.php @@ -0,0 +1,137 @@ +statement_transaction_id = $statement_transaction_id; + $this->type = $type; + $this->system = $system; + $this->owner_type = $owner_type; + $this->owner_id = $owner_id; + $this->invoice_reference = $invoice_reference; + $this->receipt_reference = $receipt_reference; + $this->is_auto_mapped = $is_auto_mapped; + $this->status = $status; + } + + /** + * @return int + */ + public function getStatementTransactionId(): int + { + return $this->statement_transaction_id; + } + + /** + * @return int + */ + public function getType(): int + { + return $this->type; + } + + /** + * @return string + */ + public function getOwnerType(): string + { + return $this->owner_type; + } + + /** + * @return int + */ + public function getOwnerId(): int + { + return $this->owner_id; + } + + /** + * @return string + */ + public function getSystem(): string + { + return $this->system; + } + + /** + * @return string + */ + public function getInvoiceReference(): string + { + return $this->invoice_reference; + } + + /** + * @return string + */ + public function getReceipteReference(): string + { + return $this->receipt_reference; + } + + /** + * @return Boolean + */ + public function getIsAutoMapped(): Boolean + { + return $this->is_auto_mapped; + } + + /** + * @return int + */ + public function getStatus(): int + { + return $this->status; + } +} diff --git a/app/Classes/Modules/Accounting/DataTransferObjects/BankStatementTrasactionOwnerObject.php b/app/Classes/Modules/Accounting/DataTransferObjects/BankStatementTrasactionOwnerObject.php new file mode 100644 index 00000000..07fe876e --- /dev/null +++ b/app/Classes/Modules/Accounting/DataTransferObjects/BankStatementTrasactionOwnerObject.php @@ -0,0 +1,46 @@ +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; + } +} diff --git a/app/Classes/Modules/Accounting/Processors/ChecksBillNumber.php b/app/Classes/Modules/Accounting/Processors/ChecksBillNumber.php new file mode 100644 index 00000000..0ac72cd2 --- /dev/null +++ b/app/Classes/Modules/Accounting/Processors/ChecksBillNumber.php @@ -0,0 +1,41 @@ + false]); + $response = $client->request('GET', $url . '?api-key=510acd13d8d24375cf038ad626c282565451461a9c2399357e0b65365300787e&filters={"bill_no":"' . $bill_no . '"}'); + $body = $response->getBody(); + $data = json_decode($body, true); + $payload = $data['payload']; + return $payload['data']; + } catch (\Exception $exception) { + dd($exception->getMessage()); +// preg_match('/\{.*\}/s', $exception->getMessage(), $matches); +// $jsonError = json_decode($matches[0]); + // Retrieved Transactions failed +// throw new MalformedRequestException($jsonError->title); + } + } + + if ($system_reference == 'exchange') { + $transaction = Transaction::where('bill_no', $bill_no)->first(); + if ($transaction) { + return $transaction; + } + } + + // if not found + throw new MalformedRequestException('Bill Number Not Found.'); + } +} diff --git a/app/Classes/Modules/Accounting/Processors/CreateBankStatementTransactionOwnersProcessor.php b/app/Classes/Modules/Accounting/Processors/CreateBankStatementTransactionOwnersProcessor.php new file mode 100644 index 00000000..90dd7048 --- /dev/null +++ b/app/Classes/Modules/Accounting/Processors/CreateBankStatementTransactionOwnersProcessor.php @@ -0,0 +1,255 @@ +whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + })->orderBy('posting_date')->get(); + +// $transactions = StatementTransaction::whereDoesntHave('owners')->where('amount', '<', 0)->get(); + + foreach ($transactions as $transaction) { + + if($transaction->amount > 0){ + + // Exchange Sales + $creditTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::PAYMENT, Booking::class, PaymentMethodType::WALLET, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + foreach ($creditTransactions as $creditTransaction) { + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::SALES, + 'system' => 'EXCHANGE', + 'owner_type' => Transaction::class, + 'owner_id'=> $creditTransaction->id, + 'owner_reference'=> $creditTransaction->owner->marking, + ]); + } + + + // Shipping Portal Sales + $creditTransactions = $this->getTransactionsFromShippingPortal($transaction->amount, $this->getDateRange($transaction->posting_date), 2); + foreach ($creditTransactions as $creditTransaction) { + if($creditTransaction['owner_type'] === Wallet::class) continue; + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::SALES, + 'system' => 'SHIPPING_PORTAL', + 'owner_type' => $creditTransaction['owner_type'], + 'owner_id'=> $creditTransaction['owner_id'], + 'owner_reference'=> $creditTransaction['owner_reference'], + ]); + } + + // Exchange Wallet Top Up + $creditTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::TOP_UP, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + foreach ($creditTransactions as $creditTransaction) { + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::WALLET_TOP_UP, + 'system' => 'EXCHANGE', + 'owner_type' => Transaction::class, + 'owner_id'=> $creditTransaction->id, + 'owner_reference'=> $creditTransaction->owner->owner->reference, + ]); + } + + $creditTransactions = $this->getTransactionsFromShippingPortal($transaction->amount, $this->getDateRange($transaction->posting_date), 5); + foreach ($creditTransactions as $creditTransaction) { + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::WALLET_TOP_UP, + 'system' => 'SHIPPING_PORTAL', + 'owner_type' => $creditTransaction['owner_type'], + 'owner_id'=> $creditTransaction['owner_id'], + 'owner_reference'=> $creditTransaction['owner_reference'], + ]); + } + + // fpx charge refund + if($transaction->transaction_description === 'DUITNOW S/CHRG REFUND'){ + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::FPX_CHARGE_REFUND + ]); + } + + // Customer Refund + + // INTERNAL_BANK_TRANSFER_IN + if(str_contains($transaction->transaction_description_2, 'CIEF WORLDWIDE')){ + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::INTERNAL_BANK_TRANSFER_IN + ]); + } + + + } + + if($transaction->amount < 0){ + + // Supplier Purchase Order Payments + foreach (['YSN', 'HCK', 'ATVANTIC', 'HIGH HILL'] as $reference){ + if(str_contains($transaction->transaction_description.' '.$transaction->transaction_description_2.' '.$transaction->transaction_description_3.' '.$transaction->transaction_description_4.' '.$transaction->transaction_description_5 , $reference)) { + $paymentDateStart = $transaction->posting_date->startOfDay()->subDays(1); + $paymentDateEnd = $transaction->posting_date->endOfDay(); + + if($paymentDateStart->dayOfWeek === Carbon::SUNDAY){ + $paymentDateStart->subDays(2); + } + $issuer = Company::where('name', 'like', '%'.$reference.'%')->get()->pluck('id'); + + $debitTransactions = Group::whereIn('issuer', $issuer)->where('amount', '>=', (($transaction->amount * -1) - 0.01)) + ->where('amount', '<=', (($transaction->amount * -1) + 0.01))->whereDate('created_at', '>=', $paymentDateStart)->whereDate('created_at', '<=', $paymentDateEnd)->get(); + + foreach ($debitTransactions as $debitTransaction) { + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::SUPPLIER_PAYMENT, + 'system' => 'EXCHANGE', + 'owner_type' => Group::class, + 'owner_id'=> $debitTransaction->id, + 'owner_reference'=> $debitTransaction->reference + ]); + } + + } + } + + // Exchange Wallet Withdrawal + $debitTransactions = $this->getTransactions($transaction->posting_date, $transaction->amount, TransactionType::DEBIT_NOTE, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + foreach ($debitTransactions as $debitTransaction) { + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::WALLET_WITHDRAWAL, + 'system' => 'EXCHANGE', + 'owner_type' => Transaction::class, + 'owner_id'=> $debitTransaction->id, + 'owner_reference'=> $debitTransaction->owner->owner->marking, + ]); + } + + // SALARY + + // STATUTORY + if(str_contains($transaction->transaction_description_2, 'PEMBANGUNAN SUMBER') || str_contains($transaction->transaction_description_2, 'HASIL') || str_contains($transaction->transaction_description_2, 'PERTUBUHAN KESELAMAT') || str_contains($transaction->transaction_description_2, 'KUMPULAN WANG SIMPAN')){ + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::STATUTORY + ]); + } + + // FPX_CHARGE + if($transaction->transaction_description === 'DR DUITNOW S/CHRG' || str_contains($transaction->transaction_description, 'Manual FPX') || str_contains($transaction->transaction_description, 'CMS - DR FPX CHG')){ + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::FPX_CHARGE + ]); + } + + // BANK_CHARGE + if($transaction->transaction_description === 'CMS - DR CORP CHG' || $transaction->transaction_description === 'MONTHLY PROFIT DEBIT'){ + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::BANK_CHARGE + ]); + } + + // CREDIT_CARD_PAYMENT + if(str_contains($transaction->transaction_description_2, 'VISA CARD')){ + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::CREDIT_CARD_PAYMENT + ]); + } + + // INTERNAL_BANK_TRANSFER_OUT + if(str_contains($transaction->transaction_description_2, 'CIEF WORLDWIDE') || str_contains($transaction->transaction_description_2, 'CIEF WORLWIDE') || str_contains($transaction->transaction_description_2, 'IZYIM GLOBAL')){ + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::INTERNAL_BANK_TRANSFER_OUT + ]); + } + + // non-operational charges + if(str_contains($transaction->transaction_description_2, 'HIRE PURCHASE') || str_contains($transaction->transaction_description_2, 'TENAGA NASIONAL') || str_contains($transaction->transaction_description, 'CABLE CHARGE') || str_contains($transaction->transaction_description_2, 'CTOS DATA SYSTEMS') || str_contains($transaction->transaction_description_2, 'MAXIS')){ + $transaction->owners()->firstOrCreate([ + 'type' => StatementTransactionOwnerType::NON_OPERATIONAL + ]); + } + } + } + } + + private function getTransactions($date, $amount, $type, $ownerType, $paymentMethod, $statuses, $model = Transaction::class) { + $query = $model::whereIn('status', $statuses) + ->where(function ($query) use ($ownerType, $paymentMethod, $type) { + if ($ownerType) { + $query->where('owner_type', $ownerType); + } + + if ($paymentMethod) { + $query->where('payment_method', '!=', $paymentMethod); + } + + if ($type) { + $query->where('type', $type); + } + }) + ->whereDate('created_at', $date->format('Y-m-d')) + ->where('amount', '>', ($amount - 0.01)) + ->where('amount', '<', ($amount + 0.01)); + + return $query->get(); + } + + private function getTransactionsFromShippingPortal($amount, $dateRange, $type){ + $url = 'https://izyim.cief-malaysia.com/public/api/v1/transactions/mappable/query'; + return $this->getFromShippingPortal($amount, $dateRange, $url, $type); + } + + private function getGroupsFromShippingPortal($amount, $dateRange, $type){ + $url = 'https://izyim.cief-malaysia.com/public/api/v1/groups/query'; + return $this->getFromShippingPortal($amount, $dateRange, $url, $type); + } + + private function getFromShippingPortal($amount, $dateRange, $url, $type){ + try{ + + $client = new \GuzzleHttp\Client(['verify' => false]); + $response = $client->request('GET', $url.'?api-key=510acd13d8d24375cf038ad626c282565451461a9c2399357e0b65365300787e&filters={"order_by":{"column":"id","DESC":true},"status_in":[2],"type":2,"created_after":"'.$dateRange['start_date'].'","created_before":"'.$dateRange['end_date'].'","amount_exceed":'.($amount - 0.01).',"amount_short":'.($amount + 0.01).',"type_in:"['.$type.']}'); + $body = $response->getBody(); + $data = json_decode($body, true); + $payload = $data['payload']; + $transactions2 = $payload['data']; + return $transactions2; + }catch(\Exception $exception){ + Log::error($exception); + return []; + } + } + + private function getDateRange(string $dateStr) { + // Create a DateTime object from the input string + $date = strtotime($dateStr); + + // Get the first day of the month + $today = date('Y-m-d', strtotime('-1 day', $date)); + + // Get the first day of the next month + $nextDay = date('Y-m-d', strtotime('+1 day', $date)); + + return [ + 'start_date' => $today, + 'end_date' => $nextDay, + ]; + } +} diff --git a/app/Classes/Modules/Accounting/Services/CreatesBankStatementTransactionOwner.php b/app/Classes/Modules/Accounting/Services/CreatesBankStatementTransactionOwner.php new file mode 100644 index 00000000..92a3ea33 --- /dev/null +++ b/app/Classes/Modules/Accounting/Services/CreatesBankStatementTransactionOwner.php @@ -0,0 +1,34 @@ +statement_transaction_id = $object->getStatementTransactionId(); + $model->type = $object->getType(); + $model->system = $object->getSystem(); + $model->owner_type = $object->getOwnerType(); + $model->owner_id = $object->getOwnerId(); + $model->invoice_reference = $object->getInvoiceReference(); + $model->receipt_reference = $object->getReceipteReference(); + $model->is_auto_mapped = $object->getIsAutoMapped(); + $model->status = $object->getStatus(); + + return $this->handler($model); + } +} diff --git a/app/Classes/Modules/Accounting/Services/FetchesBankStatementDetails.php b/app/Classes/Modules/Accounting/Services/FetchesBankStatementDetails.php new file mode 100644 index 00000000..22de5c39 --- /dev/null +++ b/app/Classes/Modules/Accounting/Services/FetchesBankStatementDetails.php @@ -0,0 +1,34 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Accounting/Services/FetchesBankStatementTransaction.php b/app/Classes/Modules/Accounting/Services/FetchesBankStatementTransaction.php new file mode 100644 index 00000000..d2bff6f9 --- /dev/null +++ b/app/Classes/Modules/Accounting/Services/FetchesBankStatementTransaction.php @@ -0,0 +1,32 @@ +repository = $repository; + } + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Accounting/Services/ListsBankStatementDetails.php b/app/Classes/Modules/Accounting/Services/ListsBankStatementDetails.php new file mode 100644 index 00000000..6e085e44 --- /dev/null +++ b/app/Classes/Modules/Accounting/Services/ListsBankStatementDetails.php @@ -0,0 +1,32 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Accounting/Services/ListsBankStatementTransactions.php b/app/Classes/Modules/Accounting/Services/ListsBankStatementTransactions.php new file mode 100644 index 00000000..8d4fbf69 --- /dev/null +++ b/app/Classes/Modules/Accounting/Services/ListsBankStatementTransactions.php @@ -0,0 +1,33 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Accounting/Services/UpdatesBankStatementDetails.php b/app/Classes/Modules/Accounting/Services/UpdatesBankStatementDetails.php new file mode 100644 index 00000000..e87121ac --- /dev/null +++ b/app/Classes/Modules/Accounting/Services/UpdatesBankStatementDetails.php @@ -0,0 +1,25 @@ +system_references = $object->getSystemReferences(); + $model->pay_for = $object->getPayFor(); + + return $this->handler($model); + } +} diff --git a/app/Classes/Modules/Accounting/Services/UpdatesBankStatementTransactionOwner.php b/app/Classes/Modules/Accounting/Services/UpdatesBankStatementTransactionOwner.php new file mode 100644 index 00000000..6af3d3d6 --- /dev/null +++ b/app/Classes/Modules/Accounting/Services/UpdatesBankStatementTransactionOwner.php @@ -0,0 +1,32 @@ +statement_transaction_id = $object->getStatementTransactionId(); + $model->type = $object->getType(); + $model->system = $object->getSystem(); + $model->owner_type = $object->getOwnerType(); + $model->owner_id = $object->getOwnerId(); + $model->invoice_reference = $object->getInvoiceReference(); + $model->receipt_reference = $object->getReceipteReference(); + $model->is_auto_mapped = $object->getIsAutoMapped(); + $model->status = $object->getStatus(); + + return $this->handler($model); + } +} diff --git a/app/Classes/Modules/Accounting/Services/UpdatesBankStatementTransactionOwnerStatus.php b/app/Classes/Modules/Accounting/Services/UpdatesBankStatementTransactionOwnerStatus.php new file mode 100644 index 00000000..abcda20a --- /dev/null +++ b/app/Classes/Modules/Accounting/Services/UpdatesBankStatementTransactionOwnerStatus.php @@ -0,0 +1,22 @@ +status = $status; + return $this->handler($model); + } +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/AutoPurchaseOrderFillLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/AutoPurchaseOrderFillLogic.php index 617691e8..8eb47681 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/AutoPurchaseOrderFillLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/AutoPurchaseOrderFillLogic.php @@ -65,7 +65,7 @@ class AutoPurchaseOrderFillLogic extends AbstractControllerLogic public function logic(Request $request) : JsonResponse { $bookings = Booking::where('service_id', '!=', 4)->where(function($query){ - return $query->whereMonth('created_at', '=', 04)->whereYear('created_at', 2023); + return $query->whereMonth('created_at', '=', 03)->whereYear('created_at', 2023); })->whereDoesntHave('transactions', function($q){ $q->where('type', TransactionType::PURCHASE_ORDER); $q->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION, ApprovalStatus::APPROVED]); diff --git a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingLogic.php index 0fbf2519..a93a3654 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingLogic.php @@ -17,6 +17,7 @@ use App\Classes\Modules\PerfexCRM\Processors\BookingToPerfexCRMProcessor; use App\Http\Resources\BookingResource; +use App\Models\Booking; use ErrorException; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -82,6 +83,7 @@ class CreateBookingLogic extends AbstractControllerLogic $this->canCreateBooking->passes($object); + /** @var Booking $booking */ $booking = $this->createsBooking->execute($company, $object); if(config('perfexcrm.is_enabled') == 'true'){ diff --git a/app/Classes/Modules/Documents/DataTransferObjects/FileObject.php b/app/Classes/Modules/Documents/DataTransferObjects/FileObject.php index 736853fc..2a425f7d 100644 --- a/app/Classes/Modules/Documents/DataTransferObjects/FileObject.php +++ b/app/Classes/Modules/Documents/DataTransferObjects/FileObject.php @@ -28,7 +28,7 @@ class FileObject implements DataTransferObject */ public function getData() { - return in_array($this->getExtension(), ['pdf', 'excel']) ? $this->data : (new imageManager())->make($this->data); + return in_array($this->getExtension(), ['pdf', 'excel', 'text']) ? $this->data : (new imageManager())->make($this->data); } /** @@ -66,7 +66,7 @@ class FileObject implements DataTransferObject */ public function getDecodedData(): string { - return in_array($this->getExtension(), ['pdf', 'excel']) ? + return in_array($this->getExtension(), ['pdf', 'excel', 'text']) ? base64_decode((explode('base64,', $this->getData()))[1]): $this->getData()->encode('data-url')->encoded; } @@ -83,4 +83,4 @@ class FileObject implements DataTransferObject -} \ No newline at end of file +} diff --git a/app/Classes/Modules/Documents/Services/ConvertsBase64ToFile.php b/app/Classes/Modules/Documents/Services/ConvertsBase64ToFile.php index 8741b823..0e0cf6b9 100644 --- a/app/Classes/Modules/Documents/Services/ConvertsBase64ToFile.php +++ b/app/Classes/Modules/Documents/Services/ConvertsBase64ToFile.php @@ -36,7 +36,7 @@ class ConvertsBase64ToFile foreach ($files as $file) { $object = new FileObject($file); - in_array($object->getExtension(), ['pdf', 'excel']) ? $this->generatePDF($object) : $this->generateImage($object); + in_array($object->getExtension(), ['pdf', 'excel', 'text']) ? $this->generatePDF($object) : $this->generateImage($object); } @@ -122,4 +122,4 @@ class ConvertsBase64ToFile ]); } -} \ No newline at end of file +} diff --git a/app/Classes/Modules/Imports/Services/BankStatementImport.php b/app/Classes/Modules/Imports/Services/BankStatementImport.php new file mode 100644 index 00000000..a552b1b0 --- /dev/null +++ b/app/Classes/Modules/Imports/Services/BankStatementImport.php @@ -0,0 +1,82 @@ +has('account_number')) { + // If the row has an account number, create a new statement account + $accountNumber = $row->get('account_number'); + $accountType = $row->get('account_type'); + $accountName = $row->get('account_name'); + $accountCurrency = $row->get('account_currency'); + + $account = StatementAccount::updateOrCreate( + ['number' => $accountNumber], + [ + 'type' => $accountType, + 'name' => $accountName, + 'currency' => $accountCurrency, + ] + ); + } else { + // Otherwise, create a new statement transaction for the current statement account + $dateFrom = $row->get('date_from'); + $dateTo = $row->get('date_to'); + $totalAmount = $row->get('total_amount'); + $beginBalance = $row->get('begin_balance'); + $endBalance = $row->get('end_balance'); + + $statement = AccountStatement::updateOrCreate( + [ + 'account_id' => $account->id, + 'date_from' => $dateFrom, + 'date_to' => $dateTo, + ], + [ + 'total_amount' => $totalAmount, + 'begin_balance' => $beginBalance, + 'end_balance' => $endBalance, + ] + ); + + $transactionDate = $row->get('transaction_date'); + $transactionTime = $row->get('transaction_time'); + $postingDate = $row->get('posting_date'); + $transactionDescription = $row->get('transaction_description'); + $transactionRef = $row->get('transaction_ref'); + $amount = $row->get('amount'); + $tellerId = $row->get('teller_id'); + $branchChannel = $row->get('branch_channel'); + $transactionCode = $row->get('transaction_code'); + + $transaction = new StatementTransaction([ + 'statement_id' => $statement->id, + 'transaction_date' => $transactionDate, + 'transaction_time' => $transactionTime, + 'posting_date' => $postingDate, + 'transaction_description' => $transactionDescription, + 'transaction_ref' => $transactionRef, + 'amount' => $amount, + 'teller_id' => $tellerId, + 'branch_channel' => $branchChannel, + 'transaction_code' => $transactionCode, + ]); + + $transaction->save(); + } + } + } +} diff --git a/app/Classes/Modules/PerfexCRM/Processors/BookingToPerfexCRMProcessor.php b/app/Classes/Modules/PerfexCRM/Processors/BookingToPerfexCRMProcessor.php index 76b01825..930d3765 100644 --- a/app/Classes/Modules/PerfexCRM/Processors/BookingToPerfexCRMProcessor.php +++ b/app/Classes/Modules/PerfexCRM/Processors/BookingToPerfexCRMProcessor.php @@ -2,6 +2,7 @@ namespace App\Classes\Modules\PerfexCRM\Processors; +use App\Classes\Exceptions\MalformedRequestException; use App\Classes\Modules\PerfexCRM\DataTransferObjects\UpdatePerfexCRMObject; use App\Classes\Jobs\UpdatePerfexCRM; use App\Models\Booking; @@ -11,10 +12,10 @@ class BookingToPerfexCRMProcessor { /** * @param Booking $booking - * @return \Illuminate\Database\Eloquent\Model - * @throws \App\Classes\Exceptions\MalformedRequestException + * @return bool + * @throws MalformedRequestException */ - public function execute(Booking $booking) + public function execute(Booking $booking): bool { $companyReference = $booking->company->reference; $companyName = $booking->company->name; @@ -37,6 +38,7 @@ class BookingToPerfexCRMProcessor [], [] ); + UpdatePerfexCRM::dispatch($updatePerfexCRMObject, null, null); return true; diff --git a/app/Classes/Modules/PerfexCRM/Processors/TransactionToPerfexCRMProcessorV2.php b/app/Classes/Modules/PerfexCRM/Processors/TransactionToPerfexCRMProcessorV2.php new file mode 100644 index 00000000..cd5990f1 --- /dev/null +++ b/app/Classes/Modules/PerfexCRM/Processors/TransactionToPerfexCRMProcessorV2.php @@ -0,0 +1,246 @@ +type, [TransactionType::PAYMENT, TransactionType::BILL, TransactionType::PURCHASE_ORDER])) { + $transaction = ($model->type === TransactionType::BILL ? $model->owner : $model); + $bookingInfo = $this->extractBookingInfo($transaction); + $projectName = 'Exchange | ' . $bookingInfo['serviceTypeName'] . ' | ' . $bookingInfo['bookingMarking']; + $tasks = $this->defineTasks($model, $status); + + if(count($tasks) > 0) { + $updatePerfexCRMObject = new UpdatePerfexCRMObject( + $bookingInfo['companyName'], + $bookingInfo['companyReference'], + $bookingInfo['contactName'], + $bookingInfo['contactEmail'], + $bookingInfo['bookingMarking'], + $projectName, + PerfexCRMProjectStatus::IN_PROGRESS, + [], + $tasks + ); + $this->dispatchUpdateJob($transaction, $status, $updatePerfexCRMObject); + } + } + } catch (\Exception $exception) { + Log::error('TransactionToPerfexCRMProcessor debug:'); + Log::error($exception); + } + return true; + } + + private function extractBookingInfo(Transaction $model): array + { + $bookingInfo = []; + $bookingInfo['companyReference'] = $model->owner->company->reference; + $bookingInfo['companyName'] = $model->owner->company->name; + $bookingInfo['contactEmail'] = $model->owner->company->employees()->first()->email; + $bookingInfo['contactName'] = $model->owner->company->employees()->first()->name; + $bookingInfo['bookingMarking'] = $model->owner->marking; + $bookingInfo['serviceTypeName'] = $model->owner->company->services()->where('id', $model->owner->service_id)->first()->name; + + return $bookingInfo; + } + + private function defineTasks(Transaction $model, int $status): array + { + $tasks = []; + $ownerServiceId = $model->type === TransactionType::BILL ? $model->owner->owner->service_id : $model->owner->service_id; + + if ($status === ApprovalStatus::PENDING_VERIFICATION) { + $tasks = $this->handlePendingVerificationStatus($model, $ownerServiceId); + } elseif ($status === ApprovalStatus::APPROVED) { + $tasks = $this->handleApprovedStatus($model, $ownerServiceId); + } + + return $tasks; + } + + private function handlePendingVerificationStatus(Transaction $model, int $serviceId): array + { + $tasks = []; + switch($model->type) { + case TransactionType::PAYMENT: + $tasks = $this->definePaymentTasks($model); + break; + case TransactionType::PURCHASE_ORDER: + if ($serviceId === 1 || $serviceId === 3) { + $task = PerfexCRMTasks::TASK_PURCHASE_ORDER_1; + $task['status'] = PerfexCRMTaskStatus::IN_PROGRESS; + $tasks = [$task]; + } + break; + case TransactionType::BILL: + $tasks = $this->handleBillPendingStatus($serviceId); + break; + } + + return $tasks; + } + + private function handleApprovedStatus(Transaction $model, int $serviceId): array + { + $tasks = []; + switch($model->type) { + case TransactionType::PAYMENT: + $tasks = $this->handlePaymentApprovedStatus($serviceId); + break; + case TransactionType::BILL: + if ($serviceId === 4) { + $tasks = $this->completeTask(PerfexCRMTasks::TASK_1688_PAYMENT_9, PerfexCRMTasks::TASK_1688_PAYMENT_10); + } + break; + } + + return $tasks; + } + + private function handlePaymentApprovedStatus(int $serviceId): array + { + $tasks = []; + switch ($serviceId) { + case 1: + $tasks = $this->completeTask(PerfexCRMTasks::TASK_1_DAY_TRANSFER_2, PerfexCRMTasks::TASK_1_DAY_TRANSFER_5); + break; + case 3: + $tasks = $this->completeTask(PerfexCRMTasks::TASK_3_DAY_TRANSFER_2, PerfexCRMTasks::TASK_3_DAY_TRANSFER_5); + break; + case 4: + $tasks = $this->completeTask(PerfexCRMTasks::TASK_1688_PAYMENT_2, PerfexCRMTasks::TASK_1688_PAYMENT_5); + break; + } + + return $tasks; + } + + private function handleBillPendingStatus(int $serviceId): array + { + $tasks = []; + switch ($serviceId) { + case 1: + $tasks = $this->completeTask(PerfexCRMTasks::TASK_1_DAY_TRANSFER_5, PerfexCRMTasks::TASK_1_DAY_TRANSFER_6); + break; + case 3: + $tasks = $this->completeTask(PerfexCRMTasks::TASK_3_DAY_TRANSFER_5, PerfexCRMTasks::TASK_3_DAY_TRANSFER_6); + break; + case 4: + $tasks = $this->completeTask(PerfexCRMTasks::TASK_1688_PAYMENT_5, PerfexCRMTasks::TASK_1688_PAYMENT_7); + break; + } + + return $tasks; + } + + private function definePaymentTasks(Transaction $model): array + { + $tasks = [ +// PerfexCRMTasks::TASK_1 + ]; + + if ($model->owner->service_id === 1) { + $tasks = array_merge($tasks, $this->oneDayTransferTasks()); + } elseif ($model->owner->service_id === 3) { + $tasks = array_merge($tasks, $this->threeDayTransferTasks()); + } elseif ($model->owner->service_id === 4) { + $tasks = array_merge($tasks, $this->payment1688Tasks()); + } + + if ($model->owner->service_id === 1 || $model->owner->service_id === 3) { + $purchaseOrder = $model->booking->transactions()->where('type', TransactionType::PURCHASE_ORDER)->complete()->first(); + if(is_null($purchaseOrder)){ + $tasks = array_merge($tasks, $this->purchaseOrderTasks()); + } + } + + return $tasks; + } + + private function oneDayTransferTasks(): array + { + return [ +// PerfexCRMTasks::TASK_1_DAY_TRANSFER_1, + PerfexCRMTasks::TASK_1_DAY_TRANSFER_2, +// PerfexCRMTasks::TASK_1_DAY_TRANSFER_3, +// PerfexCRMTasks::TASK_1_DAY_TRANSFER_3_1, +// PerfexCRMTasks::TASK_1_DAY_TRANSFER_4, + PerfexCRMTasks::TASK_1_DAY_TRANSFER_5, + PerfexCRMTasks::TASK_1_DAY_TRANSFER_6 + ]; + } + + private function threeDayTransferTasks(): array + { + return [ +// PerfexCRMTasks::TASK_3_DAY_TRANSFER_1, + PerfexCRMTasks::TASK_3_DAY_TRANSFER_2, +// PerfexCRMTasks::TASK_3_DAY_TRANSFER_3, +// PerfexCRMTasks::TASK_3_DAY_TRANSFER_3_1, +// PerfexCRMTasks::TASK_3_DAY_TRANSFER_4, + PerfexCRMTasks::TASK_3_DAY_TRANSFER_5, + PerfexCRMTasks::TASK_3_DAY_TRANSFER_6 + ]; + } + + private function payment1688Tasks(): array + { + return [ +// PerfexCRMTasks::TASK_1688_PAYMENT_1, + PerfexCRMTasks::TASK_1688_PAYMENT_2, +// PerfexCRMTasks::TASK_1688_PAYMENT_3, +// PerfexCRMTasks::TASK_1688_PAYMENT_3_1, +// PerfexCRMTasks::TASK_1688_PAYMENT_4, + PerfexCRMTasks::TASK_1688_PAYMENT_5, +// PerfexCRMTasks::TASK_1688_PAYMENT_6, + PerfexCRMTasks::TASK_1688_PAYMENT_7, + PerfexCRMTasks::TASK_1688_PAYMENT_8, + PerfexCRMTasks::TASK_1688_PAYMENT_9, + PerfexCRMTasks::TASK_1688_PAYMENT_10, + PerfexCRMTasks::TASK_1688_PAYMENT_11, + PerfexCRMTasks::TASK_1688_PAYMENT_12, +// PerfexCRMTasks::TASK_1688_PAYMENT_13 + ]; + } + + private function purchaseOrderTasks(): array + { + $potask1 = PerfexCRMTasks::TASK_PURCHASE_ORDER_1; + $potask1['status'] = PerfexCRMTaskStatus::NOT_STARTED; + + return [ + $potask1, +// PerfexCRMTasks::TASK_PURCHASE_ORDER_2 + ]; + } + + private function dispatchUpdateJob(Transaction $model, int $status, UpdatePerfexCRMObject $updatePerfexCRMObject) + { + $withInvoice = ($model->type === TransactionType::PAYMENT && $model->owner === Booking::class && $status === ApprovalStatus::APPROVED); + UpdatePerfexCRMPrelude::dispatch($model, $updatePerfexCRMObject, $withInvoice); + } + + private function completeTask($startTask, $endTask) { + $startTask['status'] = PerfexCRMTaskStatus::COMPLETED; + $endTask['status'] = PerfexCRMTaskStatus::IN_PROGRESS; + + return [$startTask, $endTask]; + } +} diff --git a/app/Classes/Modules/PerfexCRM/Processors/UpdatePerfexCRMProcessor.php b/app/Classes/Modules/PerfexCRM/Processors/UpdatePerfexCRMProcessor.php index 752c9644..7527760f 100644 --- a/app/Classes/Modules/PerfexCRM/Processors/UpdatePerfexCRMProcessor.php +++ b/app/Classes/Modules/PerfexCRM/Processors/UpdatePerfexCRMProcessor.php @@ -109,7 +109,7 @@ class UpdatePerfexCRMProcessor */ public function execute(UpdatePerfexCRMObject $updatePerfexCRMObject) { $projectId = ""; - + // Customer has to exist first before Project can appear under it // Check with Perfex CRM, if this user (email) was previously a lead, should automatically now become a customer $crmCompany = $updatePerfexCRMObject->getCompanyName(); @@ -205,7 +205,9 @@ class UpdatePerfexCRMProcessor // Get existing or create task $result = $this->fetchesPerfexCRMTask->execute($tasks[$count]['name'], $milestoneId, 'project', $projectId); if(isset($result->payload)){ - if($taskStatus != PerfexCRMTaskStatus::NOT_STARTED && $result->payload[0]['status'] == PerfexCRMTaskStatus::NOT_STARTED) + //&& $result->payload[0]['status'] == PerfexCRMTaskStatus::NOT_STARTED + Log::info(json_encode([$taskStatus])); + if($taskStatus != PerfexCRMTaskStatus::NOT_STARTED ) { $task = $result->payload[0]; $result = $this->updatesPerfexCRMTask->execute($task['id'], $task['name'], $task['milestone'], $task['rel_id'], $taskStatus, $task['startdate'], is_null($task['duedate']) ? '': $task['duedate']); diff --git a/app/Classes/Modules/Transactions/Processors/CreateSupplierTransactionProcessor.php b/app/Classes/Modules/Transactions/Processors/CreateSupplierTransactionProcessor.php index 65a52d99..9b944a5b 100644 --- a/app/Classes/Modules/Transactions/Processors/CreateSupplierTransactionProcessor.php +++ b/app/Classes/Modules/Transactions/Processors/CreateSupplierTransactionProcessor.php @@ -90,10 +90,11 @@ class CreateSupplierTransactionProcessor $object = new TransactionObject($billNumber, TransactionType::BILL, $supplier->id, 1, $supplier->banks()->where('default', true)->first()->id, PaymentMethodType::CASH, $payment->original_amount * (1 / $rate), $payment->original_amount, 1, $payment->original_currency_id, - $rate, 0, $serviceCharge, null, ApprovalStatus::PENDING_VERIFICATION); + $rate, 0, $serviceCharge, null, ApprovalStatus::PENDING_SUBMISSION); /** @var Transaction $billTransaction */ $billTransaction = $this->createsTransaction->execute($payment, $object); + $this->updatesTransactionStatus->execute($billTransaction, ApprovalStatus::PENDING_VERIFICATION); $this->pushBill($billTransaction); $transferFeeNumber = $this->generatesTransactionBillNumber->execute('TRFR-'); @@ -140,4 +141,4 @@ class CreateSupplierTransactionProcessor $this->transferFee->push($transferFee); } -} \ No newline at end of file +} diff --git a/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php b/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php index 2091c3cf..9d6b9fe4 100644 --- a/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php +++ b/app/Classes/Modules/Transactions/Services/UpdatesTransactionStatus.php @@ -3,23 +3,24 @@ namespace App\Classes\Modules\Transactions\Services; use App\Classes\General\Eloquent\AbstractUpdateRecord; +use App\Classes\Modules\PerfexCRM\Processors\TransactionToPerfexCRMProcessorV2; use App\Models\Transaction; use App\Classes\Modules\PerfexCRM\Processors\TransactionToPerfexCRMProcessor; class UpdatesTransactionStatus extends AbstractUpdateRecord { - /** @var TransactionToPerfexCRMProcessor */ + /** @var TransactionToPerfexCRMProcessorV2 */ private $transactionToPerfexCRMProcessor; /** - * UpdatesTransactionStatus constructor. - * @param TransactionToPerfexCRMProcessor $transactionToPerfexCRMProcessor + * @param TransactionToPerfexCRMProcessorV2 $transactionToPerfexCRMProcessor */ - public function __construct(TransactionToPerfexCRMProcessor $transactionToPerfexCRMProcessor) + public function __construct(TransactionToPerfexCRMProcessorV2 $transactionToPerfexCRMProcessor) { $this->transactionToPerfexCRMProcessor = $transactionToPerfexCRMProcessor; } + /** * @param Transaction $model * @param int $status diff --git a/app/Classes/ValueObjects/Constants/FileType.php b/app/Classes/ValueObjects/Constants/FileType.php index d5404e7a..fc6dbf1b 100644 --- a/app/Classes/ValueObjects/Constants/FileType.php +++ b/app/Classes/ValueObjects/Constants/FileType.php @@ -23,6 +23,7 @@ class FileType 'application/pdf' => 'pdf', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'excel', 'application/vnd.ms-excel' => 'excel', + 'text/plain' => 'text', ]; -} \ No newline at end of file +} diff --git a/app/Classes/ValueObjects/Constants/PerfexCRMTasks.php b/app/Classes/ValueObjects/Constants/PerfexCRMTasks.php index 9e19ccec..4129f2a8 100644 --- a/app/Classes/ValueObjects/Constants/PerfexCRMTasks.php +++ b/app/Classes/ValueObjects/Constants/PerfexCRMTasks.php @@ -43,9 +43,9 @@ class PerfexCRMTasks â—‹ Outcomes: The payment will be approved in exchange, allowing the next steps in the process to be initiated.
', 'milestone' => '', 'reference' => 'TASK_1_DAY_TRANSFER_2', - 'on_task_completion' => 'TASK_1_DAY_TRANSFER_3,TASK_1_DAY_TRANSFER_5', + 'on_task_completion' => 'TASK_1_DAY_TRANSFER_5', 'department' => 'Accounts', - 'status' => '', + 'status' => PerfexCRMTaskStatus::IN_PROGRESS, 'priority' => PerfexCRMTaskPriority::HIGH, 'duedate' => 0 ]; @@ -160,9 +160,9 @@ class PerfexCRMTasks â—‹ Link to order page: {link_transfer}
', 'milestone' => '', 'reference' => 'TASK_3_DAY_TRANSFER_2', - 'on_task_completion' => 'TASK_3_DAY_TRANSFER_3,TASK_3_DAY_TRANSFER_5', + 'on_task_completion' => 'TASK_3_DAY_TRANSFER_5', 'department' => 'Accounts', - 'status' => '', + 'status' => PerfexCRMTaskStatus::IN_PROGRESS, 'priority' => PerfexCRMTaskPriority::HIGH, 'duedate' => 0 ]; @@ -286,9 +286,9 @@ class PerfexCRMTasks â—‹ Outcomes: The payment will be approved in exchange, allowing the next steps in the process to be initiated.
', 'milestone' => '', 'reference' => 'TASK_1688_PAYMENT_2', - 'on_task_completion' => 'TASK_1688_PAYMENT_3,TASK_1688_PAYMENT_5', + 'on_task_completion' => 'TASK_1688_PAYMENT_5', 'department' => 'Accounts', - 'status' => '', + 'status' => PerfexCRMTaskStatus::IN_PROGRESS, 'priority' => PerfexCRMTaskPriority::HIGH, 'duedate' => 0 ]; @@ -353,7 +353,7 @@ class PerfexCRMTasks â—‹ Outcomes: The order is placed with a supplier and the customer\'s order is confirmed.
', 'milestone' => '', 'reference' => 'TASK_1688_PAYMENT_5', - 'on_task_completion' => 'TASK_1688_PAYMENT_6', + 'on_task_completion' => 'TASK_1688_PAYMENT_7', 'department' => 'Operations', 'status' => '', 'priority' => PerfexCRMTaskPriority::HIGH, @@ -454,14 +454,14 @@ class PerfexCRMTasks â—‹ link: {link_transfer}
', 'milestone' => '', 'reference' => 'TASK_1688_PAYMENT_12', - 'on_task_completion' => 'TASK_1688_PAYMENT_13', + 'on_task_completion' => '', 'department' => 'Operations', 'status' => '', 'priority' => PerfexCRMTaskPriority::LOW, 'duedate' => 1 ]; public const TASK_1688_PAYMENT_13 = [ - 'name' => 'Complete Order bookeeping', + 'name' => 'Complete Order bookkeeping', 'description' => 'â—‹ Purpose: To complete the bookkeeping for the booking.
â—‹ Initial Status: Not Started
â—‹ Deadline: Same day
@@ -493,7 +493,7 @@ class PerfexCRMTasks â—‹ Outcomes: Bank transaction is mapped successfully, allowing the next steps in the process to be initiated.
', 'milestone' => '', 'reference' => 'TASK_PURCHASE_ORDER_1', - 'on_task_completion' => 'TASK_PURCHASE_ORDER_2', + 'on_task_completion' => '', 'department' => 'Operations', 'status' => PerfexCRMTaskStatus::IN_PROGRESS, 'priority' => PerfexCRMTaskPriority::LOW, diff --git a/app/Classes/ValueObjects/Constants/StatementTransactionOwnerType.php b/app/Classes/ValueObjects/Constants/StatementTransactionOwnerType.php new file mode 100644 index 00000000..24d31516 --- /dev/null +++ b/app/Classes/ValueObjects/Constants/StatementTransactionOwnerType.php @@ -0,0 +1,37 @@ + self::EXCHANGE, + 'shipping_portal' => self::SHIPPING_PORTAL, + 'izyim' => self::SHIPPING_PORTAL, + ]; +} diff --git a/app/Http/Controllers/Accounting/ApproveDuplicateBankStatementDetailsStatusController.php b/app/Http/Controllers/Accounting/ApproveDuplicateBankStatementDetailsStatusController.php new file mode 100644 index 00000000..0c026835 --- /dev/null +++ b/app/Http/Controllers/Accounting/ApproveDuplicateBankStatementDetailsStatusController.php @@ -0,0 +1,20 @@ +execute($request); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Accounting/BankStatementController.php b/app/Http/Controllers/Accounting/BankStatementController.php new file mode 100644 index 00000000..ff1324b4 --- /dev/null +++ b/app/Http/Controllers/Accounting/BankStatementController.php @@ -0,0 +1,365 @@ +input('account'); + $search = $request->input('search'); + + $accounts = StatementAccount::all(); + + $statementsQuery = AccountStatement::query(); + + if ($selectedAccount) { + $statementsQuery->where('statement_account_id', $selectedAccount); + } + + if ($search) { + $statementsQuery->where(function ($query) use ($search) { + $query->where('date_from', 'LIKE', "%$search%") + ->orWhere('date_to', 'LIKE', "%$search%") + ->orWhere('total_amount', 'LIKE', "%$search%") + ->orWhere('begin_balance', 'LIKE', "%$search%") + ->orWhere('end_balance', 'LIKE', "%$search%"); + }); + } + + $statements = $statementsQuery->orderBy('date_from')->paginate(10); + + return view('pages.accounting.bank-statements.index', compact('accounts', 'selectedAccount', 'search', 'statements')); + } + + public function indexv2(Request $request) + { + $selectedAccount = $request->input('account'); + $search = $request->input('search'); + + $accounts = StatementAccount::all(); + + $statementsQuery = AccountStatement::query(); + + if ($selectedAccount) { + $statementsQuery->where('statement_account_id', $selectedAccount); + } + + if ($search) { + $statementsQuery->where(function ($query) use ($search) { + $query->where('date_from', 'LIKE', "%$search%") + ->orWhere('date_to', 'LIKE', "%$search%") + ->orWhere('total_amount', 'LIKE', "%$search%") + ->orWhere('begin_balance', 'LIKE', "%$search%") + ->orWhere('end_balance', 'LIKE', "%$search%"); + }); + } + + $statements = $statementsQuery->paginate(10); + + return view('pages.accounting.bank-statements.indexv2', compact('accounts', 'selectedAccount', 'search', 'statements')); + } + + public function import(Request $request, ImportBankStatementLogic $logic): JsonResponse + { + return $logic->execute($request); + } + + public function rerun() + { + CreateBankStatementTransactionOwners::dispatch(); + return redirect()->back()->with('success', 'Rerun triggered successfully'); + } + + public function show(AccountStatement $statement, Request $request) + { + $transactions = $statement->transactions(); + // $account = $statement->account(); + // dd(json_encode($account->where('id', '>=', 1)->first())); + // dd(json_encode($transactions->where('id', '>=', 1)->first())); + if ($request->get('transaction_filter')) { + $transactionFilter = $request->get('transaction_filter'); + $transactions = $transactions->where('transaction_description', 'LIKE', "%$transactionFilter%"); + } + + if ($request->get('from_amount_filter')) { + $fromAmountFilter = $request->get('from_amount_filter'); + $transactions = $transactions->where('amount', '>=', $fromAmountFilter); + } + + if ($request->get('to_amount_filter')) { + $toAmountFilter = $request->get('to_amount_filter'); + $transactions = $transactions->where('amount', '<=', $toAmountFilter); + } + + // $transactions = $transactions->paginate(100); + $transactions = $transactions->get(); + echo $this->process3_merged($transactions); + + //return view('pages.accounting.bank-statements.show', compact('statement', 'transactions')); + } + + public function download(AccountStatement $statement) + { + $transactions = $statement->transactions; + + $csvExporter = new \Laracsv\Export(); + $csvExporter->build($transactions, ['transaction_date', 'transaction_time', 'posting_date', 'transaction_description', 'transaction_ref', 'debit', 'credit']) + ->download($statement->date_from->format('Y-m-d') . '_' . $statement->date_to->format('Y-m-d') . '_statement.csv'); + } + + public function fetch(Request $request, ListBankStatementDetailsLogic $logic): JsonResponse + { + return $logic->execute($request); + } + + public function transactions(Request $request, ListBankStatementTransactionsLogic $logic): JsonResponse + { + return $logic->execute($request); + } + + public function update(Request $request, UpdateBankStatementDetailLogic $logic): JsonResponse + { + return $logic->execute($request); + } + + private function process3_merged($transactions){ + + // $statement = $transactions[0]->statement(); + // dd(json_encode($statement->first())); + + $headers = [ + 'Date', + 'Bank', + 'Description', + 'Credit', + 'Debit', + 'Pay For', + 'System', + 'System Reference', + 'Human Reference', + 'Multiple', + 'Match?', + 'System Amount' + ]; + + $branches = [ + 0 => 'MBB Cyber', + 1 => 'MBB SS2', + ]; + + $yes = 'Yes'; + $no = 'No'; + + $table = ''; + $count = 0; + + foreach ($transactions as $row) { + $isExist = StatementTransactionOwner::where('statement_transaction_id', $row->id)->first(); + + if ($isExist) { + continue; + } + + $count++; + $credit = 0.00; + $debit = 0.00; + + // dd(json_encode($row['posting_date'])); + $date = new DateTime($row['posting_date']); + $description = $row['transaction_description_2']; + + if($row['amount'] < 0){ + $debit = (float) $row['amount']; + } + else{ + $credit = (float) $row['amount']; + } + + $creditTransactions = []; + $debitTransactions = []; + + $system = ''; + $systemReference = null; + $systemAmount = null; + + if($credit){ + $creditTransactions = $this->getTransactions($date, $credit, TransactionType::PAYMENT, Booking::class, PaymentMethodType::WALLET, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + foreach ($creditTransactions as $transaction) { + $systemReference[] = $transaction->owner instanceof Booking ? $transaction->owner->marking : $transaction->bill_no; + $systemAmount[] = $transaction->amount; + $system[] = 'EXCHANGE'; + } + + $creditTransactions = $this->getTransactions($date, $credit, TransactionType::TOP_UP, Wallet::class, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + foreach ($creditTransactions as $transaction) { + $systemReference[] = $transaction->owner instanceof Booking ? $transaction->owner->marking : $transaction->bill_no; + $systemAmount[] = $transaction->amount; + $system[] = 'EXCHANGE'; + } + + $creditTransactions = $this->getTransactionsFromShippingPortal($credit, $this->getDateRange($row['posting_date'])); + foreach ($creditTransactions as $transaction) { + $systemReference[] = $transaction['order']['reference']; + $systemAmount[] = $transaction['amount']; + $system[] = 'SHIPPING'; + } + } + + if($debit){ + $debitTransactions = $this->getTransactions($date, $debit, null, null, null, [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED], Group::class); + + if(!count($debitTransactions)) { + foreach (['YSN', 'HCK', 'ATVANTIC', 'HIGH HILL'] as $reference){ + if(str_contains($description, $reference)) { + $paymentDate = date('Y-m-d', strtotime('+1 day', strtotime($row['posting_date']))); //$date->addDays(1)->format('Y-m-d'); + + if($reference = 'ATVANTIC'){ + $paymentDate = date('Y-m-d', strtotime($row['posting_date']));//$date->format('Y-m-d'); + } + $issuer = Company::where('name', 'like', '%'.$reference.'%')->get()->pluck('id'); + $debitTransactions = Group::whereIn('issuer', $issuer)->whereDate('created_at', $paymentDate)->get(); + break; + } + } + + } + + foreach ($debitTransactions as $transaction) { + $systemReference[] = $transaction->reference; + $systemAmount[] = $transaction->amount; + $system[] = 'EXCHANGE'; + } + } + + + $multiple = count($creditTransactions) + count($debitTransactions) > 1 ? $yes : $no; + + + + + + $systemReference = $systemReference ? implode(',', $systemReference) : null; + $systemAmount = $systemAmount ? implode(',', $systemAmount) : null; + + $matches = $systemReference == $row['remarkreferences'] ? $yes : $no; + + $table .= ' + + + + + + + + + + + + + '; + + //AccountStatement + // $row->statement()->first()->id) + + $statementTransactionsDetail = new StatementTransactionOwner([ + 'date' => $date, + 'statement_transaction_id' => $row->id, + 'description' => is_null($description) ? "" : $description, + 'credit' => $credit, + 'debit' => $debit, + 'pay_for' => $system, + 'system_references' => is_null($systemReference) ? "" : $systemReference, + 'remark_references' => is_null($row['remarkreferences']) ? "" : $row['remarkreferences'], + 'is_multiple' => $multiple == "Yes" ? 1 : 0, + 'is_matches' => $matches == "Yes" ? 1 : 0, + 'system_amounts'=> is_null($systemAmount) ? "" : $systemAmount, + ]); + + $statementTransactionsDetail->save(); + + if($count == 10){ + break; + } + } + + $table .= '
'.implode('', $headers).'
'.$date->format('d-m-Y').'branch'.$description.''.$credit.''.$debit.''.$row['pay_for'].''.$system.''.$systemReference.''.$row['remarkreferences'].''.$multiple.''.$matches.''.$systemAmount.'
'; + + 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; + } + +} diff --git a/app/Http/Controllers/Accounting/GroupApproveStatementTransactionController.php b/app/Http/Controllers/Accounting/GroupApproveStatementTransactionController.php new file mode 100644 index 00000000..9f242704 --- /dev/null +++ b/app/Http/Controllers/Accounting/GroupApproveStatementTransactionController.php @@ -0,0 +1,20 @@ +execute($request); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Accounting/UpdateStatementTransactionStatusController.php b/app/Http/Controllers/Accounting/UpdateStatementTransactionStatusController.php new file mode 100644 index 00000000..dd45e571 --- /dev/null +++ b/app/Http/Controllers/Accounting/UpdateStatementTransactionStatusController.php @@ -0,0 +1,20 @@ +execute($request); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Imports/ImportStatementInvoiceController.php b/app/Http/Controllers/Imports/ImportStatementInvoiceController.php new file mode 100644 index 00000000..a59f9463 --- /dev/null +++ b/app/Http/Controllers/Imports/ImportStatementInvoiceController.php @@ -0,0 +1,99 @@ +input('files'), '', ApprovalStatus::APPROVED, 'imports'); + $file = json_decode($object->getFiles()[0])->file_info->original->file; + + $import = new GenericImport(); + Excel::import($import, $file); + $excelRows = $import->rows; + $excelRows = $excelRows->toArray(); + + foreach ($excelRows as $row) { + dd($row); + // $row['debtor_code'] + + // attempt 1 - try map by amount and date + // $transactionDate = $this->changeExcelDate($row['date']); + // $transaction = Transaction::where('original_amount', $row['total'])->whereDate('created_at', $transactionDate)->get(); + // if ($transaction) { + // // check company + // // $company = Company::where('debtor', $row['debtor_code'])->first(); + // // dd($company); + // // try to verify is it the correct transaction + // } + + // Shipping Info + // TOPUP -> map with transaction.bill_no + if (str_starts_with($row['shipping_info'], 'TOPUP')) { + // find in exchange first, if cannont then find in izyim + // (App()->make(ChecksBillNumber::class))->execute($bill_no, 'exchange'); + } + + // if 5 digits -> exchange booking reference + // find transation + // find statement_transaction_owners, and fill up the details + + // if <5 digits, find the transaction id (order number in izyim), find the payment in izyim + // find transation + // find statement_transaction_owners, and fill up the details + + // dd([ + // 'type' => $statementTransactionOwnerType, + // 'system' => $system, + // // 'owner_type' => Transaction::class, + // // todo-new: make sure owner_type is a class + // 'owner_type' => $owner_type, + // 'owner_id' => $owner_id, + // 'owner_reference' => $owner_reference + // ]); + + // $bankStatementTransaction->owners()->firstOrCreate([ + // 'type' => $statementTransactionOwnerType, + // 'system' => $system, + // // 'owner_type' => Transaction::class, + // // todo-new: make sure owner_type is a class + // 'owner_type' => $owner_type, + // 'owner_id' => $owner_id, + // 'owner_reference' => $owner_reference + // ]); + + + } + } + + public function changeExcelDate($date) + { + $unixTime = (($date - 25569) * 86400); + $date = new DateTime("@$unixTime"); + return $date->format('Y-m-d'); // Change the format to 'Y-m-d' + } +} diff --git a/app/Http/Controllers/Imports/ImportStatementReceiptsController.php b/app/Http/Controllers/Imports/ImportStatementReceiptsController.php new file mode 100644 index 00000000..22111881 --- /dev/null +++ b/app/Http/Controllers/Imports/ImportStatementReceiptsController.php @@ -0,0 +1,50 @@ +input('files'), '', ApprovalStatus::APPROVED, 'imports'); + $file = json_decode($object->getFiles()[0])->file_info->original->file; + + $import = new GenericImport(); + Excel::import($import, $file); + $excelRows = $import->rows; + $excelRows = $excelRows->toArray(); + + foreach ($excelRows as $row) { + // if has date column + // $transactionDate = $this->changeExcelDate($row['date']); + } + } + + public function changeExcelDate($date) + { + $unixTime = (($date - 25569) * 86400); + $date = new DateTime("@$unixTime"); + return $date->format('Y-m-d'); // Change the format to 'Y-m-d' + } +} diff --git a/app/Http/Resources/BankStatementTransactionOwnerResource.php b/app/Http/Resources/BankStatementTransactionOwnerResource.php new file mode 100644 index 00000000..468d9ec5 --- /dev/null +++ b/app/Http/Resources/BankStatementTransactionOwnerResource.php @@ -0,0 +1,60 @@ +system === 'EXCHANGE') { + if($this->owner_type === Transaction::class){ + if($this->type === StatementTransactionOwnerType::SALES){ + $referenceLink = route('booking.details', $this->owner_reference); + } + + if($this->type === StatementTransactionOwnerType::WALLET_TOP_UP){ + $referenceLink = route('booking.details', $this->owner_reference); + } + } + } + + if($this->system === 'SHIPPING_PORTAL') { + if($this->owner_type === Transaction::class){ + if($this->type === StatementTransactionOwnerType::SALES){ + $referenceLink = 'https://izyim.cief-malaysia.com/order/show/'. $this->owner_reference; + } + if($this->type === StatementTransactionOwnerType::WALLET_TOP_UP){ + $referenceLink = 'https://izyim.cief-malaysia.com/wallet/'. $this->owner_reference .'/details'; + } + } + } + + return [ + 'id' => $this->id, + 'type' => $this->type, + 'system' => $this->system, + 'owner_type' => $this->owner_type, + 'owner_id' => $this->owner_id, + 'reference' => $this->owner_reference, + 'reference_link' => $referenceLink, + 'invoice_reference' => $this->invoice_reference, + 'receipt_reference' => $this->receipt_reference, + 'status' => $this->status + ]; + } +} diff --git a/app/Http/Resources/BankStatementTransactionResource.php b/app/Http/Resources/BankStatementTransactionResource.php new file mode 100644 index 00000000..993b8c93 --- /dev/null +++ b/app/Http/Resources/BankStatementTransactionResource.php @@ -0,0 +1,41 @@ + $this->id, + 'account_number' => $this->statement->account->number, + 'account_type' => $this->statement->account->type, + 'account_name' => $this->statement->account->name, + 'account_statement_id' => $this->statement->id, + 'account_statement_date_from' => $this->statement->date_from, + 'account_statement_date_to' => $this->statement->date_to, + 'posting_date' => $this->posting_date->format('d-m-Y g:i A'), + 'amount' => $this->amount, + 'transaction_description_1' => $this->transaction_description, + 'transaction_description_2' => $this->transaction_description_2, + 'transaction_description_3' => $this->transaction_description_3, + 'transaction_description_4' => $this->transaction_description_4, + 'transaction_description_5' => $this->transaction_description_5, + 'owners' => [ + 'approved' => BankStatementTransactionOwnerResource::collection($this->owners()->whereIn('status', [ApprovalStatus::APPROVED])->get()), + 'pending_verification' => BankStatementTransactionOwnerResource::collection($this->owners()->whereIn('status', [ApprovalStatus::PENDING_VERIFICATION])->get()), + 'rejected' => BankStatementTransactionOwnerResource::collection($this->owners()->whereIn('status', [ApprovalStatus::REJECTED])->get()) + ] + ]; + } +} diff --git a/app/Models/AccountStatement.php b/app/Models/AccountStatement.php new file mode 100644 index 00000000..d9e53d60 --- /dev/null +++ b/app/Models/AccountStatement.php @@ -0,0 +1,35 @@ + 'date', + 'date_to' => 'date', + ]; + + public function account() + { + return $this->belongsTo(StatementAccount::class, 'statement_account_id', 'id'); + } + + public function transactions() + { + return $this->hasMany(StatementTransaction::class); + } +} diff --git a/app/Models/StatementAccount.php b/app/Models/StatementAccount.php new file mode 100644 index 00000000..741d2c5f --- /dev/null +++ b/app/Models/StatementAccount.php @@ -0,0 +1,23 @@ +hasMany(AccountStatement::class); + } +} diff --git a/app/Models/StatementTransaction.php b/app/Models/StatementTransaction.php new file mode 100644 index 00000000..80d6759f --- /dev/null +++ b/app/Models/StatementTransaction.php @@ -0,0 +1,50 @@ + 'datetime', + ]; + + public function account() + { + return $this->hasOneDeep(StatementAccount::class, [AccountStatement::class], ['id', 'id'], ['account_statement_id', 'statement_account_id']); + } + + public function statement() + { + return $this->belongsTo(AccountStatement::class, 'account_statement_id', 'id'); + } + + public function owners() + { + return $this->hasMany(StatementTransactionOwner::class); + } +} diff --git a/app/Models/StatementTransactionOwner.php b/app/Models/StatementTransactionOwner.php new file mode 100644 index 00000000..52e8d1fc --- /dev/null +++ b/app/Models/StatementTransactionOwner.php @@ -0,0 +1,29 @@ +belongsTo(StatementTransaction::class, 'statement_transaction_id', 'id'); + } +} diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 0df23186..eba18ee5 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -25,6 +25,10 @@ class Transaction extends AbstractModel implements Documentable, Transactionable use SoftDeletes; use LogData; + protected $casts = [ + 'type' => 'int' + ]; + protected $table = 'transactions'; public function owner(): morphTo @@ -112,6 +116,14 @@ class Transaction extends AbstractModel implements Documentable, Transactionable return $this->HasOne(GroupTransaction::class, 'transaction_id'); } + /** + * @return HasOne + */ + public function voucherRedemption(): HasOne + { + return $this->HasOne(VoucherRedemption::class, 'transaction_id', 'id'); + } + public function convert_original_amount() { if($this->booking()->first()->fix_currency_id !== 1) { diff --git a/config/perfexcrm.php b/config/perfexcrm.php index f67040f5..7644a489 100644 --- a/config/perfexcrm.php +++ b/config/perfexcrm.php @@ -1,7 +1,7 @@ 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'), ]; diff --git a/database/migrations/2023_03_26_190633_create_statement_accounts_table.php b/database/migrations/2023_03_26_190633_create_statement_accounts_table.php new file mode 100644 index 00000000..348c5ec0 --- /dev/null +++ b/database/migrations/2023_03_26_190633_create_statement_accounts_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('number')->unique(); + $table->string('type'); + $table->string('name'); + $table->string('currency'); + $table->timestamps(); + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('statement_accounts'); + } +} diff --git a/database/migrations/2023_03_26_190827_create_account_statements_table.php b/database/migrations/2023_03_26_190827_create_account_statements_table.php new file mode 100644 index 00000000..43945692 --- /dev/null +++ b/database/migrations/2023_03_26_190827_create_account_statements_table.php @@ -0,0 +1,39 @@ +id(); + $table->unsignedBigInteger('statement_account_id'); + $table->date('date_from'); + $table->date('date_to'); + $table->float('total_amount'); + $table->float('begin_balance'); + $table->float('end_balance'); + $table->timestamps(); + + $table->foreign('statement_account_id')->references('id')->on('statement_accounts'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('account_statements'); + } +} diff --git a/database/migrations/2023_03_26_190934_create_statement_transactions_table.php b/database/migrations/2023_03_26_190934_create_statement_transactions_table.php new file mode 100644 index 00000000..29cacb80 --- /dev/null +++ b/database/migrations/2023_03_26_190934_create_statement_transactions_table.php @@ -0,0 +1,47 @@ +id(); + $table->unsignedBigInteger('account_statement_id'); + $table->dateTime('transaction_date')->nullable(); + $table->dateTime('posting_date'); + $table->string('transaction_description')->nullable(); + $table->string('transaction_description_2')->nullable(); + $table->string('transaction_description_3')->nullable(); + $table->string('transaction_description_4')->nullable(); + $table->string('transaction_description_5')->nullable(); + $table->string('transaction_ref')->nullable(); + $table->float('amount', 15, 2)->unsigned(false); + $table->string('teller_id')->nullable(); + $table->string('branch_channel'); + $table->string('transaction_code'); + $table->string('end_balance')->nullable(); + $table->timestamps(); + + $table->foreign('account_statement_id')->references('id')->on('account_statements'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('statement_transactions'); + } +} diff --git a/database/migrations/2023_04_07_212512_create_statement_transaction_owners_table.php b/database/migrations/2023_04_07_212512_create_statement_transaction_owners_table.php new file mode 100644 index 00000000..5d22c520 --- /dev/null +++ b/database/migrations/2023_04_07_212512_create_statement_transaction_owners_table.php @@ -0,0 +1,45 @@ +id(); + $table->foreignId('statement_transaction_id'); + $table->integer('type')->default(StatementTransactionOwnerType::UNKNOWN); + $table->string('system')->nullable(); + $table->string('owner_type')->nullable(); + $table->bigInteger('owner_id')->nullable(); + $table->string('owner_reference')->nullable(); + $table->string('invoice_reference')->nullable(); + $table->string('receipt_reference')->nullable(); + $table->string('is_auto_mapped')->default(false); + $table->integer('status')->default(ApprovalStatus::PENDING_VERIFICATION); + + $table->foreign('statement_transaction_id')->references('id')->on('statement_transactions'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('statement_transaction_owners'); + } +} diff --git a/resources/assets/vue/components/accounting/elements/EditSingleItemInListComponent.vue b/resources/assets/vue/components/accounting/elements/EditSingleItemInListComponent.vue new file mode 100644 index 00000000..ed3f8388 --- /dev/null +++ b/resources/assets/vue/components/accounting/elements/EditSingleItemInListComponent.vue @@ -0,0 +1,141 @@ + + + diff --git a/resources/assets/vue/components/accounting/elements/StatementTransactionComponent.vue b/resources/assets/vue/components/accounting/elements/StatementTransactionComponent.vue new file mode 100644 index 00000000..db2fc252 --- /dev/null +++ b/resources/assets/vue/components/accounting/elements/StatementTransactionComponent.vue @@ -0,0 +1,161 @@ + + + diff --git a/resources/assets/vue/components/accounting/forms/ImportStatementFormComponent.vue b/resources/assets/vue/components/accounting/forms/ImportStatementFormComponent.vue new file mode 100644 index 00000000..54db23df --- /dev/null +++ b/resources/assets/vue/components/accounting/forms/ImportStatementFormComponent.vue @@ -0,0 +1,59 @@ + + diff --git a/resources/assets/vue/components/accounting/sections/StatementTransactionsDetailsComponent.vue b/resources/assets/vue/components/accounting/sections/StatementTransactionsDetailsComponent.vue new file mode 100644 index 00000000..9a5e0058 --- /dev/null +++ b/resources/assets/vue/components/accounting/sections/StatementTransactionsDetailsComponent.vue @@ -0,0 +1,232 @@ + + diff --git a/resources/assets/vue/components/accounting/sections/TransactionsMappingComponent.vue b/resources/assets/vue/components/accounting/sections/TransactionsMappingComponent.vue new file mode 100644 index 00000000..6e9e7982 --- /dev/null +++ b/resources/assets/vue/components/accounting/sections/TransactionsMappingComponent.vue @@ -0,0 +1,268 @@ + + diff --git a/resources/assets/vue/components/banks/forms/BankAccountFormComponent.vue b/resources/assets/vue/components/banks/forms/BankAccountFormComponent.vue index 6e9518d9..f0c58095 100644 --- a/resources/assets/vue/components/banks/forms/BankAccountFormComponent.vue +++ b/resources/assets/vue/components/banks/forms/BankAccountFormComponent.vue @@ -27,9 +27,10 @@
- + + {{ serviceType.id != 4 ? '*Please ensure to use the appropriate company name for corporate transfers, instead of personal names.' : ''}}
diff --git a/resources/assets/vue/components/bookings/elements/FilterBookingComponent.vue b/resources/assets/vue/components/bookings/elements/FilterBookingComponent.vue new file mode 100644 index 00000000..a236bf8a --- /dev/null +++ b/resources/assets/vue/components/bookings/elements/FilterBookingComponent.vue @@ -0,0 +1,134 @@ + + + \ No newline at end of file diff --git a/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue b/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue index 702745c7..79dddba0 100644 --- a/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue +++ b/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue @@ -33,6 +33,11 @@
{{ item.status === 2 ? 'Received' : item.status === 4 ? 'Rejected' : 'Submitted'}} On: {{item.updated_at}}
+
+
+
Bill Number: {{ item.bill_no }}
+
+
@@ -86,6 +91,11 @@
{{ item.transaction_bill.status === 1 ? 'Paid On: ' + item.updated_at : 'Transferred On:' + item.transaction_bill.updated_at }}
+
+
+
Bill Number: {{ item.bill_no }}
+
+
diff --git a/resources/assets/vue/components/bookings/forms/ChooseCurrencyComponent.vue b/resources/assets/vue/components/bookings/forms/ChooseCurrencyComponent.vue index e670a450..0fc1cd49 100644 --- a/resources/assets/vue/components/bookings/forms/ChooseCurrencyComponent.vue +++ b/resources/assets/vue/components/bookings/forms/ChooseCurrencyComponent.vue @@ -28,7 +28,7 @@ v-bind:key="currency.id">
+ @click="updateCurrency(currency)">
@@ -59,6 +59,19 @@ diff --git a/resources/assets/vue/components/segments/elements/SegmentCompanyComponent.vue b/resources/assets/vue/components/segments/elements/SegmentCompanyComponent.vue index 864c5887..3c516ac8 100644 --- a/resources/assets/vue/components/segments/elements/SegmentCompanyComponent.vue +++ b/resources/assets/vue/components/segments/elements/SegmentCompanyComponent.vue @@ -26,12 +26,12 @@ :endpoint="route('api.company.list')" :options="{ segments_in: [parameters.id], - with_total_payments: true, - recency: '2022-02-15', - frequency: 15, - business_type: 2, - with_bookings: true, - order_by: { column: 'total_payments', DESC: true }, + // with_total_payments: true, + // recency: '2022-02-15', + // frequency: 15, + // business_type: 2, + // with_bookings: true, + // order_by: { column: 'total_payments', DESC: true }, }" >