diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4748d96356a..ea01c1976046 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,10 +5,19 @@ on: name: Upload Release Asset jobs: + run: + runs-on: ubuntu-18.04 + build: name: Upload Release Asset runs-on: ubuntu-latest steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: mysql, mysqlnd, sqlite3, bcmath, gd, curl, zip, openssl, mbstring, xml + - name: Checkout code uses: actions/checkout@v1 with: diff --git a/app/Helpers/Invoice/InvoiceItemSum.php b/app/Helpers/Invoice/InvoiceItemSum.php index 22b4008b1519..3aa197474147 100644 --- a/app/Helpers/Invoice/InvoiceItemSum.php +++ b/app/Helpers/Invoice/InvoiceItemSum.php @@ -112,6 +112,9 @@ class InvoiceItemSum { $item_tax = 0; +// info(print_r($this->item,1)); +// info(print_r($this->invoice,1)); + $amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount / 100)); $item_tax_rate1_total = $this->calcAmountLineTax($this->item->tax_rate1, $amount); diff --git a/app/Http/Requests/Invoice/StoreInvoiceRequest.php b/app/Http/Requests/Invoice/StoreInvoiceRequest.php index 5f0464d7621e..b7e77346151c 100644 --- a/app/Http/Requests/Invoice/StoreInvoiceRequest.php +++ b/app/Http/Requests/Invoice/StoreInvoiceRequest.php @@ -51,10 +51,13 @@ class StoreInvoiceRequest extends Request $rules['invitations.*.client_contact_id'] = 'distinct'; - if ($this->input('number')) { - $rules['number'] = 'unique:invoices,number,'.$this->id.',id,company_id,'.auth()->user()->company()->id; + // if ($this->input('number')) { + // $rules['number'] = 'unique:invoices,number,'.$this->id.',id,company_id,'.auth()->user()->company()->id; + // } + if (isset($this->number)) { + $rules['number'] = Rule::unique('invoices')->where('company_id', auth()->user()->company()->id); } -// $rules['number'] = new UniqueInvoiceNumberRule($this->all()); + $rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())]; diff --git a/app/Import/Definitions/PaymentMap.php b/app/Import/Definitions/PaymentMap.php new file mode 100644 index 000000000000..c9567e50e044 --- /dev/null +++ b/app/Import/Definitions/PaymentMap.php @@ -0,0 +1,58 @@ + 'payment.number', + 1 => 'payment.user_id', + 2 => 'payment.amount', + 3 => 'payment.refunded', + 4 => 'payment.applied', + 5 => 'payment.transaction_reference', + 6 => 'payment.private_notes', + 7 => 'payment.custom_value1', + 8 => 'payment.custom_value2', + 9 => 'payment.custom_value3', + 10 => 'payment.custom_value4', + 11 => 'payment.client_id', + 12 => 'payment.invoice_number', + 13 => 'payment.date', + 14 => 'payment.method', + ]; + } + + public static function import_keys() + { + return [ + 0 => 'texts.number', + 1 => 'texts.user', + 2 => 'texts.amount', + 3 => 'texts.refunded', + 4 => 'texts.applied', + 5 => 'texts.transaction_reference', + 6 => 'texts.private_notes', + 7 => 'texts.custom_value', + 8 => 'texts.custom_value', + 9 => 'texts.custom_value', + 10 => 'texts.custom_value', + 11 => 'texts.client', + 12 => 'texts.invoice_number', + 13 => 'texts.date', + 14 => 'texts.method' + ]; + } +} + diff --git a/app/Import/Definitions/ProductMap.php b/app/Import/Definitions/ProductMap.php index 91506fe9aab2..d79582852819 100644 --- a/app/Import/Definitions/ProductMap.php +++ b/app/Import/Definitions/ProductMap.php @@ -31,7 +31,6 @@ class ProductMap 12 => 'product.custom_value2', 13 => 'product.custom_value3', 14 => 'product.custom_value4', - 15 => 'product.user_id', ]; } @@ -53,7 +52,6 @@ class ProductMap 12 => 'texts.custom_value', 13 => 'texts.custom_value', 14 => 'texts.custom_value', - 15 => 'texts.user', ]; } } diff --git a/app/Import/Transformers/BaseTransformer.php b/app/Import/Transformers/BaseTransformer.php index 743c12708476..2491e52d6083 100644 --- a/app/Import/Transformers/BaseTransformer.php +++ b/app/Import/Transformers/BaseTransformer.php @@ -1,7 +1,17 @@ maps['currencies']->where('code', $code)->first()->id; + $currency = $this->maps['currencies']->where('code', $code)->first(); + + if($currency_id) + return $currency->id; } return $this->maps['company']->settings->currency_id; } + public function getClient($client_key) + { + $clients = $this->maps['company']->clients; + + $clients = $clients->where('name', $client_key); + + if($clients->count() >= 1) + return $clients->first()->id; + + $contacts = ClientContact::where('company_id', $this->maps['company']->id) + ->where('email', $client_key); + + if($contacts->count() >=1) + return $contact->first()->client_id; + + + return NULL; + + } + + + +/////////////////////////////////////////////////////////////////////////////////// /** * @param $name * diff --git a/app/Import/Transformers/ClientTransformer.php b/app/Import/Transformers/ClientTransformer.php index 70d7b44e96ff..b23e9fcde7e7 100644 --- a/app/Import/Transformers/ClientTransformer.php +++ b/app/Import/Transformers/ClientTransformer.php @@ -1,4 +1,13 @@ $this->getString($data, 'client.custom2'), 'custom_value3' => $this->getString($data, 'client.custom3'), 'custom_value4' => $this->getString($data, 'client.custom4'), - 'balance' => $this->getString($data, 'client.balance'), - 'paid_to_date' => $this->getString($data, 'client.paid_to_date'), + 'balance' => $this->getFloat($data, 'client.balance'), + 'paid_to_date' => $this->getFloat($data, 'client.paid_to_date'), 'credit_balance' => 0, 'settings' => $settings, 'client_hash' => Str::random(40), diff --git a/app/Import/Transformers/InvoiceItemTransformer.php b/app/Import/Transformers/InvoiceItemTransformer.php new file mode 100644 index 000000000000..5ddcca8df3c7 --- /dev/null +++ b/app/Import/Transformers/InvoiceItemTransformer.php @@ -0,0 +1,48 @@ + $this->getFloat($data, 'item.quantity'), + 'cost' => $this->getFloat($data, 'item.cost'), + 'product_key' => $this->getString($data, 'item.product_key'), + 'notes' => $this->getString($data, 'item.notes'), + 'discount' => $this->getFloat($data, 'item.discount'), + 'is_amount_discount' => $this->getString($data, 'item.is_amount_discount'), + 'tax_name1' => $this->getString($data, 'item.tax_name1'), + 'tax_rate1' => $this->getFloat($data, 'item.tax_rate1'), + 'tax_name2' => $this->getString($data, 'item.tax_name2'), + 'tax_rate2' => $this->getFloat($data, 'item.tax_rate2'), + 'tax_name3' => $this->getString($data, 'item.tax_name3'), + 'tax_rate3' => $this->getFloat($data, 'item.tax_rate3'), + 'custom_value1' => $this->getString($data, 'item.custom_value1'), + 'custom_value2' => $this->getString($data, 'item.custom_value2'), + 'custom_value3' => $this->getString($data, 'item.custom_value3'), + 'custom_value4' => $this->getString($data, 'item.custom_value4'), + 'type_id' => $this->getInvoiceTypeId($data, 'item.type_id'), + ]; + } +} \ No newline at end of file diff --git a/app/Import/Transformers/InvoiceTransformer.php b/app/Import/Transformers/InvoiceTransformer.php new file mode 100644 index 000000000000..a2410d3fd07a --- /dev/null +++ b/app/Import/Transformers/InvoiceTransformer.php @@ -0,0 +1,63 @@ + $this->maps['company']->id, + 'number' => $this->getString($data, 'invoice.number'), + 'user_id' => $this->getString($data, 'invoice.user_id'), + 'amount' => $this->getFloat($data, 'invoice.amount'), + 'balance' => $this->getFloat($data, 'invoice.balance'), + 'client_id' => $this->getClient($this->getString($data, 'invoice.client_id')), + 'discount' => $this->getFloat($data, 'invoice.discount'), + 'po_number' => $this->getString($data, 'invoice.po_number'), + 'date' => $this->getString($data, 'invoice.date'), + 'due_date' => $this->getString($data, 'invoice.due_date'), + 'terms' => $this->getString($data, 'invoice.terms'), + 'public_notes' => $this->getString($data, 'invoice.public_notes'), + 'is_sent' => $this->getString($data, 'invoice.is_sent'), + 'private_notes' => $this->getString($data, 'invoice.private_notes'), + 'tax_name1' => $this->getString($data, 'invoice.tax_name1'), + 'tax_rate1' => $this->getFloat($data, 'invoice.tax_rate1'), + 'tax_name2' => $this->getString($data, 'invoice.tax_name2'), + 'tax_rate2' => $this->getFloat($data, 'invoice.tax_rate2'), + 'tax_name3' => $this->getString($data, 'invoice.tax_name3'), + 'tax_rate3' => $this->getFloat($data, 'invoice.tax_rate3'), + 'custom_value1' => $this->getString($data, 'invoice.custom_value1'), + 'custom_value2' => $this->getString($data, 'invoice.custom_value2'), + 'custom_value3' => $this->getString($data, 'invoice.custom_value3'), + 'custom_value4' => $this->getString($data, 'invoice.custom_value4'), + 'footer' => $this->getString($data, 'invoice.footer'), + 'partial' => $this->getFloat($data, 'invoice.partial'), + 'partial_due_date' => $this->getString($data, 'invoice.partial_due_date'), + 'custom_surcharge1' => $this->getString($data, 'invoice.custom_surcharge1'), + 'custom_surcharge2' => $this->getString($data, 'invoice.custom_surcharge2'), + 'custom_surcharge3' => $this->getString($data, 'invoice.custom_surcharge3'), + 'custom_surcharge4' => $this->getString($data, 'invoice.custom_surcharge4'), + 'exchange_rate' => $this->getString($data, 'invoice.exchange_rate'), + ]; + } +} \ No newline at end of file diff --git a/app/Import/Transformers/PaymentTransformer.php b/app/Import/Transformers/PaymentTransformer.php new file mode 100644 index 000000000000..5ce5042f5b99 --- /dev/null +++ b/app/Import/Transformers/PaymentTransformer.php @@ -0,0 +1,48 @@ + $this->maps['company']->id, + 'number' => $this->getString($data, 'payment.number'), + 'user_id' => $this->getString($data, 'payment.user_id'), + 'amount' => $this->getFloat($data, 'payment.amount'), + 'refunded' => $this->getFloat($data, 'payment.refunded'), + 'applied' => $this->getFloat($data, 'payment.applied'), + 'transaction_reference' => $this->getString($data, 'payment.transaction_reference '), + 'date' => $this->getString($data, 'payment.date'), + 'private_notes' => $this->getString($data, 'payment.private_notes'), + 'number' => $this->getString($data, 'number'), + 'custom_value1' => $this->getString($data, 'custom_value1'), + 'custom_value2' => $this->getString($data, 'custom_value2'), + 'custom_value3' => $this->getString($data, 'custom_value3'), + 'custom_value4' => $this->getString($data, 'custom_value4'), + 'client_id' => $this->getString($data, 'client_id'), + 'invoice_number' => $this->getString($data, 'payment.invoice_number'), + 'method' => $this + ]; + } +} \ No newline at end of file diff --git a/app/Import/Transformers/ProductTransformer.php b/app/Import/Transformers/ProductTransformer.php index 6069bb51b0ac..c932823d6651 100644 --- a/app/Import/Transformers/ProductTransformer.php +++ b/app/Import/Transformers/ProductTransformer.php @@ -1,4 +1,13 @@ $this->maps['company']->id, 'product_key' => $this->getString($data, 'product.product_key'), 'notes' => $this->getString($data, 'product.notes'), - 'cost' => $this->getString($data, 'product.cost'), - 'price' => $this->getString($data, 'product.price'), - 'quantity' => $this->getString($data, 'product.quantity'), + 'cost' => $this->getFloat($data, 'product.cost'), + 'price' => $this->getFloat($data, 'product.price'), + 'quantity' => $this->getFloat($data, 'product.quantity'), 'tax_name1' => $this->getString($data, 'product.tax_name1'), - 'tax_rate1' => $this->getString($data, 'product.tax_rate1'), + 'tax_rate1' => $this->getFloat($data, 'product.tax_rate1'), 'tax_name2' => $this->getString($data, 'product.tax_name2'), - 'tax_rate2' => $this->getString($data, 'product.tax_rate2'), + 'tax_rate2' => $this->getFloat($data, 'product.tax_rate2'), 'tax_name3' => $this->getString($data, 'product.tax_name3'), - 'tax_rate3' => $this->getString($data, 'product.tax_rate3'), + 'tax_rate3' => $this->getFloat($data, 'product.tax_rate3'), 'custom_value1' => $this->getString($data, 'product.custom_value1'), 'custom_value2' => $this->getString($data, 'product.custom_value2'), 'custom_value3' => $this->getString($data, 'product.custom_value3'), diff --git a/app/Jobs/Import/CSVImport.php b/app/Jobs/Import/CSVImport.php index 5ca5d4d746b2..a98d2bc1ef9b 100644 --- a/app/Jobs/Import/CSVImport.php +++ b/app/Jobs/Import/CSVImport.php @@ -12,18 +12,24 @@ namespace App\Jobs\Import; use App\Factory\ClientFactory; +use App\Factory\InvoiceFactory; use App\Factory\ProductFactory; use App\Http\Requests\Client\StoreClientRequest; +use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Http\Requests\Product\StoreProductRequest; use App\Import\Transformers\ClientTransformer; +use App\Import\Transformers\InvoiceItemTransformer; +use App\Import\Transformers\InvoiceTransformer; use App\Import\Transformers\ProductTransformer; use App\Libraries\MultiDB; use App\Models\Client; use App\Models\Company; use App\Models\Currency; +use App\Models\Invoice; use App\Models\User; use App\Repositories\ClientContactRepository; use App\Repositories\ClientRepository; +use App\Repositories\InvoiceRepository; use App\Repositories\ProductRepository; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -89,9 +95,14 @@ class CSVImport implements ShouldQueue //sort the array by key ksort($this->column_map); + info("import".ucfirst($this->entity_type)); $this->{"import".ucfirst($this->entity_type)}(); - info(print_r($this->maps,1)); + + info("errors"); + + info(print_r($this->error_array,1)); + } public function failed($exception) @@ -134,12 +145,134 @@ class CSVImport implements ShouldQueue } } + private function importInvoice() + { + + $invoice_transformer = new InvoiceTransformer($this->maps); + + info("import invoices"); + + info("column_map"); + + info(print_r($this->column_map,1)); + + $records = $this->getCsvData(); + + $invoice_number_key = array_search('Invoice Number', reset($records)); + + info("number key = {$invoice_number_key}"); + + if ($this->skip_header) + array_shift($records); + + if(!$invoice_number_key){ + info("no invoice number to use as key - returning"); + return; + } + + $unique_array_filter = array_unique($records[$invoice_number_key]); + $unique_invoices = array_intersect_key( $records, $unique_array_filter ); + + foreach($unique_invoices as $unique) + { + + $keys = $this->column_map; + $values = array_intersect_key($unique, $this->column_map); + $invoice_data = array_combine($keys, $values); + + $invoice = $invoice_transformer->transform($invoice_data); + + foreach($unique_invoices as $val) { + + $invoices = array_filter($records, function($item) use ($val, $invoice_number_key){ + return $item[$invoice_number_key] == $val[$invoice_number_key]; + }); + + } + + $this->processInvoice($invoices, $invoice); + + } + + + } + + private function processInvoice($invoices, $invoice) + { + $invoice_repository = new InvoiceRepository(); + $item_transformer = new InvoiceItemTransformer($this->maps); + $items = []; + + foreach($invoices as $record) + { + + $keys = $this->column_map; + $values = array_intersect_key($record, $this->column_map); + $invoice_data = array_combine($keys, $values); + + $items[] = $item_transformer->transform($invoice_data); + + } + + $invoice['line_items'] = $items; + +info(print_r($invoice->toArray(),1)); + + $validator = Validator::make($invoice, (new StoreInvoiceRequest())->rules()); + + if ($validator->fails()) { + $this->error_array[] = ['invoice' => $invoice, 'error' => json_encode($validator->errors())]; + } else { + $invoice = $invoice_repository->save($invoice, InvoiceFactory::create($this->company->id, $this->setUser($record))); + + $invoice->save(); + + $this->maps['invoices'][] = $invoice->id; + + $this->performInvoiceActions($invoice, $record, $invoice_repository); + } + } + + private function performInvoiceActions($invoice, $record, $invoice_repository) + { + + $invoice = $this->actionInvoiceStatus($invoice, $record, $invoice_repository); + } + + private function actionInvoiceStatus($invoice, $status, $invoice_repository) + { + switch ($status) { + case 'Archived': + $invoice_repository->archive($invoice); + $invoice->fresh(); + break; + case 'Sent': + $invoice = $invoice->service()->markSent()->save(); + break; + case 'Viewed'; + $invoice = $invoice->service()->markSent()->save(); + break; + default: + # code... + break; + } + + if($invoice->balance < $invoice->amount && $invoice->status_id <= Invoice::STATUS_SENT){ + $invoice->status_id = Invoice::STATUS_PARTIAL; + $invoice->save(); + } + + return $invoice; + } + //todo limit client imports for hosted version private function importClient() { //clients $records = $this->getCsvData(); +info(print_r($this->column_map,1)); + $contact_repository = new ClientContactRepository(); $client_repository = new ClientRepository($contact_repository); $client_transformer = new ClientTransformer($this->maps);