From 4202464d8230922324df5727c70b600e9e258fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20=C3=87ak=C4=B1rel?= Date: Thu, 1 Oct 2020 18:20:32 +0300 Subject: [PATCH] Import/Export for Transfers --- app/BulkActions/Banking/Transfers.php | 13 ++ app/Exports/Banking/Transfers.php | 60 +++++++++ app/Http/Controllers/Banking/Transfers.php | 31 +++++ app/Imports/Banking/Transfers.php | 118 ++++++++++++++++++ public/files/import/transfers.xlsx | Bin 0 -> 5775 bytes .../views/banking/transfers/index.blade.php | 12 +- routes/admin.php | 2 + tests/Feature/Banking/TransfersTest.php | 71 ++++++++++- 8 files changed, 300 insertions(+), 7 deletions(-) create mode 100644 app/Exports/Banking/Transfers.php create mode 100644 app/Imports/Banking/Transfers.php create mode 100644 public/files/import/transfers.xlsx diff --git a/app/BulkActions/Banking/Transfers.php b/app/BulkActions/Banking/Transfers.php index e8b9a106a..23ad1f2a1 100644 --- a/app/BulkActions/Banking/Transfers.php +++ b/app/BulkActions/Banking/Transfers.php @@ -5,6 +5,7 @@ namespace App\BulkActions\Banking; use App\Abstracts\BulkAction; use App\Jobs\Banking\DeleteTransfer; use App\Models\Banking\Transfer; +use App\Exports\Banking\Transfers as Export; class Transfers extends BulkAction { @@ -16,6 +17,11 @@ class Transfers extends BulkAction 'message' => 'bulk_actions.message.delete', 'permission' => 'delete-banking-transfers', ], + 'export' => [ + 'name' => 'general.export', + 'message' => 'bulk_actions.message.export', + 'type' => 'download', + ], ]; public function destroy($request) @@ -30,4 +36,11 @@ class Transfers extends BulkAction } } } + + public function export($request) + { + $selected = $this->getSelectedInput($request); + + return \Excel::download(new Export($selected), \Str::filename(trans_choice('general.transfers', 2)) . '.xlsx'); + } } diff --git a/app/Exports/Banking/Transfers.php b/app/Exports/Banking/Transfers.php new file mode 100644 index 000000000..70c250fdb --- /dev/null +++ b/app/Exports/Banking/Transfers.php @@ -0,0 +1,60 @@ +usingSearchString(request('search')); + + if (!empty($this->ids)) { + $model->whereIn('id', (array) $this->ids); + } + + return $model->cursor(); + } + + public function map($model): array + { + $model->transferred_at = Date::parse($model->expense_transaction->paid_at)->format('Y-m-d'); + $model->amount = $model->expense_transaction->amount; + $model->from_account_name = $model->expense_transaction->account->name; + $model->from_currency_code = $model->expense_transaction->currency_code; + $model->from_currency_rate = $model->expense_transaction->currency_rate; + $model->to_account_name = $model->income_transaction->account->name; + $model->to_currency_code = $model->income_transaction->currency_code; + $model->to_currency_rate = $model->income_transaction->currency_rate; + $model->description = $model->income_transaction->description; + $model->payment_method = $model->income_transaction->payment_method; + $model->reference = $model->income_transaction->reference; + + return parent::map($model); + } + + public function fields(): array + { + return [ + 'transferred_at', + 'amount', + 'from_currency_code', + 'from_currency_rate', + 'from_account_name', + 'to_currency_code', + 'to_currency_rate', + 'to_account_name', + 'description', + 'payment_method', + 'reference', + ]; + } +} diff --git a/app/Http/Controllers/Banking/Transfers.php b/app/Http/Controllers/Banking/Transfers.php index 58cc85b32..64e7b7a71 100644 --- a/app/Http/Controllers/Banking/Transfers.php +++ b/app/Http/Controllers/Banking/Transfers.php @@ -4,6 +4,9 @@ namespace App\Http\Controllers\Banking; use App\Abstracts\Http\Controller; use App\Http\Requests\Banking\Transfer as Request; +use App\Exports\Banking\Transfers as Export; +use App\Http\Requests\Common\Import as ImportRequest; +use App\Imports\Banking\Transfers as Import; use App\Jobs\Banking\CreateTransfer; use App\Jobs\Banking\UpdateTransfer; use App\Jobs\Banking\DeleteTransfer; @@ -218,4 +221,32 @@ class Transfers extends Controller return response()->json($response); } + + /** + * Import the specified resource. + * + * @param ImportRequest $request + * + * @return Response + */ + public function import(ImportRequest $request) + { + \Excel::import(new Import(), $request->file('import')); + + $message = trans('messages.success.imported', ['type' => trans_choice('general.transfers', 2)]); + + flash($message)->success(); + + return redirect()->route('transfers.index'); + } + + /** + * Export the specified resource. + * + * @return Response + */ + public function export() + { + return \Excel::download(new Export(), \Str::filename(trans_choice('general.transfers', 2)) . '.xlsx'); + } } diff --git a/app/Imports/Banking/Transfers.php b/app/Imports/Banking/Transfers.php new file mode 100644 index 000000000..f298076f2 --- /dev/null +++ b/app/Imports/Banking/Transfers.php @@ -0,0 +1,118 @@ +getFromAccountId($row); + $row['to_account_id'] = $this->getToAccountId($row); + $row['expense_transaction_id'] = $this->getExpenseTransactionId($row); + $row['income_transaction_id'] = $this->getIncomeTransactionId($row); + + return $row; + } + + public function rules(): array + { + return [ + 'from_account_id' => 'required|integer', + 'from_currency_code' => 'required|string|currency', + 'from_currency_rate' => 'required', + 'to_account_id' => 'required|integer', + 'to_currency_code' => 'required|string|currency', + 'to_currency_rate' => 'required', + 'amount' => 'required|amount', + 'transferred_at' => 'required|date_format:Y-m-d', + 'payment_method' => 'required|string', + ]; + } + + private function getExpenseTransactionId($row) + { + $expense_transaction = Transaction::create([ + 'company_id' => session('company_id'), + 'type' => 'expense', + 'account_id' => $row['from_account_id'], + 'paid_at' => $row['transferred_at'], + 'currency_code' => $row['from_currency_code'], + 'currency_rate' => $row['from_currency_rate'], + 'amount' => $row['amount'], + 'contact_id' => 0, + 'description' => $row['description'], + 'category_id' => Category::transfer(), // Transfer Category ID + 'payment_method' => $row['payment_method'], + 'reference' => $row['reference'], + ]); + + return $expense_transaction->id; + } + + private function getIncomeTransactionId($row) + { + $amount = $row['amount']; + // Convert amount if not same currency + if ($row['from_currency_code'] !== $row['to_currency_code']) { + $amount = $this->convertBetween( + $amount, + $row['from_currency_code'], + $row['from_currency_rate'], + $row['to_currency_code'], + $row['to_currency_rate'] + ); + } + + $income_transaction = Transaction::create([ + 'company_id' => session('company_id'), + 'type' => 'income', + 'account_id' => $row['to_account_id'], + 'paid_at' => $row['transferred_at'], + 'currency_code' => $row['to_currency_code'], + 'currency_rate' => $row['to_currency_rate'], + 'amount' => $amount, + 'contact_id' => 0, + 'description' => $row['description'], + 'category_id' => Category::transfer(), // Transfer Category ID + 'payment_method' => $row['payment_method'], + 'reference' => $row['reference'], + ]); + + return $income_transaction->id; + } + + private function getFromAccountId($row) + { + $row['account_id'] = $row['from_account_id'] ?? null; + $row['account_name'] = $row['from_account_name'] ?? null; + $row['account_number'] = $row['from_account_number'] ?? null; + $row['currency_code'] = $row['from_currency_code'] ?? null; + + return $this->getAccountId($row); + } + + private function getToAccountId($row) + { + $row['account_id'] = $row['to_account_id'] ?? null; + $row['account_name'] = $row['to_account_name'] ?? null; + $row['account_number'] = $row['to_account_number'] ?? null; + $row['currency_code'] = $row['to_currency_code'] ?? null; + + return $this->getAccountId($row); + } +} diff --git a/public/files/import/transfers.xlsx b/public/files/import/transfers.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2ed56ab8f6913686508fe362c17f05b1b7ba450d GIT binary patch literal 5775 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 F{{bzNs_Os% literal 0 HcmV?d00001 diff --git a/resources/views/banking/transfers/index.blade.php b/resources/views/banking/transfers/index.blade.php index 85a461b28..12619eda1 100644 --- a/resources/views/banking/transfers/index.blade.php +++ b/resources/views/banking/transfers/index.blade.php @@ -2,11 +2,13 @@ @section('title', trans_choice('general.transfers', 2)) -@permission('create-banking-transfers') - @section('new_button') - {{ trans('general.add_new') }} - @endsection -@endpermission +@section('new_button') + @permission('create-banking-transfers') +  {{ trans('general.add_new') }} + @endpermission +  {{ trans('import.import') }} +  {{ trans('general.export') }} +@endsection @section('content') @if ($transfers->count()) diff --git a/routes/admin.php b/routes/admin.php index 37b221147..ee2ea519c 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -134,6 +134,8 @@ Route::group(['prefix' => 'banking'], function () { Route::get('transactions/export', 'Banking\Transactions@export')->name('transactions.export'); Route::resource('transactions', 'Banking\Transactions'); + Route::post('transfers/import', 'Banking\Transfers@import')->name('transfers.import'); + Route::get('transfers/export', 'Banking\Transfers@export')->name('transfers.export'); Route::resource('transfers', 'Banking\Transfers', ['middleware' => ['date.format', 'money']]); Route::post('reconciliations/calculate', 'Banking\Reconciliations@calculate')->middleware(['money']); diff --git a/tests/Feature/Banking/TransfersTest.php b/tests/Feature/Banking/TransfersTest.php index ba3b31d00..5b8f17ff6 100644 --- a/tests/Feature/Banking/TransfersTest.php +++ b/tests/Feature/Banking/TransfersTest.php @@ -2,9 +2,13 @@ namespace Tests\Feature\Banking; -use App\Models\Banking\Account; -use Tests\Feature\FeatureTestCase; +use App\Exports\Banking\Transfers as Export; use App\Jobs\Banking\CreateTransfer; +use App\Models\Banking\Account; +use App\Models\Banking\Transfer; +use Illuminate\Http\UploadedFile; +use Illuminate\Support\Facades\File; +use Tests\Feature\FeatureTestCase; class TransfersTest extends FeatureTestCase { @@ -69,6 +73,69 @@ class TransfersTest extends FeatureTestCase $this->assertFlashLevel('success'); } + public function testItShouldExportTransfers() + { + $count = 5; + factory(Transfer::class, $count)->create(); + + \Excel::fake(); + + $this->loginAs() + ->get(route('transfers.export')) + ->assertStatus(200); + + \Excel::assertDownloaded( + \Str::filename(trans_choice('general.transfers', 2)) . '.xlsx', + function (Export $export) use ($count) { + // Assert that the correct export is downloaded. + return $export->collection()->count() === $count; + } + ); + } + + public function testItShouldExportSelectedTransfers() + { + $count = 5; + $transfers = factory(Transfer::class, $count)->create(); + + \Excel::fake(); + + $this->loginAs() + ->post( + route('bulk-actions.action', ['group' => 'banking', 'type' => 'transfers']), + ['handle' => 'export', 'selected' => [$transfers->random()->id]] + ) + ->assertStatus(200); + + \Excel::assertDownloaded( + \Str::filename(trans_choice('general.transfers', 2)) . '.xlsx', + function (Export $export) { + return $export->collection()->count() === 1; + } + ); + } + + public function testItShouldImportTransfers() + { + \Excel::fake(); + + $this->loginAs() + ->post( + route('transfers.import'), + [ + 'import' => UploadedFile::fake()->createWithContent( + 'transfers.xlsx', + File::get(public_path('files/import/transfers.xlsx')) + ), + ] + ) + ->assertStatus(302); + + \Excel::assertImported('transfers.xlsx'); + + $this->assertFlashLevel('success'); + } + public function getRequest() { $from_account = factory(Account::class)->states('enabled', 'default_currency')->create();