From 75f17e546602807df966c54d7e18743a3f0cceaa Mon Sep 17 00:00:00 2001 From: denisdulici Date: Tue, 21 Jan 2020 09:10:12 +0300 Subject: [PATCH] transactions import/export --- app/Abstracts/Import.php | 108 +-------- app/Exports/Banking/Transactions.php | 31 ++- app/Imports/Banking/Transactions.php | 14 +- app/Imports/Common/Items.php | 9 +- app/Imports/Purchases/Payments.php | 33 +-- app/Imports/Sales/Revenues.php | 33 +-- app/Imports/Sales/Sheets/InvoiceItemTaxes.php | 8 +- .../Sales/Sheets/InvoiceTransactions.php | 31 +-- app/Imports/Sales/Sheets/Invoices.php | 13 +- app/Traits/Import.php | 212 ++++++++++++++++++ public/files/import/transactions.xlsx | Bin 6829 -> 9445 bytes 11 files changed, 270 insertions(+), 222 deletions(-) create mode 100644 app/Traits/Import.php diff --git a/app/Abstracts/Import.php b/app/Abstracts/Import.php index 2165336f6..66f5edefa 100644 --- a/app/Abstracts/Import.php +++ b/app/Abstracts/Import.php @@ -2,11 +2,7 @@ namespace App\Abstracts; -use App\Models\Banking\Account; -use App\Models\Common\Contact; -use App\Models\Common\Item; -use App\Models\Setting\Category; -use App\Models\Setting\Tax; +use App\Traits\Import as ImportHelpers; use Illuminate\Support\Str; use Jenssegers\Date\Date; use Maatwebsite\Excel\Concerns\Importable; @@ -22,7 +18,7 @@ use Maatwebsite\Excel\Validators\Failure; abstract class Import implements ToModel, SkipsOnError, SkipsOnFailure, WithBatchInserts, WithChunkReading, WithHeadingRow, WithMapping, WithValidation { - use Importable; + use Importable, ImportHelpers; public $empty_field = 'empty---'; @@ -91,104 +87,4 @@ abstract class Import implements ToModel, SkipsOnError, SkipsOnFailure, WithBatc { flash($e->getMessage())->error()->important(); } - - public function getAccountIdFromCurrency($row) - { - return Account::firstOrCreate([ - 'currency_code' => $row['currency_code'], - ], [ - 'company_id' => session('company_id'), - 'name' => $row['currency_code'], - 'number' => Account::max('number') + 1, - 'opening_balance' => 0, - 'enabled' => 1, - ])->id; - } - - public function getAccountIdFromName($row) - { - return Account::firstOrCreate([ - 'name' => $row['account_name'], - ], [ - 'company_id' => session('company_id'), - 'number' => Account::max('number') + 1, - 'currency_code' => setting('default.currency'), - 'opening_balance' => 0, - 'enabled' => 1, - ])->id; - } - - public function getAccountIdFromNumber($row) - { - return Account::firstOrCreate([ - 'number' => $row['account_number'], - ], [ - 'company_id' => session('company_id'), - 'name' => $row['account_number'], - 'currency_code' => setting('default.currency'), - 'opening_balance' => 0, - 'enabled' => 1, - ])->id; - } - - public function getCategoryIdFromName($row, $type) - { - return Category::firstOrCreate([ - 'name' => $row['category_name'], - ], [ - 'company_id' => session('company_id'), - 'type' => $type, - 'color' => '#' . dechex(rand(0x000000, 0xFFFFFF)), - 'enabled' => 1, - ])->id; - } - - public function getContactIdFromEmail($row, $type) - { - return Contact::firstOrCreate([ - 'email' => $row['contact_email'], - ], [ - 'company_id' => session('company_id'), - 'type' => $type, - 'name' => $row['contact_email'], - 'currency_code' => setting('default.currency'), - 'enabled' => 1, - ])->id; - } - - public function getContactIdFromName($row, $type) - { - return Contact::firstOrCreate([ - 'name' => $row['contact_name'], - ], [ - 'company_id' => session('company_id'), - 'type' => $type, - 'currency_code' => setting('default.currency'), - 'enabled' => 1, - ])->id; - } - - public function getItemIdFromName($row) - { - return Item::firstOrCreate([ - 'name' => $row['item_name'], - ], [ - 'company_id' => session('company_id'), - 'sale_price' => $row['price'], - 'purchase_price' => $row['price'], - 'enabled' => 1, - ])->id; - } - - public function getTaxIdFromRate($row, $type = 'normal') - { - return Tax::firstOrCreate([ - 'rate' => $row['tax_rate'], - ], [ - 'company_id' => session('company_id'), - 'type' => $type, - 'name' => $row['tax_rate'], - 'enabled' => 1, - ])->id; - } } diff --git a/app/Exports/Banking/Transactions.php b/app/Exports/Banking/Transactions.php index d991e6a22..828ef3e3c 100644 --- a/app/Exports/Banking/Transactions.php +++ b/app/Exports/Banking/Transactions.php @@ -9,7 +9,28 @@ class Transactions extends Export { public function collection() { - return Model::usingSearchString(request('search'))->get(); + $model = Model::with(['account', 'bill', 'category', 'contact', 'invoice'])->usingSearchString(request('search'))->get(); + + if (!empty($this->ids)) { + $model->whereIn('id', (array) $this->ids); + } + + return $model->get(); + } + + public function map($model): array + { + $model->account_name = $model->account->name; + $model->contact_email = $model->contact->email; + $model->category_name = $model->category->name; + + if ($model->type == 'income') { + $model->invoice_bill_number = $model->invoice ? $model->invoice->invoice_number : 0; + } else { + $model->invoice_bill_number = $model->bill ? $model->bill->bill_number : 0; + } + + return parent::map($model); } public function fields(): array @@ -20,10 +41,10 @@ class Transactions extends Export 'amount', 'currency_code', 'currency_rate', - 'account_id', - 'document_id', - 'contact_id', - 'category_id', + 'account_name', + 'invoice_bill_number', + 'contact_email', + 'category_name', 'description', 'payment_method', 'reference', diff --git a/app/Imports/Banking/Transactions.php b/app/Imports/Banking/Transactions.php index 75a25e98e..03d86d35a 100644 --- a/app/Imports/Banking/Transactions.php +++ b/app/Imports/Banking/Transactions.php @@ -3,8 +3,8 @@ namespace App\Imports\Banking; use App\Abstracts\Import; -use App\Models\Banking\Transaction as Model; use App\Http\Requests\Banking\Transaction as Request; +use App\Models\Banking\Transaction as Model; class Transactions extends Import { @@ -13,6 +13,18 @@ class Transactions extends Import return new Model($row); } + public function map($row): array + { + $row = parent::map($row); + + $row['account_id'] = $this->getAccountId($row); + $row['category_id'] = $this->getCategoryId($row); + $row['contact_id'] = $this->getContactId($row); + $row['document_id'] = $this->getDocumentId($row); + + return $row; + } + public function rules(): array { return (new Request())->rules(); diff --git a/app/Imports/Common/Items.php b/app/Imports/Common/Items.php index 6560b1ab9..f3e78b060 100644 --- a/app/Imports/Common/Items.php +++ b/app/Imports/Common/Items.php @@ -17,13 +17,8 @@ class Items extends Import { $row = parent::map($row); - if (empty($row['category_id']) && !empty($row['category_name'])) { - $row['category_id'] = $this->getCategoryIdFromName($row, 'item'); - } - - if (empty($row['tax_id']) && !empty($row['tax_rate'])) { - $row['tax_id'] = $this->getTaxIdFromRate($row); - } + $row['category_id'] = $this->getCategoryId($row, 'item'); + $row['tax_id'] = $this->getTaxId($row); return $row; } diff --git a/app/Imports/Purchases/Payments.php b/app/Imports/Purchases/Payments.php index 6c6a20e9f..e5b9a9dde 100644 --- a/app/Imports/Purchases/Payments.php +++ b/app/Imports/Purchases/Payments.php @@ -5,7 +5,6 @@ namespace App\Imports\Purchases; use App\Abstracts\Import; use App\Models\Banking\Transaction as Model; use App\Http\Requests\Banking\Transaction as Request; -use App\Models\Purchase\Bill; class Payments extends Import { @@ -19,34 +18,10 @@ class Payments extends Import $row = parent::map($row); $row['type'] = 'expense'; - - if (empty($row['account_id']) && !empty($row['account_name'])) { - $row['account_id'] = $this->getAccountIdFromName($row); - } - - if (empty($row['account_id']) && !empty($row['account_number'])) { - $row['account_id'] = $this->getAccountIdFromNumber($row); - } - - if (empty($row['account_id']) && !empty($row['currency_code'])) { - $row['account_id'] = $this->getAccountIdFromCurrency($row); - } - - if (empty($row['contact_id']) && !empty($row['contact_name'])) { - $row['contact_id'] = $this->getContactIdFromName($row, 'vendor'); - } - - if (empty($row['contact_id']) && !empty($row['contact_email'])) { - $row['contact_id'] = $this->getContactIdFromEmail($row, 'vendor'); - } - - if (empty($row['category_id']) && !empty($row['category_name'])) { - $row['category_id'] = $this->getCategoryIdFromName($row, 'expense'); - } - - if (!empty($row['bill_number'])) { - $row['document_id'] = Bill::number($row['bill_number'])->pluck('id')->first(); - } + $row['account_id'] = $this->getAccountId($row); + $row['category_id'] = $this->getCategoryId($row, 'expense'); + $row['contact_id'] = $this->getContactId($row, 'vendor'); + $row['document_id'] = $this->getDocumentId($row); return $row; } diff --git a/app/Imports/Sales/Revenues.php b/app/Imports/Sales/Revenues.php index 57e4da863..babc5e92a 100644 --- a/app/Imports/Sales/Revenues.php +++ b/app/Imports/Sales/Revenues.php @@ -5,7 +5,6 @@ namespace App\Imports\Sales; use App\Abstracts\Import; use App\Http\Requests\Banking\Transaction as Request; use App\Models\Banking\Transaction as Model; -use App\Models\Sale\Invoice; class Revenues extends Import { @@ -19,34 +18,10 @@ class Revenues extends Import $row = parent::map($row); $row['type'] = 'income'; - - if (empty($row['account_id']) && !empty($row['account_name'])) { - $row['account_id'] = $this->getAccountIdFromName($row); - } - - if (empty($row['account_id']) && !empty($row['account_number'])) { - $row['account_id'] = $this->getAccountIdFromNumber($row); - } - - if (empty($row['account_id']) && !empty($row['currency_code'])) { - $row['account_id'] = $this->getAccountIdFromCurrency($row); - } - - if (empty($row['contact_id']) && !empty($row['contact_name'])) { - $row['contact_id'] = $this->getContactIdFromName($row, 'customer'); - } - - if (empty($row['contact_id']) && !empty($row['contact_email'])) { - $row['contact_id'] = $this->getContactIdFromEmail($row, 'customer'); - } - - if (empty($row['category_id']) && !empty($row['category_name'])) { - $row['category_id'] = $this->getCategoryIdFromName($row, 'income'); - } - - if (!empty($row['invoice_number'])) { - $row['document_id'] = Invoice::number($row['invoice_number'])->pluck('id')->first(); - } + $row['account_id'] = $this->getAccountId($row); + $row['category_id'] = $this->getCategoryId($row, 'income'); + $row['contact_id'] = $this->getContactId($row, 'customer'); + $row['document_id'] = $this->getDocumentId($row); return $row; } diff --git a/app/Imports/Sales/Sheets/InvoiceItemTaxes.php b/app/Imports/Sales/Sheets/InvoiceItemTaxes.php index 54a770c95..58ab7e812 100644 --- a/app/Imports/Sales/Sheets/InvoiceItemTaxes.php +++ b/app/Imports/Sales/Sheets/InvoiceItemTaxes.php @@ -35,13 +35,7 @@ class InvoiceItemTaxes extends Import $row['invoice_item_id'] = InvoiceItem::where('item_id', $item_id)->pluck('id')->first(); } - if (empty($row['tax_id']) && !empty($row['tax_name'])) { - $row['tax_id'] = Tax::name($row['tax_name'])->pluck('id')->first(); - } - - if (empty($row['tax_id']) && !empty($row['tax_rate'])) { - $row['tax_id'] = $this->getTaxIdFromRate($row); - } + $row['tax_id'] = $this->getTaxId($row); if (empty($row['name']) && !empty($row['item_name'])) { $row['name'] = $row['item_name']; diff --git a/app/Imports/Sales/Sheets/InvoiceTransactions.php b/app/Imports/Sales/Sheets/InvoiceTransactions.php index 429177e9d..1b4ec7e9f 100644 --- a/app/Imports/Sales/Sheets/InvoiceTransactions.php +++ b/app/Imports/Sales/Sheets/InvoiceTransactions.php @@ -5,7 +5,6 @@ namespace App\Imports\Sales\Sheets; use App\Abstracts\Import; use App\Http\Requests\Banking\Transaction as Request; use App\Models\Banking\Transaction as Model; -use App\Models\Sale\Invoice; class InvoiceTransactions extends Import { @@ -19,32 +18,10 @@ class InvoiceTransactions extends Import $row = parent::map($row); $row['type'] = 'income'; - - if (empty($row['account_id']) && !empty($row['account_name'])) { - $row['account_id'] = $this->getAccountIdFromName($row); - } - - if (empty($row['account_id']) && !empty($row['account_number'])) { - $row['account_id'] = $this->getAccountIdFromNumber($row); - } - - if (empty($row['account_id']) && !empty($row['currency_code'])) { - $row['account_id'] = $this->getAccountIdFromCurrency($row); - } - - if (empty($row['contact_id']) && !empty($row['contact_name'])) { - $row['contact_id'] = $this->getContactIdFromName($row, 'customer'); - } - - if (empty($row['contact_id']) && !empty($row['contact_email'])) { - $row['contact_id'] = $this->getContactIdFromEmail($row, 'customer'); - } - - if (empty($row['category_id']) && !empty($row['category_name'])) { - $row['category_id'] = $this->getCategoryIdFromName($row, 'income'); - } - - $row['document_id'] = Invoice::number($row['invoice_number'])->pluck('id')->first(); + $row['account_id'] = $this->getAccountId($row); + $row['category_id'] = $this->getCategoryId($row, 'income'); + $row['contact_id'] = $this->getContactId($row, 'customer'); + $row['document_id'] = $this->getDocumentId($row); return $row; } diff --git a/app/Imports/Sales/Sheets/Invoices.php b/app/Imports/Sales/Sheets/Invoices.php index 5d1afb140..d13377113 100644 --- a/app/Imports/Sales/Sheets/Invoices.php +++ b/app/Imports/Sales/Sheets/Invoices.php @@ -17,17 +17,8 @@ class Invoices extends Import { $row = parent::map($row); - if (empty($row['contact_id']) && !empty($row['contact_name'])) { - $row['contact_id'] = $this->getContactIdFromName($row, 'customer'); - } - - if (empty($row['contact_id']) && !empty($row['contact_email'])) { - $row['contact_id'] = $this->getContactIdFromEmail($row, 'customer'); - } - - if (empty($row['category_id']) && !empty($row['category_name'])) { - $row['category_id'] = $this->getCategoryIdFromName($row, 'income'); - } + $row['category_id'] = $this->getCategoryId($row, 'income'); + $row['contact_id'] = $this->getContactId($row, 'customer'); return $row; } diff --git a/app/Traits/Import.php b/app/Traits/Import.php new file mode 100644 index 000000000..704c4c578 --- /dev/null +++ b/app/Traits/Import.php @@ -0,0 +1,212 @@ +getAccountIdFromName($row); + } + + if (empty($id) && !empty($row['account_number'])) { + $id = $this->getAccountIdFromNumber($row); + } + + if (empty($id) && !empty($row['currency_code'])) { + $id = $this->getAccountIdFromCurrency($row); + } + + return $id; + } + + public function getCategoryId($row, $type = null) + { + $id = isset($row['category_id']) ? $row['category_id'] : null; + + $type = !empty($type) ? $type : (!empty($row['type']) ? $row['type'] : 'income'); + + if (empty($id) && !empty($row['category_name'])) { + $id = $this->getCategoryIdFromName($row, $type); + } + + return $id; + } + + public function getContactId($row, $type = null) + { + $id = isset($row['contact_id']) ? $row['contact_id'] : null; + + $type = !empty($type) ? $type : (!empty($row['type']) ? (($row['type'] == 'income') ? 'customer' : 'vendor') : 'customer'); + + if (empty($id) && !empty($row['contact_name'])) { + $id = $this->getContactIdFromName($row, $type); + } + + if (empty($row['contact_id']) && !empty($row['contact_email'])) { + $id = $this->getContactIdFromEmail($row, $type); + } + + return $id; + } + + public function getDocumentId($row) + { + $id = isset($row['document_id']) ? $row['document_id'] : null; + + if (empty($id) && !empty($row['invoice_number'])) { + $id = Invoice::number($row['invoice_number'])->pluck('id')->first(); + } + + if (empty($id) && !empty($row['bill_number'])) { + $id = Bill::number($row['bill_number'])->pluck('id')->first(); + } + + if (empty($id) && !empty($row['invoice_bill_number'])) { + if ($row['type'] == 'income') { + $id = Invoice::number($row['invoice_bill_number'])->pluck('id')->first(); + } else { + $id = Bill::number($row['invoice_bill_number'])->pluck('id')->first(); + } + } + + return $id; + } + + public function getItemId($row) + { + $id = isset($row['item_id']) ? $row['item_id'] : null; + + if (empty($id) && !empty($row['item_name'])) { + $id = $this->getItemIdFromName($row); + } + + return $id; + } + + public function getTaxId($row) + { + $id = isset($row['tax_id']) ? $row['tax_id'] : null; + + if (empty($id) && !empty($row['tax_name'])) { + $id = Tax::name($row['tax_name'])->pluck('id')->first(); + } + + if (empty($id) && !empty($row['tax_rate'])) { + $id = $this->getTaxIdFromRate($row); + } + + return $id; + } + + public function getAccountIdFromCurrency($row) + { + return Account::firstOrCreate([ + 'currency_code' => $row['currency_code'], + ], [ + 'company_id' => session('company_id'), + 'name' => $row['currency_code'], + 'number' => Account::max('number') + 1, + 'opening_balance' => 0, + 'enabled' => 1, + ])->id; + } + + public function getAccountIdFromName($row) + { + return Account::firstOrCreate([ + 'name' => $row['account_name'], + ], [ + 'company_id' => session('company_id'), + 'number' => Account::max('number') + 1, + 'currency_code' => setting('default.currency'), + 'opening_balance' => 0, + 'enabled' => 1, + ])->id; + } + + public function getAccountIdFromNumber($row) + { + return Account::firstOrCreate([ + 'number' => $row['account_number'], + ], [ + 'company_id' => session('company_id'), + 'name' => $row['account_number'], + 'currency_code' => setting('default.currency'), + 'opening_balance' => 0, + 'enabled' => 1, + ])->id; + } + + public function getCategoryIdFromName($row, $type) + { + return Category::firstOrCreate([ + 'name' => $row['category_name'], + ], [ + 'company_id' => session('company_id'), + 'type' => $type, + 'color' => '#' . dechex(rand(0x000000, 0xFFFFFF)), + 'enabled' => 1, + ])->id; + } + + public function getContactIdFromEmail($row, $type) + { + return Contact::firstOrCreate([ + 'email' => $row['contact_email'], + ], [ + 'company_id' => session('company_id'), + 'type' => $type, + 'name' => $row['contact_email'], + 'currency_code' => setting('default.currency'), + 'enabled' => 1, + ])->id; + } + + public function getContactIdFromName($row, $type) + { + return Contact::firstOrCreate([ + 'name' => $row['contact_name'], + ], [ + 'company_id' => session('company_id'), + 'type' => $type, + 'currency_code' => setting('default.currency'), + 'enabled' => 1, + ])->id; + } + + public function getItemIdFromName($row) + { + return Item::firstOrCreate([ + 'name' => $row['item_name'], + ], [ + 'company_id' => session('company_id'), + 'sale_price' => $row['price'], + 'purchase_price' => $row['price'], + 'enabled' => 1, + ])->id; + } + + public function getTaxIdFromRate($row, $type = 'normal') + { + return Tax::firstOrCreate([ + 'rate' => $row['tax_rate'], + ], [ + 'company_id' => session('company_id'), + 'type' => $type, + 'name' => $row['tax_rate'], + 'enabled' => 1, + ])->id; + } +} diff --git a/public/files/import/transactions.xlsx b/public/files/import/transactions.xlsx index 7b6db1042b1414146211b29c33b6c1becb01dec3..a84bce0ba2bfaf8a650ca802e565ef15c9440d65 100644 GIT binary patch literal 9445 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 A*8l(j