diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index a375fd8856d9..66f244b2a2ff 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -78,7 +78,7 @@ class BaseImport ->setCompany($this->company); } - protected function getCsvData($entity_type) + public function getCsvData($entity_type) { $base64_encoded_csv = Cache::pull($this->hash . '-' . $entity_type); if (empty($base64_encoded_csv)) { @@ -218,7 +218,7 @@ class BaseImport $invoices = $this->groupInvoices($invoices, $invoice_number_key); foreach ($invoices as $raw_invoice) { - nlog($raw_invoice); + try { $invoice_data = $invoice_transformer->transform($raw_invoice); nlog($invoice_data); diff --git a/app/Import/Providers/Wave.php b/app/Import/Providers/Wave.php index 3da28843ebd0..7e12ff74e064 100644 --- a/app/Import/Providers/Wave.php +++ b/app/Import/Providers/Wave.php @@ -13,18 +13,24 @@ namespace App\Import\Providers; use App\Factory\ClientFactory; +use App\Factory\ExpenseFactory; use App\Factory\InvoiceFactory; use App\Factory\VendorFactory; use App\Http\Requests\Client\StoreClientRequest; +use App\Http\Requests\Expense\StoreExpenseRequest; use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Http\Requests\Vendor\StoreVendorRequest; +use App\Import\ImportException; use App\Import\Transformer\Wave\ClientTransformer; +use App\Import\Transformer\Wave\ExpenseTransformer; use App\Import\Transformer\Wave\InvoiceTransformer; use App\Import\Transformer\Wave\VendorTransformer; use App\Models\Client; use App\Repositories\ClientRepository; +use App\Repositories\ExpenseRepository; use App\Repositories\InvoiceRepository; use App\Repositories\VendorRepository; +use Illuminate\Support\Facades\Validator; class Wave extends BaseImport implements ImportInterface { @@ -40,7 +46,7 @@ class Wave extends BaseImport implements ImportInterface // 'product', // 'payment', 'vendor', - // 'expense', + 'expense', ]) ) { $this->{$entity}(); @@ -134,9 +140,8 @@ class Wave extends BaseImport implements ImportInterface $entity_type = 'vendor'; $data = $this->getCsvData($entity_type); -nlog($data); $data = $this->preTransform($data, $entity_type); -nlog($data); + if (empty($data)) { $this->entity_count['vendors'] = 0; return; @@ -157,7 +162,116 @@ nlog($data); } - public function expense() {} + public function expense() + { + $entity_type = 'expense'; + + $data = $this->getCsvData($entity_type); + $data = $this->preTransform($data, $entity_type); + + if (empty($data)) { + $this->entity_count['expense'] = 0; + return; + } + + $this->request_name = StoreExpenseRequest::class; + $this->repository_name = ExpenseRepository::class; + $this->factory_name = ExpenseFactory::class; + + $this->repository = app()->make($this->repository_name); + $this->repository->import_mode = true; + + $this->transformer = new ExpenseTransformer($this->company); + + $expense_count = $this->ingestExpenses($data, $entity_type); + + $this->entity_count['expenses'] = $expense_count; + + } public function transform(array $data){} + + + private function groupExpenses($csvData) + { + + $grouped_expense = []; + $key = 'Transaction ID'; + + foreach($csvData as $expense) + { + if($expense['Account Group'] == 'Expense') + $grouped[$expense[$key]][] = $expense; + } + + return $grouped; + } + + public function ingestExpenses($data) + { + $key = 'Transaction ID'; + + $expense_transformer = $this->transformer; + + $vendor_repository = app()->make(VendorRepository::class); + $expense_repository = app()->make(ExpenseRepository::class); + + $expenses = $this->groupExpenses($data); + +// nlog($expenses); +// exit; + foreach ($expenses as $raw_expense) { + + try { + + $expense_data = $expense_transformer->transform($raw_expense); + + // If we don't have a client ID, but we do have client data, go ahead and create the client. + if (empty($expense_data['vendor_id']) ) { + $vendor_data['user_id'] = $this->getUserIDForRecord($expense_data); + + $vendor_repository->save( + ['name' => $raw_expense['Vendor Name']], + $vendor = VendorFactory::create( + $this->company->id, + $vendor_data['user_id'] + ) + ); + $expense_data['vendor_id'] = $vendor->id; + } + + $validator = Validator::make( + $expense_data, + (new StoreExpenseRequest())->rules() + ); + if ($validator->fails()) { + $this->error_array['expense'][] = [ + 'expense' => $expense_data, + 'error' => $validator->errors()->all(), + ]; + } else { + $expense = ExpenseFactory::create( + $this->company->id, + $this->getUserIDForRecord($expense_data) + ); + + $expense_repository->save($expense_data, $expense); + + } + } catch (\Exception $ex) { + if ($ex instanceof ImportException) { + $message = $ex->getMessage(); + } else { + report($ex); + $message = 'Unknown error'; + } + + $this->error_array['expense'][] = [ + 'expense' => $raw_expense, + 'error' => $message, + ]; + } + } + } + } diff --git a/app/Import/Transformer/BaseTransformer.php b/app/Import/Transformer/BaseTransformer.php index ec4e6b01a27c..179c991fa85b 100644 --- a/app/Import/Transformer/BaseTransformer.php +++ b/app/Import/Transformer/BaseTransformer.php @@ -11,7 +11,9 @@ namespace App\Import\Transformer; +use App\Factory\ExpenseCategoryFactory; use App\Factory\ProjectFactory; +use App\Factory\VendorFactory; use App\Models\ClientContact; use App\Models\Country; use App\Models\ExpenseCategory; @@ -369,6 +371,19 @@ class BaseTransformer ->exists(); } + /** * + * @return bool + */ + public function hasExpense($expense_number) + { + return $this->company + ->expenses() + ->whereRaw("LOWER(REPLACE(`number`, ' ' ,'')) = ?", [ + strtolower(str_replace(' ', '', $expense_number)), + ]) + ->exists(); + } + /** * @param $quote_number * @@ -418,6 +433,23 @@ class BaseTransformer return $vendor ? $vendor->id : null; } + public function getVendorIdOrCreate($name) + { + if(empty($name)) + return null; + + $vendor = $this->getVendorId($name); + + if($vendor) + return $vendor; + + $vendor = VendorFactory::create($this->company->id, $this->company->owner()->id); + $vendor->name = $name; + $vendor->save(); + + return $vendor->id; + } + /** * @param $name * @@ -437,12 +469,15 @@ class BaseTransformer public function getOrCreateExpenseCategry($name) { + if(empty($name)) + return null; + $ec = $this->getExpenseCategoryId($name); if($ec) return $ec; - $expense_category = ExpenseCategory::create($this->company->id, $this->company->owner()->id); + $expense_category = ExpenseCategoryFactory::create($this->company->id, $this->company->owner()->id); $expense_category->name = $name; $expense_category->save(); diff --git a/app/Import/Transformer/Wave/ExpenseTransformer.php b/app/Import/Transformer/Wave/ExpenseTransformer.php index f8d5b5ad31e2..f5431b90c50a 100644 --- a/app/Import/Transformer/Wave/ExpenseTransformer.php +++ b/app/Import/Transformer/Wave/ExpenseTransformer.php @@ -9,7 +9,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Import\Transformer\Waveaccounting; +namespace App\Import\Transformer\Wave; use App\Import\ImportException; use App\Import\Transformer\BaseTransformer; @@ -24,22 +24,43 @@ class ExpenseTransformer extends BaseTransformer { * * @return bool|array */ - public function transform( $data ) { + public function transform( $line_items_data ) + { + + $data = $line_items_data[0]; + + $amount = 0; + $total_tax = 0; + $tax_rate = 0; + + + foreach ( $line_items_data as $record ) + { + + $amount += floatval($record['Amount (One column)']); + $total_tax += floatval($record['Sales Tax Amount']); + + } + + + + $tax_rate = round(($total_tax/$amount)*100,3); $transformed = [ 'company_id' => $this->company->id, - 'vendor_id' => $this->getVendorId($vendor_name = $this->getString($data, 'vendor')), - 'number' => $this->getString($data, 'invoice_number'), - 'public_notes'=> $this->getString($data, 'description'), - 'date' => date( 'Y-m-d', strtotime( $data['bill_date'] ) ) ?: now()->format('Y-m-d'), //27-01-2022 - 'currency_id' => $this->getCurrencyByCode( $data, 'currency' ), - 'category_id' => $this->getOrCreateExpenseCategry($data['account']), - 'amount' => $this->getFloat($data['quantity']) * $this->getFloat($data['amount']), - 'tax_name1' => $this->getTaxName($data['taxes']), - 'tax_rate1' => $this->getTaxRate($data['taxes']), + 'vendor_id' => $this->getVendorIdOrCreate($this->getString($data, 'Vendor')), + 'number' => $this->getString($data, 'Bill Number'), + 'public_notes'=> $this->getString($data, 'Notes / Memo'), + 'date' => date( 'Y-m-d', strtotime( $data['Transaction Date Added'] ) ) ?: now()->format('Y-m-d'), //27-01-2022 + 'currency_id' => $this->company->settings->currency_id, + 'category_id' => $this->getOrCreateExpenseCategry($data['Account Name']), + 'amount' => $amount, + 'tax_name1' => $data['Sales Tax Name'], + 'tax_rate1' => $tax_rate, ]; - return $transformed; } -} \ No newline at end of file +} + + diff --git a/tests/Feature/Import/Wave/WaveTest.php b/tests/Feature/Import/Wave/WaveTest.php index b474dc472cc2..50fe6122da69 100644 --- a/tests/Feature/Import/Wave/WaveTest.php +++ b/tests/Feature/Import/Wave/WaveTest.php @@ -11,6 +11,7 @@ namespace Tests\Feature\Import\CSV; +use App\Import\Providers\BaseImport; use App\Import\Providers\Wave; use App\Import\Transformer\BaseTransformer; use App\Models\Client; @@ -48,7 +49,57 @@ class WaveTest extends TestCase $this->withoutExceptionHandling(); } - public function testVendorWaveImport() + // public function testExpenseImport() + // { + + // $csv = file_get_contents( + // base_path() . '/tests/Feature/Import/wave_expenses.csv' + // ); + // $hash = Str::random(32); + + // $column_map = [ + // 0 => 'Transaction ID', + // 1 => 'Transaction Date', + // 2 => 'Account Name', + // 3 => 'Transaction Description', + // 4 => 'Transaction Line Description', + // 5 => 'Amount (One column)', + // 6 => ' ', + // 7 => 'Debit Amount (Two Column Approach)', + // 8 => 'Credit Amount (Two Column Approach)', + // 9 => 'Other Accounts for this Transaction', + // 10 => 'Customer', + // 11 => 'Vendor', + // 12 => 'Invoice Number', + // 13 => 'Bill Number', + // 14 => 'Notes / Memo', + // 15 => 'Amount Before Sales Tax', + // 16 => 'Sales Tax Amount', + // 17 => 'Sales Tax Name', + // 18 => 'Transaction Date Added', + // 19 => 'Transaction Date Last Modified', + // 20 => 'Account Group', + // 21 => 'Account Type', + // 22 => 'Account ID', + // ]; + + // $data = [ + // 'hash' => $hash, + // 'column_map' => ['expense' => ['mapping' => $column_map]], + // 'skip_header' => true, + // 'import_type' => 'waveaccounting', + // ]; + + // Cache::put($hash . '-expense', base64_encode($csv), 360); + + // $csv_importer = new Wave($data, $this->company); + + // $count = $csv_importer->import('expense'); + + + // } + + public function testVendorAndExpenseWaveImport() { $csv = file_get_contents( base_path() . '/tests/Feature/Import/wave_vendors.csv' @@ -108,6 +159,56 @@ class WaveTest extends TestCase $this->assertEquals('vendor@gmail.com', $vendor->contacts->first()->email); $this->assertEquals('phone', $vendor->contacts->first()->phone); + + // now lets try importing expenses / bills + $csv = file_get_contents( + base_path() . '/tests/Feature/Import/wave_expenses.csv' + ); + $hash = Str::random(32); + + $column_map = [ + 0 => 'Transaction ID', + 1 => 'Transaction Date', + 2 => 'Account Name', + 3 => 'Transaction Description', + 4 => 'Transaction Line Description', + 5 => 'Amount (One column)', + 6 => ' ', + 7 => 'Debit Amount (Two Column Approach)', + 8 => 'Credit Amount (Two Column Approach)', + 9 => 'Other Accounts for this Transaction', + 10 => 'Customer', + 11 => 'Vendor', + 12 => 'Invoice Number', + 13 => 'Bill Number', + 14 => 'Notes / Memo', + 15 => 'Amount Before Sales Tax', + 16 => 'Sales Tax Amount', + 17 => 'Sales Tax Name', + 18 => 'Transaction Date Added', + 19 => 'Transaction Date Last Modified', + 20 => 'Account Group', + 21 => 'Account Type', + 22 => 'Account ID', + ]; + + $data = [ + 'hash' => $hash, + 'column_map' => ['expense' => ['mapping' => $column_map]], + 'skip_header' => true, + 'import_type' => 'waveaccounting', + ]; + + Cache::put($hash . '-expense', base64_encode($csv), 360); + + $csv_importer = new Wave($data, $this->company); + + $count = $csv_importer->import('expense'); + + $base_transformer = new BaseTransformer($this->company); + + $this->assertTrue($base_transformer->hasExpense("66")); + } diff --git a/tests/Feature/Import/wave_expenses.csv b/tests/Feature/Import/wave_expenses.csv new file mode 100644 index 000000000000..4a5a6426060f --- /dev/null +++ b/tests/Feature/Import/wave_expenses.csv @@ -0,0 +1,26 @@ +Transaction ID,Transaction Date,Account Name,Transaction Description,Transaction Line Description,Amount (One column), ,Debit Amount (Two Column Approach),Credit Amount (Two Column Approach),Other Accounts for this Transaction,Customer,Vendor,Invoice Number,Bill Number,Notes / Memo,Amount Before Sales Tax,Sales Tax Amount,Sales Tax Name,Transaction Date Added,Transaction Date Last Modified,Account Group,Account Type,Account ID +537537669383611013,2015-12-30,Owner Investment / Drawings,-hjj,-hjj,96.00,,,96.00,Computer – Hardware,,,,,,96.00,,,2015-12-30,2022-02-10,Equity,Business Owner Contribution and Drawing, +537537669383611013,2015-12-30,Computer – Hardware,-hjj,-hjj,96.00,,96.00,,Owner Investment / Drawings,,,,,,96.00,,,2015-12-30,2022-02-10,Expense,Operating Expense, +537537671069721225,2016-02-20,Cash on Hand,-,-,-20.00,,,20.00,Business Licenses & Permits,,,,,,-20.00,,,2016-02-21,2022-02-10,Asset,Cash and Bank, +537537671069721225,2016-02-20,Business Licenses & Permits,-,-,20.00,,20.00,,Cash on Hand,,,,,,20.00,,,2016-02-21,2022-02-10,Expense,Operating Expense, +1405423775985459539,2022-02-10,Sales,asa - 2,asa - 2 - bananas,10.00,,,10.00,"Accounts Receivable, Capitation Fees, GST",asa,,2,,,10.00,,,2022-02-10,2022-02-10,Income,Income, +1405423775985459539,2022-02-10,Sales,asa - 2,asa - 2 - bananas,10.00,,,10.00,"Accounts Receivable, Capitation Fees, GST",asa,,2,,,10.00,,,2022-02-10,2022-02-10,Income,Income, +1405423775985459539,2022-02-10,Capitation Fees,asa - 2,asa - 2 - Best Product Name,10.00,,,10.00,"Accounts Receivable, GST, Sales",asa,,2,,,10.00,1.00,GST,2022-02-10,2022-02-10,Income,Income, +1405423775985459539,2022-02-10,GST,asa - 2,asa - 2 - Best Production Description,1.00,,,1.00,"Accounts Receivable, Capitation Fees, Sales",asa,,2,,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405423775985459539,2022-02-10,Capitation Fees,asa - 2,asa - 2 - Best Product Name,10.00,,,10.00,"Accounts Receivable, GST, Sales",asa,,2,,,10.00,1.00,GST,2022-02-10,2022-02-10,Income,Income, +1405423775985459539,2022-02-10,GST,asa - 2,asa - 2 - Best Production Description,1.00,,,1.00,"Accounts Receivable, Capitation Fees, Sales",asa,,2,,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405423775985459539,2022-02-10,Accounts Receivable,asa - 2,asa - 2,42.00,,42.00,,"Capitation Fees, GST, Sales",asa,,2,,,42.00,,,2022-02-10,2022-02-10,Asset,System Receivable Invoice, +1405429262361584366,2022-02-10,Computer – Hosting,Vendor Name - Bill ,Vendor Name - Bill - Best Product Name,10.00,,10.00,,"Accounts Payable, GST",,Vendor Name,,,,10.00,1.00,GST,2022-02-10,2022-02-10,Expense,Operating Expense, +1405429262361584366,2022-02-10,GST,Vendor Name - Bill ,Vendor Name - Bill - Best Product Name,-1.00,,1.00,,"Accounts Payable, Computer – Hosting",,Vendor Name,,,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405429262361584366,2022-02-10,Accounts Payable,Vendor Name - Bill ,Vendor Name - Bill ,11.00,,,11.00,"Computer – Hosting, GST",,Vendor Name,,,,11.00,,,2022-02-10,2022-02-10,Liability,System Payable Bill, +1405437089897538538,2022-02-10,Computer – Hosting,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,200.00,,200.00,,"Accounts Payable, GST",,Vendor Name,,66,,200.00,20.00,GST,2022-02-10,2022-02-10,Expense,Operating Expense, +1405437089897538538,2022-02-10,GST,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,-20.00,,20.00,,"Accounts Payable, Computer – Hosting",,Vendor Name,,66,,20.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405437089897538538,2022-02-10,Computer – Hosting,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,10.00,,10.00,,"Accounts Payable, GST",,Vendor Name,,66,,10.00,1.00,GST,2022-02-10,2022-02-10,Expense,Operating Expense, +1405437089897538538,2022-02-10,GST,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,-1.00,,1.00,,"Accounts Payable, Computer – Hosting",,Vendor Name,,66,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405437089897538538,2022-02-10,Computer – Hosting,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,10.00,,10.00,,"Accounts Payable, GST",,Vendor Name,,66,,10.00,1.00,GST,2022-02-10,2022-02-10,Expense,Operating Expense, +1405437089897538538,2022-02-10,GST,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,-1.00,,1.00,,"Accounts Payable, Computer – Hosting",,Vendor Name,,66,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405437089897538538,2022-02-10,Computer – Hosting,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,10.00,,10.00,,"Accounts Payable, GST",,Vendor Name,,66,,10.00,1.00,GST,2022-02-10,2022-02-10,Expense,Operating Expense, +1405437089897538538,2022-02-10,GST,Vendor Name - Bill 66,Vendor Name - Bill 66 - Best Product Name,-1.00,,1.00,,"Accounts Payable, Computer – Hosting",,Vendor Name,,66,,1.00,,,2022-02-10,2022-02-10,Liability,Sales Tax on Sales and Purchases, +1405437089897538538,2022-02-10,Accounts Payable,Vendor Name - Bill 66,Vendor Name - Bill 66,253.00,,,253.00,"Computer – Hosting, GST",,Vendor Name,,66,,253.00,,,2022-02-10,2022-02-10,Liability,System Payable Bill, +1405441958066807314,2022-02-10,Accounts Payable,Bill Payment,Bill Payment,-253.00,,253.00,,Cash on Hand,,Vendor Name,,66,,-253.00,,,2022-02-10,2022-02-10,Liability,System Payable Bill, +1405441958066807314,2022-02-10,Cash on Hand,Bill Payment,Bill Payment,-253.00,,,253.00,Accounts Payable,,,,,,-253.00,,,2022-02-10,2022-02-10,Asset,Cash and Bank,