From 6923a3d6e42cda2b24b1927f8b441660dd7bfa62 Mon Sep 17 00:00:00 2001 From: denisdulici Date: Mon, 20 Jan 2020 22:58:49 +0300 Subject: [PATCH] human friendly invoice import/export --- app/Abstracts/Import.php | 23 +++- app/Exports/Sales/Sheets/InvoiceHistories.php | 11 +- app/Exports/Sales/Sheets/InvoiceItemTaxes.php | 18 ++- app/Exports/Sales/Sheets/InvoiceItems.php | 15 ++- app/Exports/Sales/Sheets/InvoiceTotals.php | 11 +- .../Sales/Sheets/InvoiceTransactions.php | 20 +++- app/Exports/Sales/Sheets/Invoices.php | 12 +- app/Http/Requests/Sale/InvoiceItemTax.php | 34 ++++++ app/Imports/Sales/Invoices.php | 4 +- app/Imports/Sales/Sheets/InvoiceHistories.php | 12 +- app/Imports/Sales/Sheets/InvoiceItemTaxes.php | 57 +++++++++ app/Imports/Sales/Sheets/InvoiceItems.php | 36 +++++- app/Imports/Sales/Sheets/InvoiceTotals.php | 19 ++- .../Sales/Sheets/InvoiceTranactions.php | 29 ----- .../Sales/Sheets/InvoiceTransactions.php | 109 ++++++++++++++++++ app/Imports/Sales/Sheets/Invoices.php | 45 +++++++- app/Models/Banking/Account.php | 10 ++ app/Models/Common/Contact.php | 5 + app/Models/Common/Item.php | 9 +- app/Models/Sale/Invoice.php | 5 + app/Models/Sale/InvoiceItem.php | 2 +- app/Models/Sale/InvoiceItemTax.php | 11 +- app/Models/Setting/Category.php | 5 + app/Models/Setting/Tax.php | 10 ++ composer.json | 1 + composer.lock | 60 ++++++++-- public/files/import/invoices.xlsx | Bin 13893 -> 14077 bytes 27 files changed, 497 insertions(+), 76 deletions(-) create mode 100644 app/Http/Requests/Sale/InvoiceItemTax.php delete mode 100644 app/Imports/Sales/Sheets/InvoiceTranactions.php create mode 100644 app/Imports/Sales/Sheets/InvoiceTransactions.php diff --git a/app/Abstracts/Import.php b/app/Abstracts/Import.php index 6a67407dd..ebd1fe995 100644 --- a/app/Abstracts/Import.php +++ b/app/Abstracts/Import.php @@ -4,7 +4,10 @@ namespace App\Abstracts; use Illuminate\Support\Str; use Jenssegers\Date\Date; +use Maatwebsite\Excel\Concerns\Importable; use Maatwebsite\Excel\Concerns\ToModel; +use Maatwebsite\Excel\Concerns\SkipsOnError; +use Maatwebsite\Excel\Concerns\SkipsOnFailure; use Maatwebsite\Excel\Concerns\WithBatchInserts; use Maatwebsite\Excel\Concerns\WithChunkReading; use Maatwebsite\Excel\Concerns\WithHeadingRow; @@ -12,8 +15,12 @@ use Maatwebsite\Excel\Concerns\WithMapping; use Maatwebsite\Excel\Concerns\WithValidation; use Maatwebsite\Excel\Validators\Failure; -abstract class Import implements ToModel, WithBatchInserts, WithChunkReading, WithHeadingRow, WithMapping, WithValidation +abstract class Import implements ToModel, SkipsOnError, SkipsOnFailure, WithBatchInserts, WithChunkReading, WithHeadingRow, WithMapping, WithValidation { + use Importable; + + public $empty_field = 'empty---'; + public function map($row): array { $row['company_id'] = session('company_id'); @@ -60,13 +67,23 @@ abstract class Import implements ToModel, WithBatchInserts, WithChunkReading, Wi $sheet = Str::snake((new \ReflectionClass($this))->getShortName()); foreach ($failures as $failure) { + // @todo remove after 3.2 release https://github.com/Maatwebsite/Laravel-Excel/issues/1834#issuecomment-474340743 + if (collect($failure->values())->first() == $this->empty_field) { + continue; + } + $message = trans('messages.error.import_column', [ - 'message' => $failure->errors()->first(), + 'message' => collect($failure->errors())->first(), 'sheet' => $sheet, - 'line' => $failure->attribute(), + 'line' => $failure->row(), ]); flash($message)->error()->important(); } } + + public function onError(\Throwable $e) + { + flash($e->getMessage())->error()->important(); + } } diff --git a/app/Exports/Sales/Sheets/InvoiceHistories.php b/app/Exports/Sales/Sheets/InvoiceHistories.php index 7acdc0475..c31082d2e 100644 --- a/app/Exports/Sales/Sheets/InvoiceHistories.php +++ b/app/Exports/Sales/Sheets/InvoiceHistories.php @@ -9,7 +9,7 @@ class InvoiceHistories extends Export { public function collection() { - $model = Model::usingSearchString(request('search')); + $model = Model::with(['invoice'])->usingSearchString(request('search')); if (!empty($this->ids)) { $model->whereIn('invoice_id', (array) $this->ids); @@ -18,10 +18,17 @@ class InvoiceHistories extends Export return $model->get(); } + public function map($model): array + { + $model->invoice_number = $model->invoice->invoice_number; + + return parent::map($model); + } + public function fields(): array { return [ - 'invoice_id', + 'invoice_number', 'status', 'notify', 'description', diff --git a/app/Exports/Sales/Sheets/InvoiceItemTaxes.php b/app/Exports/Sales/Sheets/InvoiceItemTaxes.php index e5ff48592..fac9553d7 100644 --- a/app/Exports/Sales/Sheets/InvoiceItemTaxes.php +++ b/app/Exports/Sales/Sheets/InvoiceItemTaxes.php @@ -9,7 +9,7 @@ class InvoiceItemTaxes extends Export { public function collection() { - $model = Model::usingSearchString(request('search')); + $model = Model::with(['invoice', 'item', 'tax'])->usingSearchString(request('search')); if (!empty($this->ids)) { $model->whereIn('invoice_id', (array) $this->ids); @@ -18,13 +18,21 @@ class InvoiceItemTaxes extends Export return $model->get(); } + public function map($model): array + { + $model->invoice_number = $model->invoice->invoice_number; + $model->item_name = $model->item->name; + $model->tax_rate = $model->tax->rate; + + return parent::map($model); + } + public function fields(): array { return [ - 'invoice_id', - 'invoice_item_id', - 'tax_id', - 'name', + 'invoice_number', + 'item_name', + 'tax_rate', 'amount', ]; } diff --git a/app/Exports/Sales/Sheets/InvoiceItems.php b/app/Exports/Sales/Sheets/InvoiceItems.php index bcbe876e5..5705cb52c 100644 --- a/app/Exports/Sales/Sheets/InvoiceItems.php +++ b/app/Exports/Sales/Sheets/InvoiceItems.php @@ -9,7 +9,7 @@ class InvoiceItems extends Export { public function collection() { - $model = Model::usingSearchString(request('search')); + $model = Model::with(['invoice', 'item'])->usingSearchString(request('search')); if (!empty($this->ids)) { $model->whereIn('invoice_id', (array) $this->ids); @@ -18,12 +18,19 @@ class InvoiceItems extends Export return $model->get(); } + public function map($model): array + { + $model->invoice_number = $model->invoice->invoice_number; + $model->item_name = $model->item->name; + + return parent::map($model); + } + public function fields(): array { return [ - 'invoice_id', - 'item_id', - 'name', + 'invoice_number', + 'item_name', 'quantity', 'price', 'total', diff --git a/app/Exports/Sales/Sheets/InvoiceTotals.php b/app/Exports/Sales/Sheets/InvoiceTotals.php index 461d95517..7c8ac4b43 100644 --- a/app/Exports/Sales/Sheets/InvoiceTotals.php +++ b/app/Exports/Sales/Sheets/InvoiceTotals.php @@ -9,7 +9,7 @@ class InvoiceTotals extends Export { public function collection() { - $model = Model::usingSearchString(request('search')); + $model = Model::with(['invoice'])->usingSearchString(request('search')); if (!empty($this->ids)) { $model->whereIn('invoice_id', (array) $this->ids); @@ -18,10 +18,17 @@ class InvoiceTotals extends Export return $model->get(); } + public function map($model): array + { + $model->invoice_number = $model->invoice->invoice_number; + + return parent::map($model); + } + public function fields(): array { return [ - 'invoice_id', + 'invoice_number', 'code', 'name', 'amount', diff --git a/app/Exports/Sales/Sheets/InvoiceTransactions.php b/app/Exports/Sales/Sheets/InvoiceTransactions.php index 7a3beb61c..b4d7f660d 100644 --- a/app/Exports/Sales/Sheets/InvoiceTransactions.php +++ b/app/Exports/Sales/Sheets/InvoiceTransactions.php @@ -9,7 +9,7 @@ class InvoiceTransactions extends Export { public function collection() { - $model = Model::type('income')->isDocument()->usingSearchString(request('search')); + $model = Model::with(['account', 'category', 'contact', 'invoice'])->type('income')->isDocument()->usingSearchString(request('search')); if (!empty($this->ids)) { $model->whereIn('document_id', (array) $this->ids); @@ -18,17 +18,27 @@ class InvoiceTransactions extends Export return $model->get(); } + public function map($model): array + { + $model->invoice_number = $model->invoice->invoice_number; + $model->account_name = $model->account->name; + $model->category_name = $model->category->name; + $model->contact_email = $model->contact->email; + + return parent::map($model); + } + public function fields(): array { return [ + 'invoice_number', 'paid_at', 'amount', 'currency_code', 'currency_rate', - 'account_id', - 'document_id', - 'contact_id', - 'category_id', + 'account_name', + 'contact_email', + 'category_name', 'description', 'payment_method', 'reference', diff --git a/app/Exports/Sales/Sheets/Invoices.php b/app/Exports/Sales/Sheets/Invoices.php index de56cebde..653611742 100644 --- a/app/Exports/Sales/Sheets/Invoices.php +++ b/app/Exports/Sales/Sheets/Invoices.php @@ -9,7 +9,7 @@ class Invoices extends Export { public function collection() { - $model = Model::usingSearchString(request('search')); + $model = Model::with(['category'])->usingSearchString(request('search')); if (!empty($this->ids)) { $model->whereIn('id', (array) $this->ids); @@ -18,6 +18,13 @@ class Invoices extends Export return $model->get(); } + public function map($model): array + { + $model->category_name = $model->category->name; + + return parent::map($model); + } + public function fields(): array { return [ @@ -29,8 +36,7 @@ class Invoices extends Export 'amount', 'currency_code', 'currency_rate', - 'category_id', - 'contact_id', + 'category_name', 'contact_name', 'contact_email', 'contact_tax_number', diff --git a/app/Http/Requests/Sale/InvoiceItemTax.php b/app/Http/Requests/Sale/InvoiceItemTax.php new file mode 100644 index 000000000..b072fabf8 --- /dev/null +++ b/app/Http/Requests/Sale/InvoiceItemTax.php @@ -0,0 +1,34 @@ + 'required|integer', + 'invoice_item_id' => 'required|integer', + 'tax_id' => 'required|integer', + 'name' => 'required|string', + 'amount' => 'required', + ]; + } +} diff --git a/app/Imports/Sales/Invoices.php b/app/Imports/Sales/Invoices.php index 6edd87328..ad99d8689 100644 --- a/app/Imports/Sales/Invoices.php +++ b/app/Imports/Sales/Invoices.php @@ -7,7 +7,7 @@ use App\Imports\Sales\Sheets\InvoiceItems; use App\Imports\Sales\Sheets\InvoiceItemTaxes; use App\Imports\Sales\Sheets\InvoiceHistories; use App\Imports\Sales\Sheets\InvoiceTotals; -use App\Imports\Sales\Sheets\InvoiceTranactions; +use App\Imports\Sales\Sheets\InvoiceTransactions; use Maatwebsite\Excel\Concerns\WithMultipleSheets; class Invoices implements WithMultipleSheets @@ -20,7 +20,7 @@ class Invoices implements WithMultipleSheets 'invoice_item_taxes' => new InvoiceItemTaxes(), 'invoice_histories' => new InvoiceHistories(), 'invoice_totals' => new InvoiceTotals(), - 'invoice_transactions' => new InvoiceTranactions(), + 'invoice_transactions' => new InvoiceTransactions(), ]; } } diff --git a/app/Imports/Sales/Sheets/InvoiceHistories.php b/app/Imports/Sales/Sheets/InvoiceHistories.php index 24848d997..f4b09d503 100644 --- a/app/Imports/Sales/Sheets/InvoiceHistories.php +++ b/app/Imports/Sales/Sheets/InvoiceHistories.php @@ -3,8 +3,9 @@ namespace App\Imports\Sales\Sheets; use App\Abstracts\Import; -use App\Models\Sale\InvoiceHistory as Model; use App\Http\Requests\Sale\InvoiceHistory as Request; +use App\Models\Sale\Invoice; +use App\Models\Sale\InvoiceHistory as Model; class InvoiceHistories extends Import { @@ -17,6 +18,8 @@ class InvoiceHistories extends Import { $row = parent::map($row); + $row['invoice_id'] = Invoice::number($row['invoice_number'])->pluck('id')->first(); + $row['notify'] = (int) $row['notify']; return $row; @@ -24,6 +27,11 @@ class InvoiceHistories extends Import public function rules(): array { - return (new Request())->rules(); + $rules = (new Request())->rules(); + + $rules['invoice_number'] = 'required|string'; + unset($rules['invoice_id']); + + return $rules; } } diff --git a/app/Imports/Sales/Sheets/InvoiceItemTaxes.php b/app/Imports/Sales/Sheets/InvoiceItemTaxes.php index f6100e4ab..0dbf13c2c 100644 --- a/app/Imports/Sales/Sheets/InvoiceItemTaxes.php +++ b/app/Imports/Sales/Sheets/InvoiceItemTaxes.php @@ -3,12 +3,69 @@ namespace App\Imports\Sales\Sheets; use App\Abstracts\Import; +use App\Http\Requests\Sale\InvoiceItemTax as Request; +use App\Models\Common\Item; +use App\Models\Sale\Invoice; +use App\Models\Sale\InvoiceItem; use App\Models\Sale\InvoiceItemTax as Model; +use App\Models\Setting\Tax; class InvoiceItemTaxes extends Import { public function model(array $row) { + // @todo remove after 3.2 release + if ($row['invoice_number'] == $this->empty_field) { + return null; + } + + $row['invoice_id'] = Invoice::number($row['invoice_number'])->pluck('id')->first(); + return new Model($row); } + + public function map($row): array + { + $row = parent::map($row); + + $row['invoice_id'] = Invoice::number($row['invoice_number'])->pluck('id')->first(); + + if (empty($row['invoice_item_id']) && !empty($row['item_name'])) { + $item_id = Item::name($row['item_name'])->pluck('id')->first(); + $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'] = Tax::firstOrCreate([ + 'rate' => $row['tax_rate'], + ], [ + 'company_id' => session('company_id'), + 'type' => 'normal', + 'name' => $row['tax_rate'], + 'enabled' => 1, + ])->id; + } + + if (empty($row['name']) && !empty($row['item_name'])) { + $row['name'] = $row['item_name']; + } + + $row['amount'] = (double) $row['amount']; + + return $row; + } + + public function rules(): array + { + $rules = (new Request())->rules(); + + $rules['invoice_number'] = 'required|string'; + unset($rules['invoice_id']); + + return $rules; + } } diff --git a/app/Imports/Sales/Sheets/InvoiceItems.php b/app/Imports/Sales/Sheets/InvoiceItems.php index 0f5ebf726..8872923b9 100644 --- a/app/Imports/Sales/Sheets/InvoiceItems.php +++ b/app/Imports/Sales/Sheets/InvoiceItems.php @@ -3,8 +3,10 @@ namespace App\Imports\Sales\Sheets; use App\Abstracts\Import; -use App\Models\Sale\InvoiceItem as Model; use App\Http\Requests\Sale\InvoiceItem as Request; +use App\Models\Common\Item; +use App\Models\Sale\Invoice; +use App\Models\Sale\InvoiceItem as Model; class InvoiceItems extends Import { @@ -13,8 +15,38 @@ class InvoiceItems extends Import return new Model($row); } + public function map($row): array + { + $row = parent::map($row); + + $row['invoice_id'] = Invoice::number($row['invoice_number'])->pluck('id')->first(); + + if (empty($row['item_id']) && !empty($row['item_name'])) { + $row['item_id'] = Item::firstOrCreate([ + 'name' => $row['item_name'], + ], [ + 'company_id' => session('company_id'), + 'sale_price' => $row['price'], + 'purchase_price' => $row['price'], + 'enabled' => 1, + ])->id; + + $row['name'] = $row['item_name']; + } + + $row['tax'] = (double) $row['tax']; + $row['tax_id'] = 0; + + return $row; + } + public function rules(): array { - return (new Request())->rules(); + $rules = (new Request())->rules(); + + $rules['invoice_number'] = 'required|string'; + unset($rules['invoice_id']); + + return $rules; } } diff --git a/app/Imports/Sales/Sheets/InvoiceTotals.php b/app/Imports/Sales/Sheets/InvoiceTotals.php index d36c0d1d4..3d8606497 100644 --- a/app/Imports/Sales/Sheets/InvoiceTotals.php +++ b/app/Imports/Sales/Sheets/InvoiceTotals.php @@ -3,8 +3,9 @@ namespace App\Imports\Sales\Sheets; use App\Abstracts\Import; -use App\Models\Sale\InvoiceTotal as Model; use App\Http\Requests\Sale\InvoiceTotal as Request; +use App\Models\Sale\Invoice; +use App\Models\Sale\InvoiceTotal as Model; class InvoiceTotals extends Import { @@ -13,8 +14,22 @@ class InvoiceTotals extends Import return new Model($row); } + public function map($row): array + { + $row = parent::map($row); + + $row['invoice_id'] = Invoice::number($row['invoice_number'])->pluck('id')->first(); + + return $row; + } + public function rules(): array { - return (new Request())->rules(); + $rules = (new Request())->rules(); + + $rules['invoice_number'] = 'required|string'; + unset($rules['invoice_id']); + + return $rules; } } diff --git a/app/Imports/Sales/Sheets/InvoiceTranactions.php b/app/Imports/Sales/Sheets/InvoiceTranactions.php deleted file mode 100644 index 0618d3f0f..000000000 --- a/app/Imports/Sales/Sheets/InvoiceTranactions.php +++ /dev/null @@ -1,29 +0,0 @@ -rules(); - } -} diff --git a/app/Imports/Sales/Sheets/InvoiceTransactions.php b/app/Imports/Sales/Sheets/InvoiceTransactions.php new file mode 100644 index 000000000..fd9fb41e9 --- /dev/null +++ b/app/Imports/Sales/Sheets/InvoiceTransactions.php @@ -0,0 +1,109 @@ + $row['account_name'], + ], [ + 'company_id' => session('company_id'), + 'number' => Account::max('number') + 1, + 'currency_code' => setting('default.currency'), + 'opening_balance' => 0, + 'enabled' => 1, + ])->id; + } + + if (empty($row['account_id']) && !empty($row['account_number'])) { + $row['account_id'] = 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; + } + + if (empty($row['account_id']) && !empty($row['currency_code'])) { + $row['account_id'] = 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; + } + + if (empty($row['contact_id']) && !empty($row['contact_name'])) { + $row['contact_id'] = Contact::firstOrCreate([ + 'name' => $row['contact_name'], + ], [ + 'company_id' => session('company_id'), + 'type' => 'customer', + 'currency_code' => setting('default.currency'), + 'enabled' => 1, + ])->id; + } + + if (empty($row['contact_id']) && !empty($row['contact_email'])) { + $row['contact_id'] = Contact::firstOrCreate([ + 'email' => $row['contact_email'], + ], [ + 'company_id' => session('company_id'), + 'type' => 'customer', + 'name' => $row['contact_email'], + 'currency_code' => setting('default.currency'), + 'enabled' => 1, + ])->id; + } + + if (empty($row['category_id']) && !empty($row['category_name'])) { + $row['category_id'] = Category::firstOrCreate([ + 'name' => $row['category_name'], + ], [ + 'company_id' => session('company_id'), + 'type' => 'income', + 'color' => '#' . dechex(rand(0x000000, 0xFFFFFF)), + 'enabled' => 1, + ])->id; + } + + $row['document_id'] = Invoice::number($row['invoice_number'])->pluck('id')->first(); + + return $row; + } + + public function rules(): array + { + $rules = (new Request())->rules(); + + $rules['invoice_number'] = 'required|string'; + + return $rules; + } +} diff --git a/app/Imports/Sales/Sheets/Invoices.php b/app/Imports/Sales/Sheets/Invoices.php index dd5e0b75d..58bbc812b 100644 --- a/app/Imports/Sales/Sheets/Invoices.php +++ b/app/Imports/Sales/Sheets/Invoices.php @@ -3,8 +3,10 @@ namespace App\Imports\Sales\Sheets; use App\Abstracts\Import; -use App\Models\Sale\Invoice as Model; use App\Http\Requests\Sale\Invoice as Request; +use App\Models\Common\Contact; +use App\Models\Sale\Invoice as Model; +use App\Models\Setting\Category; class Invoices extends Import { @@ -13,6 +15,47 @@ class Invoices extends Import return new Model($row); } + public function map($row): array + { + $row = parent::map($row); + + if (empty($row['contact_id']) && !empty($row['contact_name'])) { + $row['contact_id'] = Contact::firstOrCreate([ + 'name' => $row['contact_name'], + ], [ + 'company_id' => session('company_id'), + 'type' => 'customer', + 'currency_code' => setting('default.currency'), + 'enabled' => 1, + ])->id; + } + + if (empty($row['contact_id']) && !empty($row['contact_email'])) { + $row['contact_id'] = Contact::firstOrCreate([ + 'email' => $row['contact_email'], + ], [ + 'company_id' => session('company_id'), + 'type' => 'customer', + 'name' => $row['contact_email'], + 'currency_code' => setting('default.currency'), + 'enabled' => 1, + ])->id; + } + + if (empty($row['category_id']) && !empty($row['category_name'])) { + $row['category_id'] = Category::firstOrCreate([ + 'name' => $row['category_name'], + ], [ + 'company_id' => session('company_id'), + 'type' => 'income', + 'color' => '#' . dechex(rand(0x000000, 0xFFFFFF)), + 'enabled' => 1, + ])->id; + } + + return $row; + } + public function rules(): array { return (new Request())->rules(); diff --git a/app/Models/Banking/Account.php b/app/Models/Banking/Account.php index 094b073b1..72a9211be 100644 --- a/app/Models/Banking/Account.php +++ b/app/Models/Banking/Account.php @@ -49,6 +49,16 @@ class Account extends Model return $this->hasMany('App\Models\Banking\Transaction'); } + public function scopeName($query, $name) + { + return $query->where('name', '=', $name); + } + + public function scopeNumber($query, $number) + { + return $query->where('number', '=', $number); + } + /** * Convert opening balance to double. * diff --git a/app/Models/Common/Contact.php b/app/Models/Common/Contact.php index d54b54c71..f89527060 100644 --- a/app/Models/Common/Contact.php +++ b/app/Models/Common/Contact.php @@ -79,6 +79,11 @@ class Contact extends Model return $query->whereIn('type', (array) $types); } + public function scopeEmail($query, $email) + { + return $query->where('email', '=', $email); + } + public function onCloning($src, $child = null) { $this->user_id = null; diff --git a/app/Models/Common/Item.php b/app/Models/Common/Item.php index 7e824a545..ea551dfc0 100644 --- a/app/Models/Common/Item.php +++ b/app/Models/Common/Item.php @@ -36,12 +36,12 @@ class Item extends Model public function category() { - return $this->belongsTo('App\Models\Setting\Category'); + return $this->belongsTo('App\Models\Setting\Category')->withDefault(['name' => trans('general.na')]); } public function tax() { - return $this->belongsTo('App\Models\Setting\Tax'); + return $this->belongsTo('App\Models\Setting\Tax')->withDefault(['name' => trans('general.na')]); } public function bill_items() @@ -54,6 +54,11 @@ class Item extends Model return $this->hasMany('App\Models\Sale\InvoiceItem'); } + public function scopeName($query, $name) + { + return $query->where('name', '=', $name); + } + /** * Convert sale price to double. * diff --git a/app/Models/Sale/Invoice.php b/app/Models/Sale/Invoice.php index 3540d6ab2..ddd752560 100644 --- a/app/Models/Sale/Invoice.php +++ b/app/Models/Sale/Invoice.php @@ -125,6 +125,11 @@ class Invoice extends Model return $query->where('status', '<>', 'paid'); } + public function scopeNumber($query, $number) + { + return $query->where('invoice_number', '=', $number); + } + public function onCloning($src, $child = null) { $this->status = 'draft'; diff --git a/app/Models/Sale/InvoiceItem.php b/app/Models/Sale/InvoiceItem.php index 3e50bab2a..4fee1a529 100644 --- a/app/Models/Sale/InvoiceItem.php +++ b/app/Models/Sale/InvoiceItem.php @@ -34,7 +34,7 @@ class InvoiceItem extends Model public function item() { - return $this->belongsTo('App\Models\Common\Item'); + return $this->belongsTo('App\Models\Common\Item')->withDefault(['name' => trans('general.na')]); } public function taxes() diff --git a/app/Models/Sale/InvoiceItemTax.php b/app/Models/Sale/InvoiceItemTax.php index f21a58136..b11af4539 100644 --- a/app/Models/Sale/InvoiceItemTax.php +++ b/app/Models/Sale/InvoiceItemTax.php @@ -4,11 +4,11 @@ namespace App\Models\Sale; use App\Abstracts\Model; use App\Traits\Currencies; +use Znck\Eloquent\Traits\BelongsToThrough; class InvoiceItemTax extends Model { - - use Currencies; + use Currencies, BelongsToThrough; protected $table = 'invoice_item_taxes'; @@ -24,9 +24,14 @@ class InvoiceItemTax extends Model return $this->belongsTo('App\Models\Sale\Invoice'); } + public function item() + { + return $this->belongsToThrough('App\Models\Common\Item', 'App\Models\Sale\InvoiceItem', 'invoice_item_id')->withDefault(['name' => trans('general.na')]); + } + public function tax() { - return $this->belongsTo('App\Models\Setting\Tax'); + return $this->belongsTo('App\Models\Setting\Tax')->withDefault(['name' => trans('general.na')]); } /** diff --git a/app/Models/Setting/Category.php b/app/Models/Setting/Category.php index 6dcf184bf..3c8341222 100644 --- a/app/Models/Setting/Category.php +++ b/app/Models/Setting/Category.php @@ -68,6 +68,11 @@ class Category extends Model return $query->whereIn('type', (array) $types); } + public function scopeName($query, $name) + { + return $query->where('name', '=', $name); + } + /** * Scope transfer category. * diff --git a/app/Models/Setting/Tax.php b/app/Models/Setting/Tax.php index 39a26cb95..e359ef18e 100644 --- a/app/Models/Setting/Tax.php +++ b/app/Models/Setting/Tax.php @@ -44,6 +44,16 @@ class Tax extends Model return $this->hasMany('App\Models\Sale\InvoiceItemTax'); } + public function scopeName($query, $name) + { + return $query->where('name', '=', $name); + } + + public function scopeRate($query, $rate) + { + return $query->where('rate', '=', $rate); + } + /** * Convert rate to double. * diff --git a/composer.json b/composer.json index ffafdd888..7b3ab6487 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,7 @@ "plank/laravel-mediable": "3.0.*", "santigarcor/laratrust": "5.2.*", "simshaun/recurr": "4.0.*", + "staudenmeir/belongs-to-through": "2.9", "staudenmeir/eloquent-has-many-deep": "1.11" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 911b7b2dd..011b67421 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "da7fd36fd1e0ebe5d9534e3db6ce40d5", + "content-hash": "f3cd099f0849563142edd289998d8bd9", "packages": [ { "name": "akaunting/firewall", @@ -5477,6 +5477,50 @@ ], "time": "2016-07-28T15:35:24+00:00" }, + { + "name": "staudenmeir/belongs-to-through", + "version": "v2.9", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/belongs-to-through.git", + "reference": "8f16bb7b51d081d90d9b093ba6f380f71a96d79f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/8f16bb7b51d081d90d9b093ba6f380f71a96d79f", + "reference": "8f16bb7b51d081d90d9b093ba6f380f71a96d79f", + "shasum": "" + }, + "require": { + "illuminate/database": "^6.0", + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Znck\\Eloquent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rahul Kadyan", + "email": "hi@znck.me" + }, + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" + } + ], + "description": "Laravel Eloquent BelongsToThrough relationship", + "time": "2019-12-29T10:58:12+00:00" + }, { "name": "staudenmeir/eloquent-has-many-deep", "version": "v1.11", @@ -7882,24 +7926,24 @@ }, { "name": "phpspec/prophecy", - "version": "1.10.1", + "version": "v1.10.2", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc" + "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/cbe1df668b3fe136bcc909126a0f529a78d4cbbc", - "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9", + "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.2.3|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" }, "require-dev": { "phpspec/phpspec": "^2.5 || ^3.2", @@ -7941,7 +7985,7 @@ "spy", "stub" ], - "time": "2019-12-22T21:05:45+00:00" + "time": "2020-01-20T15:57:02+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/public/files/import/invoices.xlsx b/public/files/import/invoices.xlsx index d53546af08ec6f6f1f40695c5b6c6796795d458a..fb49975f022abf58027cd631f23c9e60f847ea1a 100644 GIT binary patch literal 14077 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 CeXF1V literal 13893 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

yrt;OTGuv#8DmdNEyRV#I>f9X%A}Z@z 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