From 205dd90114c761da6e19eb2ef96c465337c25cec Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Wed, 26 Oct 2022 09:03:06 +0700 Subject: [PATCH 01/24] [Payment] Cancellation approval --- .../PaymentCancellationApprovalController.php | 50 +++++++++++++++++++ app/Model/Finance/Payment/Payment.php | 6 ++- routes/api/finance.php | 2 + 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php new file mode 100644 index 000000000..325cea49c --- /dev/null +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php @@ -0,0 +1,50 @@ +form->cancellation_approval_by = auth()->user()->id; + $Payment->form->cancellation_approval_at = now(); + $Payment->form->cancellation_status = 1; + $Payment->form->save(); + + return new ApiResource($Payment); + } + + /** + * @param Request $request + * @param $id + * @return ApiResource + * @throws ApprovalNotFoundException + * @throws UnauthorizedException + */ + public function reject(Request $request, $id) + { + $Payment = Payment::findOrFail($id); + $Payment->form->cancellation_approval_by = auth()->user()->id; + $Payment->form->cancellation_approval_at = now(); + $Payment->form->cancellation_approval_reason = $request->get('reason'); + $Payment->form->cancellation_status = -1; + $Payment->form->save(); + + return new ApiResource($Payment); + } +} diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index 13099fe38..a6d01fe98 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -3,6 +3,7 @@ namespace App\Model\Finance\Payment; use App\Exceptions\BranchNullException; +use App\Exceptions\IsReferencedException; use App\Exceptions\PointException; use App\Model\Accounting\Journal; use App\Model\Finance\PaymentOrder\PaymentOrder; @@ -47,7 +48,10 @@ public function isAllowedToUpdate() public function isAllowedToDelete() { - // TODO isAllowed to delete? + // Check if not referenced by another form + if (optional($this->payment)->count()) { + throw new IsReferencedException('Cannot delete form because it is already paid', $this->payment); + } } public static function create($data) diff --git a/routes/api/finance.php b/routes/api/finance.php index cfb3a6488..456c3ac8b 100644 --- a/routes/api/finance.php +++ b/routes/api/finance.php @@ -1,6 +1,8 @@ namespace('Finance')->group(function () { + Route::post('payments/{id}/cancellation-approve', 'Payment\\PaymentCancellationApprovalController@approve'); + Route::post('payments/{id}/cancellation-reject', 'Payment\\PaymentCancellationApprovalController@reject'); Route::apiResource('payments', 'Payment\\PaymentController'); Route::post('payment-orders/{id}/approve', 'Payment\\PaymentOrderApprovalController@approve'); Route::post('payment-orders/{id}/reject', 'Payment\\PaymentOrderApprovalController@reject'); From 7a0be28f7626ba5e5a17cff37f7e6810e44fa4ac Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Thu, 27 Oct 2022 07:32:38 +0700 Subject: [PATCH 02/24] [Payment] Integrate cash out with cash advance --- app/Model/Finance/Payment/Payment.php | 53 +++++++++++++++----- app/Traits/Model/Finance/PaymentJoin.php | 4 ++ app/Traits/Model/Finance/PaymentRelation.php | 9 ++++ 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index a6d01fe98..393d85322 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -62,7 +62,7 @@ public static function create($data) $payment->paymentable_name = $data['paymentable_name'] ?? $payment->paymentable->name; $paymentDetails = self::mapPaymentDetails($data); - + // Reference Payment Order if (isset($data['referenceable_type']) && $data['referenceable_type'] == 'PaymentOrder') { $paymentOrder = PaymentOrder::find($data['referenceable_id']); @@ -155,6 +155,8 @@ public static function create($data) ); $form->save(); + self::mapCashAdvances($data, $payment, $form); + // Reference Cash Advance if (isset($data['referenceable_type']) && $data['referenceable_type'] == 'CashAdvance') { $cashAdvance = CashAdvance::find($data['referenceable_id']); @@ -163,13 +165,13 @@ public static function create($data) } $cashAdvance->payments()->attach($payment->id); $cashAdvance->amount_remaining = $cashAdvance->amount_remaining - $payment->amount; - if($cashAdvance->amount_remaining == 0) { + if ($cashAdvance->amount_remaining == 0) { $cashAdvance->form->done = 1; $cashAdvance->form->save(); } $cashAdvance->save(); - $data['activity'] = ucfirst(strtolower($cashAdvance->payment_type)).' Out Withdrawal ('.$form->number.')'; + $data['activity'] = ucfirst(strtolower($cashAdvance->payment_type)) . ' Out Withdrawal (' . $form->number . ')'; CashAdvance::mapHistory($cashAdvance, $data); } @@ -196,6 +198,31 @@ private static function mapPaymentDetails($data) }, $data['details']); } + private static function mapCashAdvances($data, $payment, $form) + { + return array_map(function ($detail) use ($data, $payment, $form) { + if ($payment->amount == 0) { + return; + } + // Adjusted & copied from payment::create where referenceable_type = 'CashAdvance' + $cashAdvance = CashAdvance::find($detail['cash_advance_id']); + if ($cashAdvance->amount_remaining < $detail['amount']) { + throw new PointException('Amount is over pay'); + } + $payment->amount = $payment->amount - $detail['amount']; + $cashAdvance->payments()->attach($payment->id); + $cashAdvance->amount_remaining = $cashAdvance->amount_remaining - $detail['amount']; + if ($cashAdvance->amount_remaining == 0) { + $cashAdvance->form->done = 1; + $cashAdvance->form->save(); + } + $cashAdvance->save(); + + $data['activity'] = ucfirst(strtolower($cashAdvance->payment_type)) . ' Out Withdrawal (' . $form->number . ')'; + CashAdvance::mapHistory($cashAdvance, $data); + }, $data['cashAdvances']); + } + private static function calculateAmount($paymentDetails) { return array_reduce($paymentDetails, function ($carry, $detail) { @@ -213,12 +240,12 @@ private static function mapPaymentCollectionJournals($payment, $data) $paymentDetail->fill($detail); $paymentDetail->referenceable_type = $data['referenceable_type'] ?? null; $paymentDetail->referenceable_id = $data['referenceable_id'] ?? null; - + $journal = new Journal; $journal->form_id_reference = optional(optional($paymentDetail->referenceable)->form)->id; $journal->notes = $paymentDetail->notes; $journal->chart_of_account_id = $paymentDetail->chart_of_account_id; - + if ($detail['payment_collection_type'] === 'SalesDownPayment') { $journal->debit = $paymentDetail->amount; $journals['debit'][] = $journal; @@ -275,19 +302,19 @@ private static function generateFormNumber($payment, $number, $increment) */ private static function getLastPaymentIncrement($payment, $incrementGroup) { - $lastPayment = self::from(self::getTableName().' as '.self::$alias) + $lastPayment = self::from(self::getTableName() . ' as ' . self::$alias) ->joinForm() ->where('form.increment_group', $incrementGroup) ->whereNotNull('form.number') - ->where(self::$alias.'.payment_type', $payment->payment_type) - ->where(self::$alias.'.disbursed', $payment->disbursed) + ->where(self::$alias . '.payment_type', $payment->payment_type) + ->where(self::$alias . '.disbursed', $payment->disbursed) ->with('form') ->orderBy('form.increment', 'desc') - ->select(self::$alias.'.*') + ->select(self::$alias . '.*') ->first(); $increment = 1; - if (! empty($lastPayment)) { + if (!empty($lastPayment)) { $increment += $lastPayment->form->increment; } @@ -313,7 +340,7 @@ private static function updateJournal($payment) $journal->journalable_type = $payment->paymentable_type; $journal->journalable_id = $payment->paymentable_id; $journal->chart_of_account_id = $payment->payment_account_id; - if (! $payment->disbursed) { + if (!$payment->disbursed) { $journal->debit = $payment->amount; } else { $journal->credit = $payment->amount; @@ -328,7 +355,7 @@ private static function updateJournal($payment) $journal->journalable_id = $payment->paymentable_id; $journal->notes = $paymentDetail->notes; $journal->chart_of_account_id = $paymentDetail->chart_of_account_id; - if (! $payment->disbursed) { + if (!$payment->disbursed) { $journal->credit = $paymentDetail->amount; } else { $journal->debit = $paymentDetail->amount; @@ -359,6 +386,6 @@ private static function updateJournalPaymentCollection($payment, $journalsPaymen $journal->journalable_type = $payment->paymentable_type; $journal->journalable_id = $payment->paymentable_id; $journal->save(); - } + } } } diff --git a/app/Traits/Model/Finance/PaymentJoin.php b/app/Traits/Model/Finance/PaymentJoin.php index b180e0841..56e149471 100644 --- a/app/Traits/Model/Finance/PaymentJoin.php +++ b/app/Traits/Model/Finance/PaymentJoin.php @@ -59,6 +59,10 @@ public static function joins($query, $joins) }); } + if (in_array('cashAdvances', $joins)) { + $query = $query->with(['cashAdvances']); + } + return $query; } } diff --git a/app/Traits/Model/Finance/PaymentRelation.php b/app/Traits/Model/Finance/PaymentRelation.php index 34c727f9c..874044414 100644 --- a/app/Traits/Model/Finance/PaymentRelation.php +++ b/app/Traits/Model/Finance/PaymentRelation.php @@ -3,6 +3,7 @@ namespace App\Traits\Model\Finance; use App\Model\Accounting\ChartOfAccount; +use App\Model\Finance\CashAdvance\CashAdvance; use App\Model\Finance\Payment\PaymentDetail; use App\Model\Form; use App\Model\Purchase\PurchaseOrder\PurchaseOrder; @@ -45,4 +46,12 @@ public function purchaseOrders() ->orWhere(Form::getTableName('cancellation_status'), '!=', '1'); }); } + + /** + * Get all related cash advances + */ + public function cashAdvances() + { + return $this->belongsToMany(CashAdvance::class, 'cash_advance_payment', 'payment_id', 'cash_advance_id'); + } } From 4939b8a41a70edaa83653646e1d8d491ca8a220f Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Thu, 27 Oct 2022 08:00:21 +0700 Subject: [PATCH 03/24] [Payment] Preparing CashOutTest --- app/Model/Finance/Payment/Payment.php | 9 +- .../Feature/Http/Finance/Cash/CashOutTest.php | 182 ++++++++++++++++++ 2 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 tests/Feature/Http/Finance/Cash/CashOutTest.php diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index 393d85322..42772db06 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -48,10 +48,7 @@ public function isAllowedToUpdate() public function isAllowedToDelete() { - // Check if not referenced by another form - if (optional($this->payment)->count()) { - throw new IsReferencedException('Cannot delete form because it is already paid', $this->payment); - } + // TODO isAllowed to delete? } public static function create($data) @@ -155,7 +152,9 @@ public static function create($data) ); $form->save(); - self::mapCashAdvances($data, $payment, $form); + if (isset($data['cashAdvances'])) { + self::mapCashAdvances($data, $payment, $form); + } // Reference Cash Advance if (isset($data['referenceable_type']) && $data['referenceable_type'] == 'CashAdvance') { diff --git a/tests/Feature/Http/Finance/Cash/CashOutTest.php b/tests/Feature/Http/Finance/Cash/CashOutTest.php new file mode 100644 index 000000000..1720cf7c3 --- /dev/null +++ b/tests/Feature/Http/Finance/Cash/CashOutTest.php @@ -0,0 +1,182 @@ +signIn(); + $this->setProject(); + $this->importChartOfAccount(); + } + + private function importChartOfAccount() + { + Excel::import(new ChartOfAccountImport(), storage_path('template/chart_of_accounts_manufacture.xlsx')); + + Artisan::call('db:seed', [ + '--database' => 'tenant', + '--class' => 'SettingJournalSeeder', + '--force' => true, + ]); + } + + // Create paymentable + public function createPaymentable() + { + $paymentableOptions = [ + 'Supplier' => 'Supplier', + 'Customer' => 'Customer', + 'Employee' => 'Employee' + ]; + + $randomPaymentable = array_rand($paymentableOptions); + switch ($randomPaymentable) { + case 'Supplier': + $paymentable = factory(Supplier::class)->create(); + $paymentable->morphName = Supplier::$morphName; + break; + + case 'Customer': + $paymentable = factory(Customer::class)->create(); + $paymentable->morphName = Customer::$morphName; + break; + + case 'Employee': + $paymentable = factory(Employee::class)->create(); + $paymentable->morphName = Employee::$morphName; + break; + } + + return $paymentable; + } + + // Get chartOfAccount + public function getChartOfAccount() + { + $chartOfAccount = ChartOfAccount::join( + ChartOfAccountType::getTableName() . ' as ' . ChartOfAccountType::$alias, + ChartOfAccount::getTableName() . '.type_id', + '=', + ChartOfAccountType::$alias . '.id' + ) + // ->where(ChartOfAccountType::$alias . '.name', 'CASH') + ->get() + ->random(); + + return $chartOfAccount; + } + + // Create data payment order + public function createDataPaymentOrder() + { + $paymentable = $this->createPaymentable(); + + $data = [ + 'payment_type' => 'CASH', + 'paymentable_id' => $paymentable->id, + 'paymentable_type' => $paymentable->morphName, + 'details' => [ + [ + 'chart_of_account_id' => $this->getChartOfAccount()->id, + 'amount' => rand(10000, 1000000) + ] + ] + ]; + + return $data; + } + + // Create data cash out + public function createDataCashOut($reference) + { + $chartOfAccount = $this->getChartOfAccount(); + + $data = [ + 'increment_group' => date('Ym'), + 'date' => date('Y-m-d H:i:s'), + 'payment_type' => "CASH", + 'payment_account_id' => $chartOfAccount->id, + 'paymentable_id' => $reference->paymentable_id, + 'paymentable_name' => $reference->paymentable_name, + 'paymentable_type' => $reference->paymentable_type, + 'disbursed' => false, + 'notes' => null, + 'amount' => $reference->amount, + 'details' => array( + [ + 'chart_of_account_id' => $chartOfAccount->id, + 'amount' => $reference->amount, + 'allocation_id' => null, + 'allocation_name' => null, + 'notes' => "Kas" + ] + ) + ]; + + return $data; + } + + // Create cash out + public function createCashOut($reference) + { + switch ($reference) { + case 'PaymentOrder': + $dataPaymentOrder = $this->createDataPaymentOrder(); + $payment = PaymentOrder::create($dataPaymentOrder); + break; + + default: + # code... + break; + } + + $data = $this->createDataCashOut($payment); + + return $data; + } + + // Test cash out from payment order + /** @test */ + public function success_cash_out_from_payment_order() + { + $data = $this->createCashOut('PaymentOrder'); + + $response = $this->json('POST', self::$path . '/payments', $data, $this->headers); + + $response + ->assertStatus(201); + $createdCashOut = json_decode($response->getContent())->data; + } + + // Test get all cashs + /** @test */ + public function get_all_cashs() + { + $response = $this->json('GET', self::$path . '/payments?join=form,payment_account,details,account,allocation&sort_by=-form.date&fields=payment.*&filter_form=notArchived%3Bnull&filter_like=%7B%7D&filter_equal=%7B%22payment.payment_type%22:%22cash%22%7D&filter_date_min=%7B%22form.date%22:%22' . date('Y-m-01') . '%22%7D&filter_date_max=%7B%22form.date%22:%22' . date('Y-m-d') . '%22%7D&limit=10&includes=form%3Bdetails.chartOfAccount%3Bdetails.allocation%3Bpaymentable&page=1', array(), $this->headers); + $response->assertStatus(200); + } +} From 88b72701b27cb3ec840635548c943409db727ef9 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Sat, 29 Oct 2022 22:57:57 +0700 Subject: [PATCH 04/24] [Payment] Get references for payment --- .../Api/Finance/Payment/PaymentController.php | 61 +++++++++++++++++-- routes/api/finance.php | 1 + 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index 94d47160b..aa5a5d6e0 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -8,8 +8,13 @@ use App\Http\Resources\ApiCollection; use App\Http\Resources\ApiResource; use App\Model\Finance\Payment\Payment; +use App\Model\Finance\PaymentOrder\PaymentOrder; +use App\Model\Purchase\PurchaseDownPayment\PurchaseDownPayment; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\DB; use Throwable; @@ -23,7 +28,7 @@ class PaymentController extends Controller */ public function index(Request $request) { - $payment = Payment::from(Payment::getTableName().' as '.Payment::$alias)->eloquentFilter($request); + $payment = Payment::from(Payment::getTableName() . ' as ' . Payment::$alias)->eloquentFilter($request); $payment = Payment::joins($payment, $request->get('join')); @@ -83,7 +88,7 @@ public function update(UpdatePaymentRequest $request, $id) $payment->form->archive(); foreach ($payment->details as $paymentDetail) { - if (! $paymentDetail->isDownPayment()) { + if (!$paymentDetail->isDownPayment()) { $reference = $paymentDetail->referenceable; $reference->remaining += $paymentDetail->amount; $reference->save(); @@ -119,9 +124,9 @@ public function destroy(Request $request, $id) $response = $payment->requestCancel($request); - if (! $response) { + if (!$response) { foreach ($payment->details as $paymentDetail) { - if (! $paymentDetail->isDownPayment()) { + if (!$paymentDetail->isDownPayment()) { $reference = $paymentDetail->referenceable; $reference->remaining += $payment->amount; $reference->save(); @@ -133,4 +138,52 @@ public function destroy(Request $request, $id) return response()->json([], 204); } + + public function getReferences(Request $request) + { + // TO DO + // Split request filter for each reference type + + $references = new Collection(); + + $request['join'] = 'form;details;account'; + $request['group_by'] = 'payment_order.id'; + $request['fields'] = 'payment_order.*'; + $request['filter_null'] = 'payment_order.payment_id'; + $request['filter_equal'] = [ + 'payment_order.payment_type' => 'cash' + ]; + $request['includes'] = 'form;paymentable;details.account;details.allocation'; + $paymentOrders = PaymentOrder::from(PaymentOrder::getTableName() . ' as ' . PaymentOrder::$alias)->eloquentFilter($request); + $paymentOrders = PaymentOrder::joins($paymentOrders, $request->get('join'))->get(); + $references = $references->concat($paymentOrders); + + $request['join'] = 'form'; + $request['group_by'] = 'purchase_down_payment.id'; + $request['fields'] = 'purchase_down_payment.*'; + unset($request['filter_equal']); + unset($request['filter_null']); + $request['includes'] = 'form;supplier'; + $request['filter_date_min'] = [ + 'form.date' => date('Y-m-01') + ]; + $request['filter_date_max'] = [ + 'form.date' => date('Y-m-t') + ]; + $downPayments = PurchaseDownPayment::from(PurchaseDownPayment::getTableName() . ' as ' . PurchaseDownPayment::$alias)->eloquentFilter($request); + $downPayments = PurchaseDownPayment::joins($downPayments, $request->get('join'))->get(); + $references = $references->concat($downPayments); + + $paginatedReferences = $this->paginate($references, $request->get('limit')); + + return new ApiCollection($paginatedReferences); + } + + public function paginate($items, $perPage = 5, $page = null, $options = []) + { + // TO DO, make this function reusable + $page = $page ?: (Paginator::resolveCurrentPage() ?: 1); + $items = $items instanceof Collection ? $items : Collection::make($items); + return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options); + } } diff --git a/routes/api/finance.php b/routes/api/finance.php index 456c3ac8b..1a35613e2 100644 --- a/routes/api/finance.php +++ b/routes/api/finance.php @@ -1,6 +1,7 @@ namespace('Finance')->group(function () { + Route::get('payments/get-references', 'Payment\\PaymentController@getReferences'); Route::post('payments/{id}/cancellation-approve', 'Payment\\PaymentCancellationApprovalController@approve'); Route::post('payments/{id}/cancellation-reject', 'Payment\\PaymentCancellationApprovalController@reject'); Route::apiResource('payments', 'Payment\\PaymentController'); From 29c8b0bf75efa017e0a9179662fc5d1efc44b543 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Mon, 31 Oct 2022 06:25:13 +0700 Subject: [PATCH 05/24] [Payment] Get paymentables for payment --- .../Api/Finance/Payment/PaymentController.php | 8 ++++++++ routes/api/finance.php | 11 +++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index aa5a5d6e0..5aab3a769 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -179,6 +179,14 @@ public function getReferences(Request $request) return new ApiCollection($paginatedReferences); } + public function getPaymentables(Request $request) + { + $paymentables = Payment::groupBy('paymentable_type')->groupBy('paymentable_name')->select(['paymentable_type', 'paymentable_name']); + $paymentables = pagination($paymentables, $request->get('limit')); + + return new ApiCollection($paymentables); + } + public function paginate($items, $perPage = 5, $page = null, $options = []) { // TO DO, make this function reusable diff --git a/routes/api/finance.php b/routes/api/finance.php index 1a35613e2..571480d8a 100644 --- a/routes/api/finance.php +++ b/routes/api/finance.php @@ -1,10 +1,13 @@ namespace('Finance')->group(function () { - Route::get('payments/get-references', 'Payment\\PaymentController@getReferences'); - Route::post('payments/{id}/cancellation-approve', 'Payment\\PaymentCancellationApprovalController@approve'); - Route::post('payments/{id}/cancellation-reject', 'Payment\\PaymentCancellationApprovalController@reject'); - Route::apiResource('payments', 'Payment\\PaymentController'); + Route::prefix('payments')->namespace('Payment')->group(function (){ + Route::get('get-paymentables', 'PaymentController@getPaymentables'); + Route::get('get-references', 'PaymentController@getReferences'); + Route::post('{id}/cancellation-approve', 'PaymentCancellationApprovalController@approve'); + Route::post('{id}/cancellation-reject', 'PaymentCancellationApprovalController@reject'); + Route::apiResource('/', 'PaymentController'); + }); Route::post('payment-orders/{id}/approve', 'Payment\\PaymentOrderApprovalController@approve'); Route::post('payment-orders/{id}/reject', 'Payment\\PaymentOrderApprovalController@reject'); Route::post('payment-orders/{id}/cancellation-approve', 'Payment\\PaymentOrderCancellationApprovalController@approve'); From b5d8e5ce4fa28455c42a6d9f7c68ca41113a690c Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Mon, 31 Oct 2022 10:45:20 +0700 Subject: [PATCH 06/24] [Payment] Split request filter for getReferences & make paginate_collection function reusable --- .../Api/Finance/Payment/PaymentController.php | 71 ++++++++++--------- app/helpers.php | 26 +++++++ 2 files changed, 65 insertions(+), 32 deletions(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index 5aab3a769..221c5f5bd 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -141,40 +141,55 @@ public function destroy(Request $request, $id) public function getReferences(Request $request) { - // TO DO // Split request filter for each reference type + $paymentOrderRequest = new Request(); + $downPaymentRequest = new Request(); + $paymentOrderString = 'paymentorder'; + $downPaymentString = 'downpayment'; + foreach ($request->all() as $key => $value) { + if (in_array($key, ['limit', 'page'])) { + $paymentOrderRequest->merge([ + $key => $value + ]); + $downPaymentRequest->merge([ + $key => $value + ]); + continue; + } + $explodedKey = explode('_', $key); + + switch ($explodedKey[0]) { + case $paymentOrderString: + $keyAttribute = substr($key, strlen($paymentOrderString) + 1); //+1 for _ + $paymentOrderRequest->merge([ + $keyAttribute => $value + ]); + break; + + case $downPaymentString: + $keyAttribute = substr($key, strlen($downPaymentString) + 1); //+1 for _ + $downPaymentRequest->merge([ + $keyAttribute => $value + ]); + break; + + default: + # code... + break; + } + } $references = new Collection(); - $request['join'] = 'form;details;account'; - $request['group_by'] = 'payment_order.id'; - $request['fields'] = 'payment_order.*'; - $request['filter_null'] = 'payment_order.payment_id'; - $request['filter_equal'] = [ - 'payment_order.payment_type' => 'cash' - ]; - $request['includes'] = 'form;paymentable;details.account;details.allocation'; - $paymentOrders = PaymentOrder::from(PaymentOrder::getTableName() . ' as ' . PaymentOrder::$alias)->eloquentFilter($request); + $paymentOrders = PaymentOrder::from(PaymentOrder::getTableName() . ' as ' . PaymentOrder::$alias)->eloquentFilter($paymentOrderRequest); $paymentOrders = PaymentOrder::joins($paymentOrders, $request->get('join'))->get(); $references = $references->concat($paymentOrders); - - $request['join'] = 'form'; - $request['group_by'] = 'purchase_down_payment.id'; - $request['fields'] = 'purchase_down_payment.*'; - unset($request['filter_equal']); - unset($request['filter_null']); - $request['includes'] = 'form;supplier'; - $request['filter_date_min'] = [ - 'form.date' => date('Y-m-01') - ]; - $request['filter_date_max'] = [ - 'form.date' => date('Y-m-t') - ]; + $downPayments = PurchaseDownPayment::from(PurchaseDownPayment::getTableName() . ' as ' . PurchaseDownPayment::$alias)->eloquentFilter($request); $downPayments = PurchaseDownPayment::joins($downPayments, $request->get('join'))->get(); $references = $references->concat($downPayments); - $paginatedReferences = $this->paginate($references, $request->get('limit')); + $paginatedReferences = paginate_collection($references, $request->get('limit'), $request->get('page')); return new ApiCollection($paginatedReferences); } @@ -186,12 +201,4 @@ public function getPaymentables(Request $request) return new ApiCollection($paymentables); } - - public function paginate($items, $perPage = 5, $page = null, $options = []) - { - // TO DO, make this function reusable - $page = $page ?: (Paginator::resolveCurrentPage() ?: 1); - $items = $items instanceof Collection ? $items : Collection::make($items); - return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options); - } } diff --git a/app/helpers.php b/app/helpers.php index 3c6913b2a..2b2bd7d54 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -2,6 +2,9 @@ use App\Model\SettingJournal; use Carbon\Carbon; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Pagination\Paginator; +use Illuminate\Support\Collection; use Illuminate\Support\Str; if (! function_exists('log_object')) { @@ -363,3 +366,26 @@ function response_error($error) return response (['code' => $code, 'message' => $message], $httpCode); } } + +if (! function_exists('paginate_collection')) { + /** + * Paginate collection (not from query). + * + * @param $collection + * @param null $limit + * @return string + */ + function paginate_collection($collection, $limit = null, $page = null, $options = []) + { + if (! $limit) { + return $collection->paginate(100); + } + + // limit call maximum 1000 item per page + $limit = $limit > 1000 ? 1000 : $limit; + + $page = $page ?: (Paginator::resolveCurrentPage() ?: 1); + $collection = $collection instanceof Collection ? $collection : Collection::make($collection); + return new LengthAwarePaginator($collection->forPage($page, $limit), $collection->count(), $limit, $page, $options); + } +} \ No newline at end of file From cd95e31695cf4b8e303963dd81a79c8dc9a13990 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Mon, 31 Oct 2022 15:25:38 +0700 Subject: [PATCH 07/24] [Payment] Fix request filter on getReferences --- .../Controllers/Api/Finance/Payment/PaymentController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index 221c5f5bd..457cf0205 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -182,11 +182,11 @@ public function getReferences(Request $request) $references = new Collection(); $paymentOrders = PaymentOrder::from(PaymentOrder::getTableName() . ' as ' . PaymentOrder::$alias)->eloquentFilter($paymentOrderRequest); - $paymentOrders = PaymentOrder::joins($paymentOrders, $request->get('join'))->get(); + $paymentOrders = PaymentOrder::joins($paymentOrders, $paymentOrderRequest->get('join'))->get(); $references = $references->concat($paymentOrders); - $downPayments = PurchaseDownPayment::from(PurchaseDownPayment::getTableName() . ' as ' . PurchaseDownPayment::$alias)->eloquentFilter($request); - $downPayments = PurchaseDownPayment::joins($downPayments, $request->get('join'))->get(); + $downPayments = PurchaseDownPayment::from(PurchaseDownPayment::getTableName() . ' as ' . PurchaseDownPayment::$alias)->eloquentFilter($downPaymentRequest); + $downPayments = PurchaseDownPayment::joins($downPayments, $downPaymentRequest->get('join'))->get(); $references = $references->concat($downPayments); $paginatedReferences = paginate_collection($references, $request->get('limit'), $request->get('page')); From 1068118b260f919a696471f85ed56ea8f8f5e342 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Tue, 1 Nov 2022 09:48:32 +0700 Subject: [PATCH 08/24] [Payment] Transform return data on getReferences & make getPaymentables searchable --- .../Api/Finance/Payment/PaymentController.php | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index 457cf0205..35f954e30 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -16,6 +16,7 @@ use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\DB; +use stdClass; use Throwable; class PaymentController extends Controller @@ -183,12 +184,37 @@ public function getReferences(Request $request) $paymentOrders = PaymentOrder::from(PaymentOrder::getTableName() . ' as ' . PaymentOrder::$alias)->eloquentFilter($paymentOrderRequest); $paymentOrders = PaymentOrder::joins($paymentOrders, $paymentOrderRequest->get('join'))->get(); + $paymentOrders->transform(function ($paymentOrder) { + $transformData = new PaymentOrder(); + $transformData->referenceable_id = $paymentOrder->id; + $transformData->referenceable_type = $transformData::$morphName; + $transformData->date = $paymentOrder->form->date; + $transformData->number = $paymentOrder->form->number; + $transformData->person = $paymentOrder->paymentable_name; + $transformData->amount = $paymentOrder->amount; + $transformData->notes = $paymentOrder->form->notes; + $transformData->created_by = $paymentOrder->form->createdBy->full_name; + return $transformData; + }); $references = $references->concat($paymentOrders); $downPayments = PurchaseDownPayment::from(PurchaseDownPayment::getTableName() . ' as ' . PurchaseDownPayment::$alias)->eloquentFilter($downPaymentRequest); $downPayments = PurchaseDownPayment::joins($downPayments, $downPaymentRequest->get('join'))->get(); + $downPayments->transform(function ($downPayment) { + $transformData = new PurchaseDownPayment(); + $transformData->referenceable_id = $downPayment->id; + $transformData->referenceable_type = $transformData::$morphName; + $transformData->date = $downPayment->form->date; + $transformData->number = $downPayment->form->number; + $transformData->person = $downPayment->supplier_name; + $transformData->amount = $downPayment->amount; + $transformData->notes = $downPayment->form->notes; + $transformData->created_by = $downPayment->form->createdBy->full_name; + return $transformData; + }); $references = $references->concat($downPayments); + $references = $references->sortBy('date'); $paginatedReferences = paginate_collection($references, $request->get('limit'), $request->get('page')); return new ApiCollection($paginatedReferences); @@ -196,7 +222,9 @@ public function getReferences(Request $request) public function getPaymentables(Request $request) { - $paymentables = Payment::groupBy('paymentable_type')->groupBy('paymentable_name')->select(['paymentable_type', 'paymentable_name']); + $paymentables = Payment::from(Payment::getTableName() . ' as ' . Payment::$alias) + ->select(['paymentable_type', 'paymentable_name']) + ->eloquentFilter($request); $paymentables = pagination($paymentables, $request->get('limit')); return new ApiCollection($paymentables); From 1a6a960a079adab956952ba6c10227b5ae1b8cc8 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Tue, 1 Nov 2022 10:27:15 +0700 Subject: [PATCH 09/24] [Payment] Fix payment routing --- routes/api/finance.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api/finance.php b/routes/api/finance.php index 571480d8a..dc18b4d2e 100644 --- a/routes/api/finance.php +++ b/routes/api/finance.php @@ -6,8 +6,8 @@ Route::get('get-references', 'PaymentController@getReferences'); Route::post('{id}/cancellation-approve', 'PaymentCancellationApprovalController@approve'); Route::post('{id}/cancellation-reject', 'PaymentCancellationApprovalController@reject'); - Route::apiResource('/', 'PaymentController'); }); + Route::apiResource('payments', 'Payment\\PaymentController'); Route::post('payment-orders/{id}/approve', 'Payment\\PaymentOrderApprovalController@approve'); Route::post('payment-orders/{id}/reject', 'Payment\\PaymentOrderApprovalController@reject'); Route::post('payment-orders/{id}/cancellation-approve', 'Payment\\PaymentOrderCancellationApprovalController@approve'); From 404baed0c636e4bbb7286e0323737d0d62480875 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Tue, 1 Nov 2022 13:37:17 +0700 Subject: [PATCH 10/24] [Payment] Revert transform data on getReferences --- .../Api/Finance/Payment/PaymentController.php | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index 35f954e30..f3a3c3917 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -185,32 +185,36 @@ public function getReferences(Request $request) $paymentOrders = PaymentOrder::from(PaymentOrder::getTableName() . ' as ' . PaymentOrder::$alias)->eloquentFilter($paymentOrderRequest); $paymentOrders = PaymentOrder::joins($paymentOrders, $paymentOrderRequest->get('join'))->get(); $paymentOrders->transform(function ($paymentOrder) { - $transformData = new PaymentOrder(); - $transformData->referenceable_id = $paymentOrder->id; - $transformData->referenceable_type = $transformData::$morphName; - $transformData->date = $paymentOrder->form->date; - $transformData->number = $paymentOrder->form->number; - $transformData->person = $paymentOrder->paymentable_name; - $transformData->amount = $paymentOrder->amount; - $transformData->notes = $paymentOrder->form->notes; - $transformData->created_by = $paymentOrder->form->createdBy->full_name; - return $transformData; + $paymentOrder->referenceable_type = $paymentOrder::$morphName; + return $paymentOrder; + // $transformData = new PaymentOrder(); + // $transformData->referenceable_id = $paymentOrder->id; + // $transformData->referenceable_type = $transformData::$morphName; + // $transformData->date = $paymentOrder->form->date; + // $transformData->number = $paymentOrder->form->number; + // $transformData->person = $paymentOrder->paymentable_name; + // $transformData->amount = $paymentOrder->amount; + // $transformData->notes = $paymentOrder->form->notes; + // $transformData->created_by = $paymentOrder->form->createdBy->full_name; + // return $transformData; }); $references = $references->concat($paymentOrders); $downPayments = PurchaseDownPayment::from(PurchaseDownPayment::getTableName() . ' as ' . PurchaseDownPayment::$alias)->eloquentFilter($downPaymentRequest); $downPayments = PurchaseDownPayment::joins($downPayments, $downPaymentRequest->get('join'))->get(); $downPayments->transform(function ($downPayment) { - $transformData = new PurchaseDownPayment(); - $transformData->referenceable_id = $downPayment->id; - $transformData->referenceable_type = $transformData::$morphName; - $transformData->date = $downPayment->form->date; - $transformData->number = $downPayment->form->number; - $transformData->person = $downPayment->supplier_name; - $transformData->amount = $downPayment->amount; - $transformData->notes = $downPayment->form->notes; - $transformData->created_by = $downPayment->form->createdBy->full_name; - return $transformData; + $downPayment->referenceable_type = $downPayment::$morphName; + return $downPayment; + // $transformData = new PurchaseDownPayment(); + // $transformData->referenceable_id = $downPayment->id; + // $transformData->referenceable_type = $transformData::$morphName; + // $transformData->date = $downPayment->form->date; + // $transformData->number = $downPayment->form->number; + // $transformData->person = $downPayment->supplier_name; + // $transformData->amount = $downPayment->amount; + // $transformData->notes = $downPayment->form->notes; + // $transformData->created_by = $downPayment->form->createdBy->full_name; + // return $transformData; }); $references = $references->concat($downPayments); From c6e5acb6e421b234fee25c8ba4791d781a4e019a Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Tue, 1 Nov 2022 20:47:04 +0700 Subject: [PATCH 11/24] [Payment] Revert transform data on getReferences --- .../Api/Finance/Payment/PaymentController.php | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index f3a3c3917..dc1006b24 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -184,38 +184,10 @@ public function getReferences(Request $request) $paymentOrders = PaymentOrder::from(PaymentOrder::getTableName() . ' as ' . PaymentOrder::$alias)->eloquentFilter($paymentOrderRequest); $paymentOrders = PaymentOrder::joins($paymentOrders, $paymentOrderRequest->get('join'))->get(); - $paymentOrders->transform(function ($paymentOrder) { - $paymentOrder->referenceable_type = $paymentOrder::$morphName; - return $paymentOrder; - // $transformData = new PaymentOrder(); - // $transformData->referenceable_id = $paymentOrder->id; - // $transformData->referenceable_type = $transformData::$morphName; - // $transformData->date = $paymentOrder->form->date; - // $transformData->number = $paymentOrder->form->number; - // $transformData->person = $paymentOrder->paymentable_name; - // $transformData->amount = $paymentOrder->amount; - // $transformData->notes = $paymentOrder->form->notes; - // $transformData->created_by = $paymentOrder->form->createdBy->full_name; - // return $transformData; - }); $references = $references->concat($paymentOrders); $downPayments = PurchaseDownPayment::from(PurchaseDownPayment::getTableName() . ' as ' . PurchaseDownPayment::$alias)->eloquentFilter($downPaymentRequest); $downPayments = PurchaseDownPayment::joins($downPayments, $downPaymentRequest->get('join'))->get(); - $downPayments->transform(function ($downPayment) { - $downPayment->referenceable_type = $downPayment::$morphName; - return $downPayment; - // $transformData = new PurchaseDownPayment(); - // $transformData->referenceable_id = $downPayment->id; - // $transformData->referenceable_type = $transformData::$morphName; - // $transformData->date = $downPayment->form->date; - // $transformData->number = $downPayment->form->number; - // $transformData->person = $downPayment->supplier_name; - // $transformData->amount = $downPayment->amount; - // $transformData->notes = $downPayment->form->notes; - // $transformData->created_by = $downPayment->form->createdBy->full_name; - // return $transformData; - }); $references = $references->concat($downPayments); $references = $references->sortBy('date'); From d40c5d5acda93bffdea7a4ff1691827fcd056cf8 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Wed, 2 Nov 2022 14:34:14 +0700 Subject: [PATCH 12/24] [Payment] Add paymentable_id on getPaymentables --- app/Http/Controllers/Api/Finance/Payment/PaymentController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index dc1006b24..6aca55e6c 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -199,7 +199,7 @@ public function getReferences(Request $request) public function getPaymentables(Request $request) { $paymentables = Payment::from(Payment::getTableName() . ' as ' . Payment::$alias) - ->select(['paymentable_type', 'paymentable_name']) + ->select(['paymentable_id', 'paymentable_type', 'paymentable_name']) ->eloquentFilter($request); $paymentables = pagination($paymentables, $request->get('limit')); From 2520be565da7a7412a9d0ebca579b2878302fb32 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Thu, 3 Nov 2022 09:25:32 +0700 Subject: [PATCH 13/24] [Payment] WIP request cancellation --- .../PaymentCancellationApprovalController.php | 108 ++++++++++++++--- .../Api/Finance/Payment/PaymentController.php | 69 ++++++++--- .../PaymentCancellationApprovalRequest.php | 48 ++++++++ app/Model/Finance/Payment/Payment.php | 33 +++++- .../payment/cancellation-approval.blade.php | 109 ++++++++++++++++++ 5 files changed, 338 insertions(+), 29 deletions(-) create mode 100644 app/Mail/Finance/Payment/PaymentCancellationApprovalRequest.php create mode 100644 resources/views/emails/finance/payment/cancellation-approval.blade.php diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php index 325cea49c..e4c9a1f80 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php @@ -3,11 +3,14 @@ namespace App\Http\Controllers\Api\Finance\Payment; use App\Exceptions\ApprovalNotFoundException; +use App\Exceptions\PointException; use App\Exceptions\UnauthorizedException; use App\Http\Controllers\Controller; use App\Http\Resources\ApiResource; +use App\Model\Accounting\Journal; use App\Model\Finance\Payment\Payment; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; class PaymentCancellationApprovalController extends Controller { @@ -20,13 +23,49 @@ class PaymentCancellationApprovalController extends Controller */ public function approve(Request $request, $id) { - $Payment = Payment::findOrFail($id); - $Payment->form->cancellation_approval_by = auth()->user()->id; - $Payment->form->cancellation_approval_at = now(); - $Payment->form->cancellation_status = 1; - $Payment->form->save(); + $payment = Payment::findOrFail($id); - return new ApiResource($Payment); + // ### Approve fail if + // Jika Role Bukan Super Admin dan tidak memiliki akses approval maka mengirimkan pesan eror + if ($request->has('token')) { + // approve from email + $approvalBy = $request->get('approver_id'); + } else { + $payment->isHaveAccessToDelete(); + $approvalBy = auth()->user()->id; + } + $this->isCancellationRequestStillValid($payment); + + DB::connection('tenant')->transaction(function () use ($payment, $approvalBy) { + // ### If approve success then + // Status form cash out akan menjadi cancelled + $payment->form->cancellation_approval_by = $approvalBy; + $payment->form->cancellation_approval_at = now(); + $payment->form->cancellation_status = 1; + + // Status form payment order & cash advance jadi pending + foreach ($payment->details as $paymentDetail) { + $paymentDetail->referenceable->form->done = 0; + $paymentDetail->referenceable->form->save(); + } + + $cashAdvanceAmount = $payment->details()->sum('amount') - $payment->amount; + // Jumlah saldo cash account / cash advance/ biaya yang dipilih akan bertambah sebesar data yang dihapus (?) + foreach ($payment->cashAdvances as $cashAdvance) { + $cashAdvance->form->done = 0; + $cashAdvance->form->save(); + } + // $payment->disbursed = !$payment->disbursed; + // Data jurnal cash out dari cash report / journal / general ledger/ subledger akan berkurang sebesar data yang dihapus (?) + // $payment::updateJournal($payment); + + // Delete data allocation pada allocation report (?) + + $payment->form->save(); + }); + + $payment->form = $payment->form; + return new ApiResource($payment); } /** @@ -38,13 +77,54 @@ public function approve(Request $request, $id) */ public function reject(Request $request, $id) { - $Payment = Payment::findOrFail($id); - $Payment->form->cancellation_approval_by = auth()->user()->id; - $Payment->form->cancellation_approval_at = now(); - $Payment->form->cancellation_approval_reason = $request->get('reason'); - $Payment->form->cancellation_status = -1; - $Payment->form->save(); - - return new ApiResource($Payment); + $request->validate([ + 'reason' => 'required' + ]); + + $payment = Payment::findOrFail($id); + + // ### Reject fail if + // Jika Role Bukan Super Admin / Pihak yang dipilih utk approval maka akan mengirimkan pesan eror + // Jika tidak memiliki akses approval pada payment order maka akan mengirimkan pesan eror + if ($request->has('token')) { + // reject from email + $approvalBy = $request->get('approver_id'); + } else { + $payment->isHaveAccessToDelete(); + $approvalBy = auth()->user()->id; + } + + $this->isCancellationRequestStillValid($payment); + + DB::connection('tenant')->transaction(function () use ($payment, $request, $approvalBy) { + // ### If reject success then + // Update status approval form menjadi rejected + $payment->form->approval_status = -1; + + // Update status form status menjadi pending + $payment->form->done = 0; + + $payment->form->cancellation_approval_by = $approvalBy; + $payment->form->cancellation_approval_at = now(); + $payment->form->cancellation_approval_reason = $request->get('reason'); + $payment->form->cancellation_status = -1; + + $payment->form->save(); + }); + + $payment->form = $payment->form; + return new ApiResource($payment); + } + + public function isCancellationRequestStillValid($payment) + { + // is cancellation request already approved / rejected? + $cancellationStatus = $payment->form->cancellation_status; + if ($cancellationStatus == 1) { + throw new PointException('Form cancellation already approved'); + } + if ($cancellationStatus == -1) { + throw new PointException('Form cancellation already rejected'); + } } } diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index 6aca55e6c..963743ee2 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -7,16 +7,19 @@ use App\Http\Requests\Finance\Payment\Payment\UpdatePaymentRequest; use App\Http\Resources\ApiCollection; use App\Http\Resources\ApiResource; +use App\Mail\Finance\Payment\PaymentCancellationApprovalRequest; +use App\Model\Auth\Role; use App\Model\Finance\Payment\Payment; use App\Model\Finance\PaymentOrder\PaymentOrder; +use App\Model\Master\User; use App\Model\Purchase\PurchaseDownPayment\PurchaseDownPayment; +use App\Model\Token; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\Request; use Illuminate\Http\Response; -use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\DB; -use stdClass; +use Illuminate\Support\Facades\Mail; use Throwable; class PaymentController extends Controller @@ -120,22 +123,62 @@ public function update(UpdatePaymentRequest $request, $id) */ public function destroy(Request $request, $id) { + $request->validate([ + 'reason' => 'required' + ]); + $payment = Payment::findOrFail($id); $payment->isAllowedToDelete(); - $response = $payment->requestCancel($request); + DB::connection('tenant')->transaction(function () use ($payment, $request) { + $payment->requestCancel($request); - if (!$response) { - foreach ($payment->details as $paymentDetail) { - if (!$paymentDetail->isDownPayment()) { - $reference = $paymentDetail->referenceable; - $reference->remaining += $payment->amount; - $reference->save(); - $reference->form->done = false; - $reference->form->save(); + // Status form cash out jadi pending + $payment->form->done = 0; + $payment->form->save(); + + // Kirim notifikasi by program & email + $superAdminRole = Role::where('name', 'super admin')->first(); + $emailUsers = User::whereHas('roles', function (Builder $query) use ($superAdminRole) { + $query->where('role_id', '=', $superAdminRole->id); + })->get(); + + foreach ($emailUsers as $recipient) { + // create token based on request_approval_to + $token = Token::where('user_id', $recipient->id)->first(); + + if (!$token) { + $token = new Token([ + 'user_id' => $recipient->id, + 'token' => md5($recipient->email . '' . now()), + ]); + $token->save(); } + + Mail::to([ + $recipient->email, + ])->queue(new PaymentCancellationApprovalRequest( + $payment, + $recipient, + $payment->form, + $token->token + )); } - } + + // if (!$response) { + // foreach ($payment->details as $paymentDetail) { + // if (!$paymentDetail->isDownPayment()) { + // $reference = $paymentDetail->referenceable; + // $reference->remaining += $payment->amount; + // $reference->save(); + // $reference->form->done = false; + // $reference->form->save(); + // } + // } + // } + + + }); return response()->json([], 204); } diff --git a/app/Mail/Finance/Payment/PaymentCancellationApprovalRequest.php b/app/Mail/Finance/Payment/PaymentCancellationApprovalRequest.php new file mode 100644 index 000000000..a34738303 --- /dev/null +++ b/app/Mail/Finance/Payment/PaymentCancellationApprovalRequest.php @@ -0,0 +1,48 @@ +payment = $payment; + $this->approver = $approver; + $this->form = $form; + $this->token = $token; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + return $this->subject('Cancellation Approval Email') + ->view('emails.finance.payment.cancellation-approval', [ + 'payment' => $this->payment, + 'approverId' => $this->approver->id, + 'fullName' => $this->approver->getFullNameAttribute(), + 'form' => $this->form, + 'token' => $this->token + ]); + } +} diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index 42772db06..4d64b0678 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -3,8 +3,8 @@ namespace App\Model\Finance\Payment; use App\Exceptions\BranchNullException; -use App\Exceptions\IsReferencedException; use App\Exceptions\PointException; +use App\Exceptions\UnauthorizedException; use App\Model\Accounting\Journal; use App\Model\Finance\PaymentOrder\PaymentOrder; use App\Model\Finance\CashAdvance\CashAdvance; @@ -15,7 +15,7 @@ use App\Model\TransactionModel; use App\Traits\Model\Finance\PaymentJoin; use App\Traits\Model\Finance\PaymentRelation; -use DB; +use Carbon\Carbon; class Payment extends TransactionModel { @@ -49,6 +49,35 @@ public function isAllowedToUpdate() public function isAllowedToDelete() { // TODO isAllowed to delete? + $form = $this->form; + + // Forbidden to delete, + // - Jika tidak memiliki permission + $this->isHaveAccessToDelete(); + + // - Jika form sudah di-request to delete + if ($form->request_cancellation_by != null) { + throw new PointException("Form already request to delete"); + } + + // - Jika pada periode yang akan didelete sudah dilakukan close book maka akan mengirimkan pesan eror + $now = Carbon::now(); + $formDate = Carbon::parse($form->date); + if ($now->month != $formDate->month) { + throw new PointException("Cannot delete form because the book period is closed"); + } + } + + // Check if auth user have access to delete payment + public function isHaveAccessToDelete() + { + $authUserId = auth()->user()->id; + // Only super admin & approver referenceable can delete + $isSuperAdmin = tenant($authUserId)->hasRole('super admin'); + $isApproverReferenceable = $this->details()->first()->referenceable->form->approval_by; + if ((!$isSuperAdmin) && ($authUserId != $isApproverReferenceable)) { + throw new UnauthorizedException(); + } } public static function create($data) diff --git a/resources/views/emails/finance/payment/cancellation-approval.blade.php b/resources/views/emails/finance/payment/cancellation-approval.blade.php new file mode 100644 index 000000000..d090f73e4 --- /dev/null +++ b/resources/views/emails/finance/payment/cancellation-approval.blade.php @@ -0,0 +1,109 @@ +@extends('emails.template') + +@section('content') +
Cancellation Approval Email
+
+
+ Hello Mrs/Mr/Ms {{ $fullName }}, +
+ You Have an approval for Payment Cancellation. we would like to details as follows: +
+
+ + + + + + + + + + + + + + + @foreach ($payment->cashAdvances as $cashAdvance) + + + + + @endforeach + + + + + + + + + + + + + + + + + +
Form Number: {{ $payment->form->number ?: '-' }}
Form Date: {{ date('d F Y', strtotime($payment->form->date)) ?: '-' }}
Form Reference: {{ $payment->details()->first()->referenceable->form->number ?: '-' }}
Cash Advance: {{ $payment->form->number ?: '-' }}
Amount Cash Advance: {{ $payment->amount - $payment->details()->sum('amount') }}
Cash Account: {{ $payment->paymentAccount->label }}
Person: {{ $payment->paymentable_name }}
Notes: {{ $payment->form->notes ?: '-' }}
+
+
+ + + + + + + + + + + + @foreach($payment->details as $i => $detail) + + + + + + + + @php ($i++) + @endforeach + +
NoAccountNotesAmountAllocation
+ {{ ++$i }} + + {{ $detail->chartOfAccount->label }} + + {{ $detail->notes }} + + {{ $detail->amount }} + + {{ $detail->allocation->name }} +
+
+ +
+
+@stop \ No newline at end of file From ead9611d812fe491746a457c17d88eaec14fac8a Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Thu, 3 Nov 2022 11:22:22 +0700 Subject: [PATCH 14/24] [Payment] Save allocation reports --- app/Model/Finance/Payment/Payment.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index 4d64b0678..7dc036794 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -6,6 +6,7 @@ use App\Exceptions\PointException; use App\Exceptions\UnauthorizedException; use App\Model\Accounting\Journal; +use App\Model\AllocationReport; use App\Model\Finance\PaymentOrder\PaymentOrder; use App\Model\Finance\CashAdvance\CashAdvance; use App\Model\Form; @@ -50,7 +51,7 @@ public function isAllowedToDelete() { // TODO isAllowed to delete? $form = $this->form; - + // Forbidden to delete, // - Jika tidak memiliki permission $this->isHaveAccessToDelete(); @@ -185,6 +186,9 @@ public static function create($data) self::mapCashAdvances($data, $payment, $form); } + // Save allocation reports + self::mapAllocationReports($data, $payment); + // Reference Cash Advance if (isset($data['referenceable_type']) && $data['referenceable_type'] == 'CashAdvance') { $cashAdvance = CashAdvance::find($data['referenceable_id']); @@ -251,6 +255,22 @@ private static function mapCashAdvances($data, $payment, $form) }, $data['cashAdvances']); } + private static function mapAllocationReports($data, $payment) + { + return array_map(function ($detail) use ($data, $payment) { + if ($detail['allocation_id']) { + $allocationReport = new AllocationReport; + $allocationReport->allocation_id = $detail['allocation_id']; + $allocationReport->allocationable_id = $payment->id; + $allocationReport->allocationable_type = $payment::$morphName; + $allocationReport->form_id = $payment->form->id; + $allocationReport->notes = $detail['notes']; + + $allocationReport->save(); + } + }, $data['details']); + } + private static function calculateAmount($paymentDetails) { return array_reduce($paymentDetails, function ($carry, $detail) { From cde358375e4d97cb39ef37c648420c531553baf1 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Thu, 3 Nov 2022 11:32:07 +0700 Subject: [PATCH 15/24] [Payment] Fix sort by on getReferences --- .../Controllers/Api/Finance/Payment/PaymentController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index 963743ee2..21d9911a4 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -56,7 +56,8 @@ public function store(StorePaymentRequest $request) ->load('form') ->load('paymentable') ->load('details.allocation') - ->load('details.referenceable.form'); + ->load('details.referenceable.form') + ->load('cashAdvances'); return new ApiResource($payment); }); @@ -233,7 +234,7 @@ public function getReferences(Request $request) $downPayments = PurchaseDownPayment::joins($downPayments, $downPaymentRequest->get('join'))->get(); $references = $references->concat($downPayments); - $references = $references->sortBy('date'); + $references = $references->sortBy($request->get('sort_by')); $paginatedReferences = paginate_collection($references, $request->get('limit'), $request->get('page')); return new ApiCollection($paginatedReferences); From a14d2dfc13eb09a86452b512ffc90e944f6ab331 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Sun, 6 Nov 2022 12:47:04 +0700 Subject: [PATCH 16/24] WIP fix payment with cash advance --- .../CashAdvance/CashAdvancePayment.php | 35 +++++++++++++++++++ app/Model/Finance/Payment/Payment.php | 28 +++++++++++++-- app/Traits/Model/Finance/PaymentRelation.php | 6 ++++ ...cash_advance_payment_add_amount_column.php | 32 +++++++++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 app/Model/Finance/CashAdvance/CashAdvancePayment.php create mode 100644 database/migrations/tenant/2022_11_04_052031_alter_cash_advance_payment_add_amount_column.php diff --git a/app/Model/Finance/CashAdvance/CashAdvancePayment.php b/app/Model/Finance/CashAdvance/CashAdvancePayment.php new file mode 100644 index 000000000..fec45a3b4 --- /dev/null +++ b/app/Model/Finance/CashAdvance/CashAdvancePayment.php @@ -0,0 +1,35 @@ +belongsTo(CashAdvance::class); + } + + public function payment() + { + return $this->belongsTo(Payment::class); + } +} diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index 7dc036794..922a08140 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -9,6 +9,7 @@ use App\Model\AllocationReport; use App\Model\Finance\PaymentOrder\PaymentOrder; use App\Model\Finance\CashAdvance\CashAdvance; +use App\Model\Finance\CashAdvance\CashAdvancePayment; use App\Model\Form; use App\Model\Purchase\PurchaseDownPayment\PurchaseDownPayment; use App\Model\Sales\PaymentCollection\PaymentCollection; @@ -182,8 +183,31 @@ public static function create($data) ); $form->save(); - if (isset($data['cashAdvances'])) { - self::mapCashAdvances($data, $payment, $form); + if (isset($data['cash_advance']['id']) && $data['cash_advance']['id'] != null) { + $cashAdvance = CashAdvance::find($data['cash_advance']['id']); + + $payByCashAdvance = $cashAdvance->amount_remaining; + + if ($cashAdvance->amount_remaining > $payment->amount) { + $payByCashAdvance = $payment->amount; + $cashAdvance->amount_remaining = $cashAdvance->amountRemaining - $payment->amount; + } + + CashAdvancePayment::create([ + 'cash_advance_id' => $cashAdvance->id, + 'payment_id' => $payment->id, + 'amount' => $payByCashAdvance + ]); + + if ($cashAdvance->amount_remaining == 0 || $data['cash_advance']['close'] == true) { + $cashAdvance->amount_remaining = 0; + $cashAdvance->form->done = 1; + $cashAdvance->form->save(); + } + $cashAdvance->save(); + + $data['activity'] = ucfirst(strtolower($cashAdvance->payment_type)) . ' Out Withdrawal (' . $form->number . ')'; + CashAdvance::mapHistory($cashAdvance, $data); } // Save allocation reports diff --git a/app/Traits/Model/Finance/PaymentRelation.php b/app/Traits/Model/Finance/PaymentRelation.php index 874044414..a2c636fe1 100644 --- a/app/Traits/Model/Finance/PaymentRelation.php +++ b/app/Traits/Model/Finance/PaymentRelation.php @@ -4,6 +4,7 @@ use App\Model\Accounting\ChartOfAccount; use App\Model\Finance\CashAdvance\CashAdvance; +use App\Model\Finance\CashAdvance\CashAdvancePayment; use App\Model\Finance\Payment\PaymentDetail; use App\Model\Form; use App\Model\Purchase\PurchaseOrder\PurchaseOrder; @@ -54,4 +55,9 @@ public function cashAdvances() { return $this->belongsToMany(CashAdvance::class, 'cash_advance_payment', 'payment_id', 'cash_advance_id'); } + + public function cashAdvance() + { + return $this->hasOne(CashAdvancePayment::class); + } } diff --git a/database/migrations/tenant/2022_11_04_052031_alter_cash_advance_payment_add_amount_column.php b/database/migrations/tenant/2022_11_04_052031_alter_cash_advance_payment_add_amount_column.php new file mode 100644 index 000000000..100da4556 --- /dev/null +++ b/database/migrations/tenant/2022_11_04_052031_alter_cash_advance_payment_add_amount_column.php @@ -0,0 +1,32 @@ +unsignedDecimal('amount', 65, 30); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('cash_advance_payment', function (Blueprint $table) { + $table->dropColumn(['amount']); + }); + } +} From e6028c0438f38e66f9ea20d103d9e68022adb66e Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Sun, 6 Nov 2022 20:25:40 +0700 Subject: [PATCH 17/24] Fix typo attribute --- app/Model/Finance/CashAdvance/CashAdvancePayment.php | 5 +++++ app/Model/Finance/Payment/Payment.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Model/Finance/CashAdvance/CashAdvancePayment.php b/app/Model/Finance/CashAdvance/CashAdvancePayment.php index fec45a3b4..e691eff5a 100644 --- a/app/Model/Finance/CashAdvance/CashAdvancePayment.php +++ b/app/Model/Finance/CashAdvance/CashAdvancePayment.php @@ -21,6 +21,11 @@ class CashAdvancePayment extends TransactionModel protected $fillable = [ 'payment_id', 'cash_advance_id', + 'amount' + ]; + + protected $casts = [ + 'amount' => 'double' ]; public function cashAdvance() diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index 922a08140..b97691ac5 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -190,7 +190,7 @@ public static function create($data) if ($cashAdvance->amount_remaining > $payment->amount) { $payByCashAdvance = $payment->amount; - $cashAdvance->amount_remaining = $cashAdvance->amountRemaining - $payment->amount; + $cashAdvance->amount_remaining = $cashAdvance->amount_remaining - $payment->amount; } CashAdvancePayment::create([ From 42486c107e0b09d07c2e0cced567f614e666baf2 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Mon, 7 Nov 2022 07:10:49 +0700 Subject: [PATCH 18/24] [Payment] Feature test create cash out --- app/Model/Finance/Payment/Payment.php | 2 +- .../Feature/Http/Finance/Cash/CashOutTest.php | 288 ++++++++++++++++++ 2 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/Http/Finance/Cash/CashOutTest.php diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index 13099fe38..f51af05f6 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -173,7 +173,7 @@ public static function create($data) if ($isPaymentCollection) { self::updateJournalPaymentCollection($payment, $journalsPaymentCollection); } else { - error_log('false'); + // error_log('false'); self::updateJournal($payment); } diff --git a/tests/Feature/Http/Finance/Cash/CashOutTest.php b/tests/Feature/Http/Finance/Cash/CashOutTest.php new file mode 100644 index 000000000..b4419156a --- /dev/null +++ b/tests/Feature/Http/Finance/Cash/CashOutTest.php @@ -0,0 +1,288 @@ +signIn(); + $this->setProject(); + $this->importChartOfAccount(); + } + + private function importChartOfAccount() + { + Excel::import(new ChartOfAccountImport(), storage_path('template/chart_of_accounts_manufacture.xlsx')); + + $this->artisan('db:seed', [ + '--database' => 'tenant', + '--class' => 'SettingJournalSeeder', + '--force' => true, + ]); + } + + public function getChartOfAccountCash() + { + return ChartOfAccount::whereHas('type', function ($query) { + $query->where('name', 'CASH'); + })->first(); + } + + public function createPaymentOrder($onlyGetData = false) + { + $countDetails = rand(1, 5); + + $coas = ChartOfAccount::whereHas('type', function ($query) { + $query->whereIn('name', ['DIRECT EXPENSE', 'OTHER EXPENSE', 'OTHER CURRENT ASSET', 'INCOME TAX RECEIVABLE', 'INCOME TAX PAYABLE', 'OTHER ACCOUNT RECEIVABLE', 'OTHER CURRENT LIABILITY', 'LIABILITAS JANGKA PANJANG', 'FACTORY OVERHEAD COST']); + })->inRandomOrder()->limit($countDetails)->get(); + $details = []; + for ($i = 0; $i < $countDetails; $i++) { + ${'coa' . $i} = $coas->skip($i)->first(); + $details[$i] = [ + 'chart_of_account_id' => ${'coa' . $i}->id, + 'chart_of_account_name' => ${'coa' . $i}->label, + 'amount' => rand(1, 100) * 1_000, + 'allocation_id' => factory(Allocation::class)->create()->id + ]; + } + + $paymentable = factory(Customer::class)->create(); + $form = [ + 'payment_type' => 'cash', + 'due_date' => Carbon::create(date('Y'), date('m'), rand(1, 28)), + 'paymentable_id' => $paymentable->id, + 'paymentable_type' => $paymentable::$morphName, + 'details' => $details + ]; + + if ($onlyGetData) { + return $form; + } + return PaymentOrder::create($form); + } + + public function createCashAdvance($amount) + { + // Copied & adjusted from CashAdvanceTest/createDataCashAdvance & CashAdvanceTest/makePaymentCashIn + $account = ChartOfAccount::where('name', 'CASH')->first(); + $account_detail = ChartOfAccount::where('name', 'OTHER INCOME')->first(); + + $employee = factory(Employee::class)->create(); + $user = factory(User::class)->create(); + + // s: insert cash in + $amount_account = rand(10, 100) * 100_000; + $data = [ + 'increment_group' => date('Ym'), + 'date' => date('Y-m-d H:i:s'), + 'due_date' => date('Y-m-d H:i:s'), + 'payment_type' => "cash", + 'payment_account_id' => $account->id, + 'paymentable_id' => $employee->id, + 'paymentable_name' => $employee->name, + 'paymentable_type' => Employee::$morphName, + 'disbursed' => false, + 'notes' => null, + 'amount' => $amount_account, + 'details' => array( + [ + 'chart_of_account_id' => $account_detail->id, + 'amount' => $amount_account, + 'allocation_id' => null, + 'allocation_name' => null, + 'notes' => "Kas" + ] + ) + ]; + $payment = Payment::create($data); + + $form = $payment->form; + $journal = new Journal; + $journal->form_id = $form->id; + $journal->chart_of_account_id = $account->id; + $journal->debit = $amount_account; + $journal->save(); + + //create sample cash advance + $data = [ + 'increment_group' => date('Ym'), + 'date' => date('Y-m-d H:i:s'), + 'payment_type' => 'cash', + 'employee_id' => $employee->id, + 'request_approval_to' => $user->id, + 'notes' => 'Notes Form', + 'amount' => $amount, + 'activity' => 'Created', + 'details' => array( + [ + 'chart_of_account_id' => $account->id, + 'amount' => $amount, + 'notes' => 'Notes' + ] + ) + ]; + + return CashAdvance::create($data); + } + + public function transformPaymentOrderDetails($paymentOrder) + { + return $paymentOrder->details->transform(function ($detail) { + return [ + 'allocation_id' => $detail->allocation_id, + 'amount' => $detail->amount, + 'chart_of_account_id' => $detail->chart_of_account_id, + 'notes' => $detail->notes + ]; + }); + } + + public function paymentAssertDatabaseHas($response, $data) + { + $this->assertDatabaseHas('payments', [ + 'id' => $response->json('data.id'), + 'payment_type' => "CASH" + ], 'tenant') + ->assertDatabaseHas('forms', [ + 'id' => $response->json('data.form.id') + ], 'tenant'); + + foreach ($data['details'] as $detail) { + $this->assertDatabaseHas('payment_details', $detail, 'tenant'); + } + } + + public function getDataPayment($reference) + { + $paymentAccount = $this->getChartOfAccountCash(); + $details = []; + if ($reference::$morphName == 'PaymentOrder') { + $details = $this->transformPaymentOrderDetails($reference); + } + + return [ + 'date' => date('Y-m-d H:i:s'), + 'increment_group' => date('Ym'), + 'payment_account_id' => $paymentAccount->id, + 'disbursed' => true, + 'paymentable_id' => $reference->id, + 'paymentable_type' => $reference::$morphName, + 'details' => $details, + ]; + } + + // Test success get all cash outs + /** @test */ + public function success_get_all_cash_outs() + { + $this->success_cash_out_from_payment_order_with_cash_advance_and_account(); + + $response = $this->json('GET', self::$path . '/payments?join=form,payment_account,details,account,allocation&sort_by=-form.date&fields=payment.*&filter_form=notArchived%3Bnull&filter_like=%7B%7D&filter_equal=%7B%22payment.payment_type%22:%22cash%22%7D&filter_date_min=%7B%22form.date%22:%22' . date('Y-m-01') . '+00:00:00%22%7D&filter_date_max=%7B%22form.date%22:%22' . date('Y-m-d') . '+23:59:59%22%7D&limit=10&includes=form%3Bdetails.chartOfAccount%3Bdetails.allocation%3Bpaymentable&page=1', $this->headers); + $response->assertStatus(200); + } + + // Test success get a cash out + /** @test */ + public function success_get_a_cash_out() + { + $this->success_cash_out_from_payment_order_without_cash_advance(); + $payment = Payment::orderBy('id', 'desc')->first(); + $data = [ + 'includes' => 'form.branch;paymentAccount;details.chartOfAccount;details.allocation' + ]; + $response = $this->json('GET', self::$path . '/payments/' . $payment->id, $data, $this->headers); + $response->assertStatus(200); + } + + // Test create payment order for reference cash out + /** @test */ + public function success_create_payment_order() + { + $data = $this->createPaymentOrder(true); + $response = $this->json('POST', self::$path . '/payment-orders', $data, $this->headers); + $response->assertStatus(201); + + $this->assertDatabaseHas('payment_orders', [ + 'id' => $response->json('data.id') + ], 'tenant') + ->assertDatabaseHas('forms', [ + 'id' => $response->json('data.form.id') + ], 'tenant'); + + foreach ($data['details'] as $detail) { + unset($detail['chart_of_account_name']); + $this->assertDatabaseHas('payment_order_details', $detail, 'tenant'); + } + } + + // Test cash out from payment order without cash advance + /** @test */ + public function success_cash_out_from_payment_order_without_cash_advance() + { + $paymentOrder = $this->createPaymentOrder(); + $data = $this->getDataPayment($paymentOrder); + + $response = $this->json('POST', self::$path . '/payments', $data, $this->headers); + $response->assertStatus(201); + + $this->paymentAssertDatabaseHas($response, $data); + } + + // Test cash out from payment order with cash advance and account + /** @test */ + public function success_cash_out_from_payment_order_with_cash_advance_and_account() + { + $paymentOrder = $this->createPaymentOrder(); + $data = $this->getDataPayment($paymentOrder); + $cashAdvance = $this->createCashAdvance($paymentOrder->amount - 10_000); + + $data['cash_advance'] = [ + 'id' => $cashAdvance->id, + 'amount' => $cashAdvance->amount_remaining + ]; + + $response = $this->json('POST', self::$path . '/payments', $data, $this->headers); + $response->assertStatus(201); + + $this->paymentAssertDatabaseHas($response, $data); + } + + // Test cash out from payment order with cash advance only + /** @test */ + public function success_cash_out_from_payment_order_with_cash_advance_only() + { + $paymentOrder = $this->createPaymentOrder(); + $data = $this->getDataPayment($paymentOrder); + $cashAdvance = $this->createCashAdvance($paymentOrder->amount); + + $data['cash_advance'] = [ + 'id' => $cashAdvance->id, + 'amount' => $cashAdvance->amount_remaining + ]; + + $response = $this->json('POST', self::$path . '/payments', $data, $this->headers); + $response->assertStatus(201); + + $this->paymentAssertDatabaseHas($response, $data); + } +} From 184c301f8d74267680971991f487cafeed55f52e Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Tue, 8 Nov 2022 09:26:15 +0700 Subject: [PATCH 19/24] [Payment] Add validation on cash/bank out & fix delete cash out --- .../PaymentCancellationApprovalController.php | 85 ++++++++++++++----- .../Payment/Payment/StorePaymentRequest.php | 29 ++++++- app/Model/AllocationReport.php | 8 ++ app/Model/Finance/Payment/Payment.php | 2 +- app/Traits/Model/Finance/PaymentRelation.php | 6 ++ 5 files changed, 108 insertions(+), 22 deletions(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php index e4c9a1f80..c4c8bb26d 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php @@ -8,7 +8,9 @@ use App\Http\Controllers\Controller; use App\Http\Resources\ApiResource; use App\Model\Accounting\Journal; +use App\Model\Finance\CashAdvance\CashAdvance; use App\Model\Finance\Payment\Payment; +use App\Model\UserActivity; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; @@ -26,15 +28,15 @@ public function approve(Request $request, $id) $payment = Payment::findOrFail($id); // ### Approve fail if - // Jika Role Bukan Super Admin dan tidak memiliki akses approval maka mengirimkan pesan eror + $this->isCancellationRequestStillValid($payment); if ($request->has('token')) { // approve from email $approvalBy = $request->get('approver_id'); } else { + // Jika Role Bukan Super Admin dan tidak memiliki akses approval maka mengirimkan pesan eror $payment->isHaveAccessToDelete(); $approvalBy = auth()->user()->id; } - $this->isCancellationRequestStillValid($payment); DB::connection('tenant')->transaction(function () use ($payment, $approvalBy) { // ### If approve success then @@ -42,29 +44,75 @@ public function approve(Request $request, $id) $payment->form->cancellation_approval_by = $approvalBy; $payment->form->cancellation_approval_at = now(); $payment->form->cancellation_status = 1; + $payment->form->save(); + + // Jumlah saldo cash account / cash advance/ biaya yang dipilih akan bertambah sebesar data yang dihapus + // Pengembalian dana cash advance & status formnya menjadi pending + $amountPaidByCashAdvance = 0; + if ($payment->cashAdvance) { + $cashAdvancePayment = $payment->cashAdvance; + $cashAdvance = $cashAdvancePayment->cashAdvance; + $amountPaidByCashAdvance = $payment->cashAdvance->amount; + + $cashAdvance->amount_remaining += $amountPaidByCashAdvance; + $cashAdvance->save(); + + $cashAdvance->form->done = 0; + $cashAdvance->form->save(); + + $history = new UserActivity; + + $history->table_type = $cashAdvance::$morphName; + $history->table_id = $cashAdvance->id; + $history->number = $cashAdvance->form->number; + $history->user_id = $approvalBy; + $history->date = convert_to_local_timezone(date('Y-m-d H:i:s')); + $history->activity = 'Cash Out Refund (' . $cashAdvance->form->number . ')'; + + $history->save(); + } + + // Pengembalian dana account + $amountPaidByAccount = $payment->amount - $amountPaidByCashAdvance; + $journal = new Journal; + $journal->form_id = $payment->form->id; + $journal->journalable_type = $payment->paymentable_type; + $journal->journalable_id = $payment->paymentable_id; + $journal->chart_of_account_id = $payment->payment_account_id; + if ($payment->disbursed) { + $journal->debit = $amountPaidByAccount; + } else { + $journal->credit = $amountPaidByAccount; + } + $journal->save(); - // Status form payment order & cash advance jadi pending foreach ($payment->details as $paymentDetail) { + $journal = new Journal; + $journal->form_id = $payment->form->id; + $journal->form_id_reference = optional(optional($paymentDetail->referenceable)->form)->id; + $journal->journalable_type = $payment->paymentable_type; + $journal->journalable_id = $payment->paymentable_id; + $journal->notes = $paymentDetail->notes; + $journal->chart_of_account_id = $paymentDetail->chart_of_account_id; + if (!$payment->disbursed) { + $journal->credit = $paymentDetail->amount; + } else { + $journal->debit = $paymentDetail->amount; + } + $journal->save(); + + // Status form payment order jadi pending $paymentDetail->referenceable->form->done = 0; $paymentDetail->referenceable->form->save(); } - $cashAdvanceAmount = $payment->details()->sum('amount') - $payment->amount; - // Jumlah saldo cash account / cash advance/ biaya yang dipilih akan bertambah sebesar data yang dihapus (?) - foreach ($payment->cashAdvances as $cashAdvance) { - $cashAdvance->form->done = 0; - $cashAdvance->form->save(); - } - // $payment->disbursed = !$payment->disbursed; // Data jurnal cash out dari cash report / journal / general ledger/ subledger akan berkurang sebesar data yang dihapus (?) - // $payment::updateJournal($payment); - - // Delete data allocation pada allocation report (?) - $payment->form->save(); + // Delete data allocation pada allocation report + $payment->allocationReports()->delete(); }); - $payment->form = $payment->form; + $payment->load('form'); return new ApiResource($payment); } @@ -84,18 +132,17 @@ public function reject(Request $request, $id) $payment = Payment::findOrFail($id); // ### Reject fail if - // Jika Role Bukan Super Admin / Pihak yang dipilih utk approval maka akan mengirimkan pesan eror - // Jika tidak memiliki akses approval pada payment order maka akan mengirimkan pesan eror + $this->isCancellationRequestStillValid($payment); if ($request->has('token')) { // reject from email $approvalBy = $request->get('approver_id'); } else { + // Jika Role Bukan Super Admin / Pihak yang dipilih utk approval maka akan mengirimkan pesan eror + // Jika tidak memiliki akses approval pada payment order maka akan mengirimkan pesan eror $payment->isHaveAccessToDelete(); $approvalBy = auth()->user()->id; } - $this->isCancellationRequestStillValid($payment); - DB::connection('tenant')->transaction(function () use ($payment, $request, $approvalBy) { // ### If reject success then // Update status approval form menjadi rejected diff --git a/app/Http/Requests/Finance/Payment/Payment/StorePaymentRequest.php b/app/Http/Requests/Finance/Payment/Payment/StorePaymentRequest.php index 034cca592..fd94948ca 100644 --- a/app/Http/Requests/Finance/Payment/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Finance/Payment/Payment/StorePaymentRequest.php @@ -2,8 +2,10 @@ namespace App\Http\Requests\Finance\Payment\Payment; +use App\Exceptions\PointException; use App\Http\Requests\ValidationRule; use App\Model\Accounting\ChartOfAccount; +use App\Model\Finance\CashAdvance\CashAdvance; use App\Model\Finance\Payment\PaymentDetail; use App\Model\Master\Allocation; use Illuminate\Foundation\Http\FormRequest; @@ -43,8 +45,8 @@ public function rules() 'details.*.allocation_id' => ValidationRule::foreignKeyNullable(Allocation::getTableName()), 'details.*.referenceable_type' => [ function ($attribute, $value, $fail) { - if (! PaymentDetail::referenceableIsValid($value)) { - $fail($attribute.' is invalid'); + if (!PaymentDetail::referenceableIsValid($value)) { + $fail($attribute . ' is invalid'); } }, ], @@ -52,4 +54,27 @@ function ($attribute, $value, $fail) { return array_merge($rulesForm, $rulesPayment, $rulesPaymentDetail); } + + public function withValidator($validator) + { + $validator->after(function ($validator) { + // Cash out/bank out + if (request()->get('disbursed') == 1) { + $amountCashAdvance = 0; + $needToPayFromAccount = request()->get('amount'); + + if ((request()->filled('cash_advance.id'))) { + $cashAdvance = CashAdvance::find(request()->get('cash_advance')['id']); + $amountCashAdvance = $cashAdvance->amount_remaining; + $needToPayFromAccount = request()->get('amount') - $amountCashAdvance; + } + + // Check balance payment account + $balancePaymentAccount = ChartOfAccount::find(request()->get('payment_account_id'))->total(date('Y-m-d 23:59:59')); + if ($balancePaymentAccount < $needToPayFromAccount) { + throw new PointException('Balance is not enough'); + } + } + }); + } } diff --git a/app/Model/AllocationReport.php b/app/Model/AllocationReport.php index 3c3698bc6..fe3b53347 100644 --- a/app/Model/AllocationReport.php +++ b/app/Model/AllocationReport.php @@ -20,4 +20,12 @@ public function form() { return $this->belongsTo(Form::class); } + + /** + * Get all of the owning formable models. + */ + public function allocationable() + { + return $this->morphTo(); + } } diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index 4165986b5..3d18bb59a 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -282,7 +282,7 @@ private static function mapCashAdvances($data, $payment, $form) private static function mapAllocationReports($data, $payment) { return array_map(function ($detail) use ($data, $payment) { - if ($detail['allocation_id']) { + if (isset($detail['allocation_id'])) { $allocationReport = new AllocationReport; $allocationReport->allocation_id = $detail['allocation_id']; $allocationReport->allocationable_id = $payment->id; diff --git a/app/Traits/Model/Finance/PaymentRelation.php b/app/Traits/Model/Finance/PaymentRelation.php index a2c636fe1..dfd252c28 100644 --- a/app/Traits/Model/Finance/PaymentRelation.php +++ b/app/Traits/Model/Finance/PaymentRelation.php @@ -3,6 +3,7 @@ namespace App\Traits\Model\Finance; use App\Model\Accounting\ChartOfAccount; +use App\Model\AllocationReport; use App\Model\Finance\CashAdvance\CashAdvance; use App\Model\Finance\CashAdvance\CashAdvancePayment; use App\Model\Finance\Payment\PaymentDetail; @@ -60,4 +61,9 @@ public function cashAdvance() { return $this->hasOne(CashAdvancePayment::class); } + + public function allocationReports() + { + return $this->morphMany(AllocationReport::class, 'allocationable'); + } } From 9cd8e398135a447614306d7f4cc21b97eb58e5e4 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Tue, 8 Nov 2022 20:46:36 +0700 Subject: [PATCH 20/24] [Payment] Fix mail format on cancellation request --- .../Payment/PaymentCancellationApprovalRequest.php | 3 ++- .../finance/payment/cancellation-approval.blade.php | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/Mail/Finance/Payment/PaymentCancellationApprovalRequest.php b/app/Mail/Finance/Payment/PaymentCancellationApprovalRequest.php index a34738303..1f2f6b9e8 100644 --- a/app/Mail/Finance/Payment/PaymentCancellationApprovalRequest.php +++ b/app/Mail/Finance/Payment/PaymentCancellationApprovalRequest.php @@ -42,7 +42,8 @@ public function build() 'approverId' => $this->approver->id, 'fullName' => $this->approver->getFullNameAttribute(), 'form' => $this->form, - 'token' => $this->token + 'token' => $this->token, + 'cashAdvancePayment' => $this->payment->cashAdvance ]); } } diff --git a/resources/views/emails/finance/payment/cancellation-approval.blade.php b/resources/views/emails/finance/payment/cancellation-approval.blade.php index d090f73e4..8695e3e49 100644 --- a/resources/views/emails/finance/payment/cancellation-approval.blade.php +++ b/resources/views/emails/finance/payment/cancellation-approval.blade.php @@ -25,15 +25,13 @@ Form Reference : {{ $payment->details()->first()->referenceable->form->number ?: '-' }} - @foreach ($payment->cashAdvances as $cashAdvance) Cash Advance - : {{ $payment->form->number ?: '-' }} + : {{ $cashAdvancePayment ? $cashAdvancePayment->cashAdvance->form->number : '-' }} - @endforeach Amount Cash Advance - : {{ $payment->amount - $payment->details()->sum('amount') }} + : {{ $cashAdvancePayment ? $cashAdvancePayment->amount : '-' }} Cash Account @@ -79,7 +77,7 @@ {{ $detail->amount }} - {{ $detail->allocation->name }} + {{ $detail->allocation ? $detail->allocation->name : '-' }} @php ($i++) From 1caa45f6c71c7a748487d06378bb344a219da64b Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Wed, 9 Nov 2022 10:16:43 +0700 Subject: [PATCH 21/24] Enable user to request delete although has been rejected --- app/Model/Finance/Payment/Payment.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index 3d18bb59a..ec31f30a4 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -58,16 +58,16 @@ public function isAllowedToDelete() $this->isHaveAccessToDelete(); // - Jika form sudah di-request to delete - if ($form->request_cancellation_by != null) { - throw new PointException("Form already request to delete"); - } + // if ($form->request_cancellation_by != null) { + // throw new PointException("Form already request to delete"); + // } // - Jika pada periode yang akan didelete sudah dilakukan close book maka akan mengirimkan pesan eror - $now = Carbon::now(); - $formDate = Carbon::parse($form->date); - if ($now->month != $formDate->month) { - throw new PointException("Cannot delete form because the book period is closed"); - } + // $now = Carbon::now(); + // $formDate = Carbon::parse($form->date); + // if ($now->month != $formDate->month) { + // throw new PointException("Cannot delete form because the book period is closed"); + // } } // Check if auth user have access to delete payment From f06b0d104cc8624644779cff978e84cb4e5878e9 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Wed, 9 Nov 2022 15:31:10 +0700 Subject: [PATCH 22/24] Fix rejected cancellation approval by email --- .../Payment/PaymentCancellationApprovalController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php index c4c8bb26d..59e4b5d88 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php @@ -125,10 +125,6 @@ public function approve(Request $request, $id) */ public function reject(Request $request, $id) { - $request->validate([ - 'reason' => 'required' - ]); - $payment = Payment::findOrFail($id); // ### Reject fail if @@ -136,7 +132,11 @@ public function reject(Request $request, $id) if ($request->has('token')) { // reject from email $approvalBy = $request->get('approver_id'); + $request->merge(['reason' => 'Rejected by email']); } else { + $request->validate([ + 'reason' => 'required' + ]); // Jika Role Bukan Super Admin / Pihak yang dipilih utk approval maka akan mengirimkan pesan eror // Jika tidak memiliki akses approval pada payment order maka akan mengirimkan pesan eror $payment->isHaveAccessToDelete(); From 3c20115eb100301e8605a40e35af1cdf6ae660ad Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Thu, 10 Nov 2022 08:50:12 +0700 Subject: [PATCH 23/24] Fix validation & remove unused codes --- .../PaymentCancellationApprovalController.php | 40 ++-- .../Api/Finance/Payment/PaymentController.php | 8 +- .../Payment/Payment/StorePaymentRequest.php | 18 +- app/Model/Finance/Payment/Payment.php | 44 +--- .../Http/Finance/Cash/CashOutSetup.php | 190 ++++++++++++++++++ .../Feature/Http/Finance/Cash/CashOutTest.php | 190 +----------------- 6 files changed, 237 insertions(+), 253 deletions(-) create mode 100644 tests/Feature/Http/Finance/Cash/CashOutSetup.php diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php index 59e4b5d88..02167d11d 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentCancellationApprovalController.php @@ -60,16 +60,8 @@ public function approve(Request $request, $id) $cashAdvance->form->done = 0; $cashAdvance->form->save(); - $history = new UserActivity; - - $history->table_type = $cashAdvance::$morphName; - $history->table_id = $cashAdvance->id; - $history->number = $cashAdvance->form->number; - $history->user_id = $approvalBy; - $history->date = convert_to_local_timezone(date('Y-m-d H:i:s')); - $history->activity = 'Cash Out Refund (' . $cashAdvance->form->number . ')'; - - $history->save(); + $activity = 'Payment Refund (' . $payment->form->number . ')'; + $this->writeHistory($cashAdvance, $approvalBy, $activity); } // Pengembalian dana account @@ -101,13 +93,11 @@ public function approve(Request $request, $id) } $journal->save(); - // Status form payment order jadi pending + // Status form reference jadi pending $paymentDetail->referenceable->form->done = 0; $paymentDetail->referenceable->form->save(); } - // Data jurnal cash out dari cash report / journal / general ledger/ subledger akan berkurang sebesar data yang dihapus (?) - // Delete data allocation pada allocation report $payment->allocationReports()->delete(); }); @@ -165,13 +155,29 @@ public function reject(Request $request, $id) public function isCancellationRequestStillValid($payment) { - // is cancellation request already approved / rejected? + // is cancellation request already approved? $cancellationStatus = $payment->form->cancellation_status; if ($cancellationStatus == 1) { throw new PointException('Form cancellation already approved'); } - if ($cancellationStatus == -1) { - throw new PointException('Form cancellation already rejected'); - } + // is cancellation request already rejected? + // if ($cancellationStatus == -1) { + // throw new PointException('Form cancellation already rejected'); + // } + } + + // $reference = cash advance, + public function writeHistory($reference, int $userId, string $activity) + { + $history = new UserActivity; + + $history->table_type = $reference::$morphName; + $history->table_id = $reference->id; + $history->number = $reference->form->number; + $history->user_id = $userId; + $history->date = convert_to_local_timezone(date('Y-m-d H:i:s')); + $history->activity = $activity; + + $history->save(); } } diff --git a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php index 21d9911a4..fc46cac4a 100644 --- a/app/Http/Controllers/Api/Finance/Payment/PaymentController.php +++ b/app/Http/Controllers/Api/Finance/Payment/PaymentController.php @@ -57,7 +57,7 @@ public function store(StorePaymentRequest $request) ->load('paymentable') ->load('details.allocation') ->load('details.referenceable.form') - ->load('cashAdvances'); + ->load('cashAdvance.cashAdvance.form'); return new ApiResource($payment); }); @@ -134,10 +134,6 @@ public function destroy(Request $request, $id) DB::connection('tenant')->transaction(function () use ($payment, $request) { $payment->requestCancel($request); - // Status form cash out jadi pending - $payment->form->done = 0; - $payment->form->save(); - // Kirim notifikasi by program & email $superAdminRole = Role::where('name', 'super admin')->first(); $emailUsers = User::whereHas('roles', function (Builder $query) use ($superAdminRole) { @@ -177,8 +173,6 @@ public function destroy(Request $request, $id) // } // } // } - - }); return response()->json([], 204); diff --git a/app/Http/Requests/Finance/Payment/Payment/StorePaymentRequest.php b/app/Http/Requests/Finance/Payment/Payment/StorePaymentRequest.php index fd94948ca..38aef780a 100644 --- a/app/Http/Requests/Finance/Payment/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Finance/Payment/Payment/StorePaymentRequest.php @@ -37,6 +37,7 @@ public function rules() 'paymentable_id' => 'required|integer|min:0', 'paymentable_type' => 'required|string', 'details' => 'required|array', + 'notes' => 'nullable|max:255' ]; $rulesPaymentDetail = [ @@ -66,13 +67,20 @@ public function withValidator($validator) if ((request()->filled('cash_advance.id'))) { $cashAdvance = CashAdvance::find(request()->get('cash_advance')['id']); $amountCashAdvance = $cashAdvance->amount_remaining; - $needToPayFromAccount = request()->get('amount') - $amountCashAdvance; + if ($amountCashAdvance > $needToPayFromAccount) { + // All covered by cash advance + $needToPayFromAccount = 0; + } else { + $needToPayFromAccount = request()->get('amount') - $amountCashAdvance; + } } - // Check balance payment account - $balancePaymentAccount = ChartOfAccount::find(request()->get('payment_account_id'))->total(date('Y-m-d 23:59:59')); - if ($balancePaymentAccount < $needToPayFromAccount) { - throw new PointException('Balance is not enough'); + if ($needToPayFromAccount > 0) { + // Check balance payment account + $balancePaymentAccount = ChartOfAccount::find(request()->get('payment_account_id'))->total(date('Y-m-d 23:59:59')); + if ($balancePaymentAccount < $needToPayFromAccount) { + throw new PointException('Balance is not enough'); + } } } }); diff --git a/app/Model/Finance/Payment/Payment.php b/app/Model/Finance/Payment/Payment.php index ec31f30a4..6cff1f1b4 100644 --- a/app/Model/Finance/Payment/Payment.php +++ b/app/Model/Finance/Payment/Payment.php @@ -17,7 +17,6 @@ use App\Model\TransactionModel; use App\Traits\Model\Finance\PaymentJoin; use App\Traits\Model\Finance\PaymentRelation; -use Carbon\Carbon; class Payment extends TransactionModel { @@ -51,23 +50,13 @@ public function isAllowedToUpdate() public function isAllowedToDelete() { // TODO isAllowed to delete? - $form = $this->form; // Forbidden to delete, - // - Jika tidak memiliki permission + // Jika memiliki permission $this->isHaveAccessToDelete(); - // - Jika form sudah di-request to delete - // if ($form->request_cancellation_by != null) { - // throw new PointException("Form already request to delete"); - // } - - // - Jika pada periode yang akan didelete sudah dilakukan close book maka akan mengirimkan pesan eror - // $now = Carbon::now(); - // $formDate = Carbon::parse($form->date); - // if ($now->month != $formDate->month) { - // throw new PointException("Cannot delete form because the book period is closed"); - // } + // Jika pada periode yang akan didelete sudah dilakukan close book maka akan mengirimkan pesan eror + // Wait for next release feature } // Check if auth user have access to delete payment @@ -183,6 +172,8 @@ public static function create($data) ); $form->save(); + $payByCashAdvance = 0; + // If user select cash advance if (isset($data['cash_advance']['id']) && $data['cash_advance']['id'] != null) { $cashAdvance = CashAdvance::find($data['cash_advance']['id']); @@ -254,31 +245,6 @@ private static function mapPaymentDetails($data) }, $data['details']); } - private static function mapCashAdvances($data, $payment, $form) - { - return array_map(function ($detail) use ($data, $payment, $form) { - if ($payment->amount == 0) { - return; - } - // Adjusted & copied from payment::create where referenceable_type = 'CashAdvance' - $cashAdvance = CashAdvance::find($detail['cash_advance_id']); - if ($cashAdvance->amount_remaining < $detail['amount']) { - throw new PointException('Amount is over pay'); - } - $payment->amount = $payment->amount - $detail['amount']; - $cashAdvance->payments()->attach($payment->id); - $cashAdvance->amount_remaining = $cashAdvance->amount_remaining - $detail['amount']; - if ($cashAdvance->amount_remaining == 0) { - $cashAdvance->form->done = 1; - $cashAdvance->form->save(); - } - $cashAdvance->save(); - - $data['activity'] = ucfirst(strtolower($cashAdvance->payment_type)) . ' Out Withdrawal (' . $form->number . ')'; - CashAdvance::mapHistory($cashAdvance, $data); - }, $data['cashAdvances']); - } - private static function mapAllocationReports($data, $payment) { return array_map(function ($detail) use ($data, $payment) { diff --git a/tests/Feature/Http/Finance/Cash/CashOutSetup.php b/tests/Feature/Http/Finance/Cash/CashOutSetup.php new file mode 100644 index 000000000..653cda434 --- /dev/null +++ b/tests/Feature/Http/Finance/Cash/CashOutSetup.php @@ -0,0 +1,190 @@ +signIn(); + $this->setProject(); + $this->importChartOfAccount(); + } + + private function importChartOfAccount() + { + Excel::import(new ChartOfAccountImport(), storage_path('template/chart_of_accounts_manufacture.xlsx')); + + $this->artisan('db:seed', [ + '--database' => 'tenant', + '--class' => 'SettingJournalSeeder', + '--force' => true, + ]); + } + + public function getChartOfAccountCash() + { + return ChartOfAccount::whereHas('type', function ($query) { + $query->where('name', 'CASH'); + })->first(); + } + + public function createPaymentOrder($onlyGetData = false) + { + $countDetails = rand(1, 5); + + $coas = ChartOfAccount::whereHas('type', function ($query) { + $query->whereIn('name', ['DIRECT EXPENSE', 'OTHER EXPENSE', 'OTHER CURRENT ASSET', 'INCOME TAX RECEIVABLE', 'INCOME TAX PAYABLE', 'OTHER ACCOUNT RECEIVABLE', 'OTHER CURRENT LIABILITY', 'LIABILITAS JANGKA PANJANG', 'FACTORY OVERHEAD COST']); + })->inRandomOrder()->limit($countDetails)->get(); + $details = []; + for ($i = 0; $i < $countDetails; $i++) { + ${'coa' . $i} = $coas->skip($i)->first(); + $details[$i] = [ + 'chart_of_account_id' => ${'coa' . $i}->id, + 'chart_of_account_name' => ${'coa' . $i}->label, + 'amount' => rand(1, 100) * 1_000, + 'allocation_id' => factory(Allocation::class)->create()->id + ]; + } + + $paymentable = factory(Customer::class)->create(); + $form = [ + 'payment_type' => 'cash', + 'due_date' => Carbon::create(date('Y'), date('m'), rand(1, 28)), + 'paymentable_id' => $paymentable->id, + 'paymentable_type' => $paymentable::$morphName, + 'details' => $details + ]; + + if ($onlyGetData) { + return $form; + } + return PaymentOrder::create($form); + } + + public function createCashAdvance($amount) + { + // Copied & adjusted from CashAdvanceTest/createDataCashAdvance & CashAdvanceTest/makePaymentCashIn + $account = ChartOfAccount::where('name', 'CASH')->first(); + $account_detail = ChartOfAccount::where('name', 'OTHER INCOME')->first(); + + $employee = factory(Employee::class)->create(); + $user = factory(User::class)->create(); + + // s: insert cash in + $amount_account = rand(10, 100) * 100_000; + $data = [ + 'increment_group' => date('Ym'), + 'date' => date('Y-m-d H:i:s'), + 'due_date' => date('Y-m-d H:i:s'), + 'payment_type' => "cash", + 'payment_account_id' => $account->id, + 'paymentable_id' => $employee->id, + 'paymentable_name' => $employee->name, + 'paymentable_type' => Employee::$morphName, + 'disbursed' => false, + 'notes' => null, + 'amount' => $amount_account, + 'details' => array( + [ + 'chart_of_account_id' => $account_detail->id, + 'amount' => $amount_account, + 'allocation_id' => null, + 'allocation_name' => null, + 'notes' => "Kas" + ] + ) + ]; + $payment = Payment::create($data); + + $form = $payment->form; + $journal = new Journal; + $journal->form_id = $form->id; + $journal->chart_of_account_id = $account->id; + $journal->debit = $amount_account; + $journal->save(); + + //create sample cash advance + $data = [ + 'increment_group' => date('Ym'), + 'date' => date('Y-m-d H:i:s'), + 'payment_type' => 'cash', + 'employee_id' => $employee->id, + 'request_approval_to' => $user->id, + 'notes' => 'Notes Form', + 'amount' => $amount, + 'activity' => 'Created', + 'details' => array( + [ + 'chart_of_account_id' => $account->id, + 'amount' => $amount, + 'notes' => 'Notes' + ] + ) + ]; + + return CashAdvance::create($data); + } + + public function transformPaymentOrderDetails($paymentOrder) + { + return $paymentOrder->details->transform(function ($detail) { + return [ + 'allocation_id' => $detail->allocation_id, + 'amount' => $detail->amount, + 'chart_of_account_id' => $detail->chart_of_account_id, + 'notes' => $detail->notes + ]; + }); + } + + public function paymentAssertDatabaseHas($response, $data) + { + $this->assertDatabaseHas('payments', [ + 'id' => $response->json('data.id'), + 'payment_type' => "CASH" + ], 'tenant') + ->assertDatabaseHas('forms', [ + 'id' => $response->json('data.form.id') + ], 'tenant'); + + foreach ($data['details'] as $detail) { + $this->assertDatabaseHas('payment_details', $detail, 'tenant'); + } + } + + public function getDataPayment($reference) + { + $paymentAccount = $this->getChartOfAccountCash(); + $details = []; + if ($reference::$morphName == 'PaymentOrder') { + $details = $this->transformPaymentOrderDetails($reference); + } + + return [ + 'date' => date('Y-m-d H:i:s'), + 'increment_group' => date('Ym'), + 'payment_account_id' => $paymentAccount->id, + 'disbursed' => true, + 'paymentable_id' => $reference->id, + 'paymentable_type' => $reference::$morphName, + 'details' => $details, + ]; + } +} diff --git a/tests/Feature/Http/Finance/Cash/CashOutTest.php b/tests/Feature/Http/Finance/Cash/CashOutTest.php index 2540e3b13..14025833c 100644 --- a/tests/Feature/Http/Finance/Cash/CashOutTest.php +++ b/tests/Feature/Http/Finance/Cash/CashOutTest.php @@ -2,194 +2,14 @@ namespace Tests\Feature\Http\Finance\Cash; -use App\Imports\Template\ChartOfAccountImport; -use App\Model\Accounting\ChartOfAccount; -use App\Model\Accounting\Journal; -use App\Model\Finance\CashAdvance\CashAdvance; use App\Model\Finance\Payment\Payment; -use App\Model\Finance\PaymentOrder\PaymentOrder; -use App\Model\HumanResource\Employee\Employee; -use App\Model\Master\Allocation; -use App\Model\Master\Customer; -use App\Model\Master\User; -use Carbon\Carbon; -use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Foundation\Testing\WithFaker; -use Maatwebsite\Excel\Facades\Excel; use Tests\TestCase; class CashOutTest extends TestCase { - public static $path = '/api/v1/finance'; - - public function setUp(): void - { - parent::setUp(); - $this->signIn(); - $this->setProject(); - $this->importChartOfAccount(); - } - - private function importChartOfAccount() - { - Excel::import(new ChartOfAccountImport(), storage_path('template/chart_of_accounts_manufacture.xlsx')); - - $this->artisan('db:seed', [ - '--database' => 'tenant', - '--class' => 'SettingJournalSeeder', - '--force' => true, - ]); - } - - public function getChartOfAccountCash() - { - return ChartOfAccount::whereHas('type', function ($query) { - $query->where('name', 'CASH'); - })->first(); - } - - public function createPaymentOrder($onlyGetData = false) - { - $countDetails = rand(1, 5); - - $coas = ChartOfAccount::whereHas('type', function ($query) { - $query->whereIn('name', ['DIRECT EXPENSE', 'OTHER EXPENSE', 'OTHER CURRENT ASSET', 'INCOME TAX RECEIVABLE', 'INCOME TAX PAYABLE', 'OTHER ACCOUNT RECEIVABLE', 'OTHER CURRENT LIABILITY', 'LIABILITAS JANGKA PANJANG', 'FACTORY OVERHEAD COST']); - })->inRandomOrder()->limit($countDetails)->get(); - $details = []; - for ($i = 0; $i < $countDetails; $i++) { - ${'coa' . $i} = $coas->skip($i)->first(); - $details[$i] = [ - 'chart_of_account_id' => ${'coa' . $i}->id, - 'chart_of_account_name' => ${'coa' . $i}->label, - 'amount' => rand(1, 100) * 1_000, - 'allocation_id' => factory(Allocation::class)->create()->id - ]; - } - - $paymentable = factory(Customer::class)->create(); - $form = [ - 'payment_type' => 'cash', - 'due_date' => Carbon::create(date('Y'), date('m'), rand(1, 28)), - 'paymentable_id' => $paymentable->id, - 'paymentable_type' => $paymentable::$morphName, - 'details' => $details - ]; - - if ($onlyGetData) { - return $form; - } - return PaymentOrder::create($form); - } - - public function createCashAdvance($amount) - { - // Copied & adjusted from CashAdvanceTest/createDataCashAdvance & CashAdvanceTest/makePaymentCashIn - $account = ChartOfAccount::where('name', 'CASH')->first(); - $account_detail = ChartOfAccount::where('name', 'OTHER INCOME')->first(); - - $employee = factory(Employee::class)->create(); - $user = factory(User::class)->create(); - - // s: insert cash in - $amount_account = rand(10, 100) * 100_000; - $data = [ - 'increment_group' => date('Ym'), - 'date' => date('Y-m-d H:i:s'), - 'due_date' => date('Y-m-d H:i:s'), - 'payment_type' => "cash", - 'payment_account_id' => $account->id, - 'paymentable_id' => $employee->id, - 'paymentable_name' => $employee->name, - 'paymentable_type' => Employee::$morphName, - 'disbursed' => false, - 'notes' => null, - 'amount' => $amount_account, - 'details' => array( - [ - 'chart_of_account_id' => $account_detail->id, - 'amount' => $amount_account, - 'allocation_id' => null, - 'allocation_name' => null, - 'notes' => "Kas" - ] - ) - ]; - $payment = Payment::create($data); + use CashOutSetup; - $form = $payment->form; - $journal = new Journal; - $journal->form_id = $form->id; - $journal->chart_of_account_id = $account->id; - $journal->debit = $amount_account; - $journal->save(); - - //create sample cash advance - $data = [ - 'increment_group' => date('Ym'), - 'date' => date('Y-m-d H:i:s'), - 'payment_type' => 'cash', - 'employee_id' => $employee->id, - 'request_approval_to' => $user->id, - 'notes' => 'Notes Form', - 'amount' => $amount, - 'activity' => 'Created', - 'details' => array( - [ - 'chart_of_account_id' => $account->id, - 'amount' => $amount, - 'notes' => 'Notes' - ] - ) - ]; - - return CashAdvance::create($data); - } - - public function transformPaymentOrderDetails($paymentOrder) - { - return $paymentOrder->details->transform(function ($detail) { - return [ - 'allocation_id' => $detail->allocation_id, - 'amount' => $detail->amount, - 'chart_of_account_id' => $detail->chart_of_account_id, - 'notes' => $detail->notes - ]; - }); - } - - public function paymentAssertDatabaseHas($response, $data) - { - $this->assertDatabaseHas('payments', [ - 'id' => $response->json('data.id'), - 'payment_type' => "CASH" - ], 'tenant') - ->assertDatabaseHas('forms', [ - 'id' => $response->json('data.form.id') - ], 'tenant'); - - foreach ($data['details'] as $detail) { - $this->assertDatabaseHas('payment_details', $detail, 'tenant'); - } - } - - public function getDataPayment($reference) - { - $paymentAccount = $this->getChartOfAccountCash(); - $details = []; - if ($reference::$morphName == 'PaymentOrder') { - $details = $this->transformPaymentOrderDetails($reference); - } - - return [ - 'date' => date('Y-m-d H:i:s'), - 'increment_group' => date('Ym'), - 'payment_account_id' => $paymentAccount->id, - 'disbursed' => true, - 'paymentable_id' => $reference->id, - 'paymentable_type' => $reference::$morphName, - 'details' => $details, - ]; - } + public static $path = '/api/v1/finance'; // Test success get all cash outs /** @test */ @@ -258,7 +78,7 @@ public function success_cash_out_from_payment_order_with_cash_advance_and_accoun $data['cash_advance'] = [ 'id' => $cashAdvance->id, - 'amount' => $cashAdvance->amount_remaining + 'close' => false ]; $response = $this->json('POST', self::$path . '/payments', $data, $this->headers); @@ -277,7 +97,7 @@ public function success_cash_out_from_payment_order_with_cash_advance_only() $data['cash_advance'] = [ 'id' => $cashAdvance->id, - 'amount' => $cashAdvance->amount_remaining + 'close' => false ]; $response = $this->json('POST', self::$path . '/payments', $data, $this->headers); @@ -285,4 +105,4 @@ public function success_cash_out_from_payment_order_with_cash_advance_only() $this->paymentAssertDatabaseHas($response, $data); } -} \ No newline at end of file +} From 6e0772b29ef28636c5f278cadc0cec9cda866202 Mon Sep 17 00:00:00 2001 From: Siti Laelatur Rochmah Date: Mon, 21 Nov 2022 08:03:57 +0700 Subject: [PATCH 24/24] Tdd cash out cancellation --- .../Finance/Cash/CashOutCancellationTest.php | 95 +++++++++++++++++++ .../Http/Finance/Cash/CashOutSetup.php | 52 +++++++++- 2 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 tests/Feature/Http/Finance/Cash/CashOutCancellationTest.php diff --git a/tests/Feature/Http/Finance/Cash/CashOutCancellationTest.php b/tests/Feature/Http/Finance/Cash/CashOutCancellationTest.php new file mode 100644 index 000000000..2c3b69e7d --- /dev/null +++ b/tests/Feature/Http/Finance/Cash/CashOutCancellationTest.php @@ -0,0 +1,95 @@ +createPaymentOrder(); + $data = $this->getDataPayment($paymentOrder); + + $this->json('POST', self::$path . '/payments', $data, $this->headers); + + return Payment::orderBy('id', 'desc')->first(); + } + + // Success request to delete + /** @test */ + public function success_request_to_delete() + { + $data = [ + 'reason' => 'Please delete this form because...' + ]; + + $payment = $this->createPayment(); + + $response = $this->json('DELETE', self::$path . '/payments/' . $payment->id, $data, $this->headers); + $response->assertStatus(204); + } + + // Fail request to delete because didn't insert reason + /** @test */ + public function fail_request_to_delete() + { + $payment = $this->createPayment(); + + $response = $this->json('DELETE', self::$path . '/payments/' . $payment->id, $this->headers); + $response->assertStatus(422); + } + + public function getPaymentReadyToCancellation() + { + $data = [ + 'reason' => 'Please delete this form because...' + ]; + + $payment = $this->createPayment(); + $this->json('DELETE', self::$path . '/payments/' . $payment->id, $data, $this->headers); + + return Payment::orderBy('id', 'desc')->first(); + } + + // Success approve to delete + /** @test */ + public function success_approve_to_delete() + { + $payment = $this->getPaymentReadyToCancellation(); + + $response = $this->json('POST', self::$path . '/payments/' . $payment->id . '/cancellation-approve', $this->headers); + $response->assertStatus(200); + } + + // Success reject to delete + /** @test */ + public function success_reject_to_delete() + { + $data = [ + 'reason' => 'Sorry, this form can\'t be deleted' + ]; + + $payment = $this->getPaymentReadyToCancellation(); + + $response = $this->json('POST', self::$path . '/payments/' . $payment->id . '/cancellation-reject', $data, $this->headers); + $response->assertStatus(200); + } + + // Fail reject to delete + /** @test */ + public function fail_reject_to_delete() + { + $payment = $this->getPaymentReadyToCancellation(); + + $response = $this->json('POST', self::$path . '/payments/' . $payment->id . '/cancellation-reject', $this->headers); + $response->assertStatus(422); + } +} diff --git a/tests/Feature/Http/Finance/Cash/CashOutSetup.php b/tests/Feature/Http/Finance/Cash/CashOutSetup.php index 653cda434..4f13190b5 100644 --- a/tests/Feature/Http/Finance/Cash/CashOutSetup.php +++ b/tests/Feature/Http/Finance/Cash/CashOutSetup.php @@ -23,6 +23,7 @@ public function setUp(): void { parent::setUp(); $this->signIn(); + $this->setRole(); $this->setProject(); $this->importChartOfAccount(); } @@ -75,7 +76,12 @@ public function createPaymentOrder($onlyGetData = false) if ($onlyGetData) { return $form; } - return PaymentOrder::create($form); + $paymentOrder = PaymentOrder::create($form); + // Approve payment order + $paymentOrder->form->approval_by = auth()->user()->id; + $paymentOrder->form->save(); + + return $paymentOrder; } public function createCashAdvance($amount) @@ -138,7 +144,7 @@ public function createCashAdvance($amount) ] ) ]; - + return CashAdvance::create($data); } @@ -172,19 +178,57 @@ public function paymentAssertDatabaseHas($response, $data) public function getDataPayment($reference) { $paymentAccount = $this->getChartOfAccountCash(); + $this->makePaymentCashIn($paymentAccount, $reference->amount); $details = []; if ($reference::$morphName == 'PaymentOrder') { $details = $this->transformPaymentOrderDetails($reference); } return [ + 'amount' => $reference->amount, + 'payment_type' => $reference->payment_type, 'date' => date('Y-m-d H:i:s'), 'increment_group' => date('Ym'), 'payment_account_id' => $paymentAccount->id, 'disbursed' => true, - 'paymentable_id' => $reference->id, - 'paymentable_type' => $reference::$morphName, + 'paymentable_id' => $reference->paymentable_id, + 'paymentable_type' => $reference->paymentable_type, + 'referenceable_type' => $reference::$morphName, + 'referenceable_id' => $reference->id, 'details' => $details, ]; } + + // Copied from CashAdvanceTest + public function makePaymentCashIn($account, $amount_account) + { + $paymentable = factory(Customer::class)->create(); + $account_detail = ChartOfAccount::where('name', 'OTHER INCOME')->first(); + // s: insert cash in + $data = [ + 'increment_group' => date('Ym'), + 'date' => date('Y-m-d H:i:s'), + 'due_date' => date('Y-m-d H:i:s'), + 'payment_type' => "cash", + 'payment_account_id' => $account->id, + 'paymentable_id' => $paymentable->id, + 'paymentable_name' => $paymentable->name, + 'paymentable_type' => $paymentable::$morphName, + 'disbursed' => false, + 'notes' => null, + 'amount' => $amount_account, + 'details' => array( + [ + 'chart_of_account_id' => $account_detail->id, + 'amount' => $amount_account, + 'allocation_id' => null, + 'allocation_name' => null, + 'notes' => "Kas" + ] + ) + ]; + + Payment::create($data); + // e: insert cash in + } }