diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 848519c822e6..a52b657b1f72 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -237,12 +237,12 @@ class BaseExport "cost" => "item.cost", "product_key" => "item.product_key", "notes" => "item.notes", - "item_tax1" => "item.tax_name1", - "item_tax_rate1" => "item.tax_rate1", - "item_tax2" => "item.tax_name2", - "item_tax_rate2" => "item.tax_rate2", - "item_tax3" => "item.tax_name3", - "item_tax_rate3" => "item.tax_rate3", + "tax_name1" => "item.tax_name1", + "tax_rate1" => "item.tax_rate1", + "tax_name2" => "item.tax_name2", + "tax_rate2" => "item.tax_rate2", + "tax_name3" => "item.tax_name3", + "tax_rate3" => "item.tax_rate3", "custom_value1" => "item.custom_value1", "custom_value2" => "item.custom_value2", "custom_value3" => "item.custom_value3", @@ -250,6 +250,9 @@ class BaseExport "discount" => "item.discount", "type" => "item.type_id", "tax_category" => "item.tax_id", + 'is_amount_discount' => 'item.is_amount_discount', + 'line_total' => 'item.line_total', + 'gross_line_total' => 'item.gross_line_total', ]; protected array $quote_report_keys = [ @@ -861,6 +864,19 @@ class BaseExport return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC'); } } + + /** + * Returns the merged array of + * the entity with the matching + * item report keys + * + * @param string $entity_report_keys + * @return array + */ + public function mergeItemsKeys(string $entity_report_keys): array + { + return array_merge($this->{$entity_report_keys}, $this->item_report_keys); + } public function buildHeader() :array { diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php index 246abe243f36..bf999f9177f7 100644 --- a/app/Export/CSV/InvoiceExport.php +++ b/app/Export/CSV/InvoiceExport.php @@ -126,7 +126,7 @@ class InvoiceExport extends BaseExport private function decorateAdvancedFields(Invoice $invoice, array $entity) :array { - nlog($entity); + if (in_array('invoice.country_id', $this->input['report_keys'])) { $entity['invoice.country_id'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : ''; } diff --git a/app/Export/CSV/InvoiceItemExport.php b/app/Export/CSV/InvoiceItemExport.php index 943844bc125a..33fb942c846e 100644 --- a/app/Export/CSV/InvoiceItemExport.php +++ b/app/Export/CSV/InvoiceItemExport.php @@ -17,6 +17,7 @@ use App\Models\Company; use App\Models\Invoice; use App\Transformers\InvoiceTransformer; use App\Utils\Ninja; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; use League\Csv\Writer; @@ -31,64 +32,7 @@ class InvoiceItemExport extends BaseExport private bool $force_keys = false; - public array $entity_keys = [ - 'amount' => 'amount', - 'balance' => 'balance', - 'client' => 'client_id', - 'client_number' => 'client.number', - 'client_id_number' => 'client.id_number', - 'custom_surcharge1' => 'custom_surcharge1', - 'custom_surcharge2' => 'custom_surcharge2', - 'custom_surcharge3' => 'custom_surcharge3', - 'custom_surcharge4' => 'custom_surcharge4', - 'custom_value1' => 'custom_value1', - 'custom_value2' => 'custom_value2', - 'custom_value3' => 'custom_value3', - 'custom_value4' => 'custom_value4', - 'date' => 'date', - 'discount' => 'discount', - 'due_date' => 'due_date', - 'exchange_rate' => 'exchange_rate', - 'footer' => 'footer', - 'number' => 'number', - 'paid_to_date' => 'paid_to_date', - 'partial' => 'partial', - 'partial_due_date' => 'partial_due_date', - 'po_number' => 'po_number', - 'private_notes' => 'private_notes', - 'public_notes' => 'public_notes', - 'status' => 'status_id', - 'tax_name1' => 'tax_name1', - 'tax_name2' => 'tax_name2', - 'tax_name3' => 'tax_name3', - 'tax_rate1' => 'tax_rate1', - 'tax_rate2' => 'tax_rate2', - 'tax_rate3' => 'tax_rate3', - 'terms' => 'terms', - 'total_taxes' => 'total_taxes', - 'currency' => 'currency_id', - 'quantity' => 'item.quantity', - 'cost' => 'item.cost', - 'product_key' => 'item.product_key', - 'buy_price' => 'item.product_cost', - 'notes' => 'item.notes', - 'discount' => 'item.discount', - 'is_amount_discount' => 'item.is_amount_discount', - 'tax_rate1' => 'item.tax_rate1', - 'tax_rate2' => 'item.tax_rate2', - 'tax_rate3' => 'item.tax_rate3', - 'tax_name1' => 'item.tax_name1', - 'tax_name2' => 'item.tax_name2', - 'tax_name3' => 'item.tax_name3', - 'line_total' => 'item.line_total', - 'gross_line_total' => 'item.gross_line_total', - 'invoice1' => 'item.custom_value1', - 'invoice2' => 'item.custom_value2', - 'invoice3' => 'item.custom_value3', - 'invoice4' => 'item.custom_value4', - 'tax_category' => 'item.tax_id', - 'type' => 'item.type_id', - ]; + private array $storage_array = []; private array $decorate_keys = [ 'client', @@ -103,25 +47,20 @@ class InvoiceItemExport extends BaseExport $this->invoice_transformer = new InvoiceTransformer(); } - public function run() + public function init(): Builder { + MultiDB::setDb($this->company->db); App::forgetInstance('translator'); App::setLocale($this->company->locale()); $t = app('translator'); $t->replace(Ninja::transformTranslations($this->company->settings)); - //load the CSV document from a string - $this->csv = Writer::createFromString(); - if (count($this->input['report_keys']) == 0) { $this->force_keys = true; - $this->input['report_keys'] = array_values($this->entity_keys); + $this->input['report_keys'] = array_values($this->mergeItemsKeys('invoice_report_keys')); } - //insert the header - $this->csv->insertOne($this->buildHeader()); - $query = Invoice::query() ->withTrashed() ->with('client')->where('company_id', $this->company->id) @@ -129,11 +68,46 @@ class InvoiceItemExport extends BaseExport $query = $this->addDateRange($query); + return $query; + + } + + public function returnJson() + { + $query = $this->init(); + + $headerdisplay = $this->buildHeader(); + + $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ + return ['identifier' => $value, 'display_value' => $headerdisplay[$value]]; + })->toArray(); + + $query->cursor() + ->each(function ($resource) { + $this->iterateItems($resource); + }); + + return array_merge(['columns' => $header], $this->storage_array); + } + + + public function run() + { + $query = $this->init(); + + //load the CSV document from a string + $this->csv = Writer::createFromString(); + + //insert the header + $this->csv->insertOne($this->buildHeader()); + $query->cursor() ->each(function ($invoice) { $this->iterateItems($invoice); }); + $this->csv->insertAll($this->storage_array); + return $this->csv->toString(); } @@ -146,46 +120,50 @@ class InvoiceItemExport extends BaseExport foreach ($invoice->line_items as $item) { $item_array = []; - foreach (array_values($this->input['report_keys']) as $key) { //items iterator produces item array + foreach (array_values(array_intersect($this->input['report_keys'], $this->item_report_keys)) as $key) { //items iterator produces item array if (str_contains($key, "item.")) { $key = str_replace("item.", "", $key); - $keyval = $key; + // $keyval = $key; - $keyval = str_replace("custom_value", "invoice", $key); + // $key = str_replace("custom_value", "invoice", $key); if($key == 'type_id') - $keyval = 'type'; + $key = 'type'; if($key == 'tax_id') - $keyval = 'tax_category'; + $key = 'tax_category'; if (property_exists($item, $key)) { - $item_array[$keyval] = $item->{$key}; - } else { - $item_array[$keyval] = ''; + $item_array[$key] = $item->{$key}; + } + else { + $item_array[$key] = ''; } } } +nlog($item_array); + // $entity = []; - $entity = []; + // foreach (array_values($this->input['report_keys']) as $key) { //create an array of report keys only + // // $keyval = array_search($key, $this->entity_keys); + // $key = str_replace("item.", "", $key); - foreach (array_values($this->input['report_keys']) as $key) { //create an array of report keys only - $keyval = array_search($key, $this->entity_keys); - - if (array_key_exists($key, $transformed_items)) { - $entity[$keyval] = $transformed_items[$key]; - } else { - $entity[$keyval] = ""; - } - } + // if (array_key_exists($key, $transformed_items)) { + // $entity[$key] = $transformed_items[$key]; + // } else { + // $entity[$key] = ""; + // } + // } $transformed_items = array_merge($transformed_invoice, $item_array); $entity = $this->decorateAdvancedFields($invoice, $transformed_items); - $this->csv->insertOne($entity); + $this->storage_array[] = $entity; + + // $this->csv->insertOne($entity); } } @@ -196,23 +174,30 @@ class InvoiceItemExport extends BaseExport $entity = []; foreach (array_values($this->input['report_keys']) as $key) { - $keyval = array_search($key, $this->entity_keys); + // $keyval = array_search($key, $this->entity_keys); - if(!$keyval) { - $keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key; - } + // if(!$keyval) { + // $keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key; + // } - if(!$keyval) { - $keyval = $key; - } + // if(!$keyval) { + // $keyval = $key; + // } + $parts = explode('.', $key); - if (array_key_exists($key, $transformed_invoice)) { - $entity[$keyval] = $transformed_invoice[$key]; - } elseif (array_key_exists($keyval, $transformed_invoice)) { - $entity[$keyval] = $transformed_invoice[$keyval]; - } + if(is_array($parts) && $parts[0] == 'item') + continue; + + if (is_array($parts) && $parts[0] == 'invoice' && array_key_exists($parts[1], $transformed_invoice)) { + $entity[$key] = $transformed_invoice[$parts[1]]; + }else if (array_key_exists($key, $transformed_invoice)) { + $entity[$key] = $transformed_invoice[$key]; + } + // elseif (array_key_exists($keyval, $transformed_invoice)) { + // $entity[$keyval] = $transformed_invoice[$keyval]; + // } else { - $entity[$keyval] = $this->resolveKey($keyval, $invoice, $this->invoice_transformer); + $entity[$key] = $this->resolveKey($key, $invoice, $this->invoice_transformer); } } @@ -233,12 +218,12 @@ class InvoiceItemExport extends BaseExport $entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']); } - if($this->force_keys) { - $entity['client'] = $invoice->client->present()->name(); - $entity['client_id_number'] = $invoice->client->id_number; - $entity['client_number'] = $invoice->client->number; - $entity['status'] = $invoice->stringStatus($invoice->status_id); - } + // if($this->force_keys) { + // $entity['client'] = $invoice->client->present()->name(); + // $entity['client_id_number'] = $invoice->client->id_number; + // $entity['client_number'] = $invoice->client->number; + // $entity['status'] = $invoice->stringStatus($invoice->status_id); + // } return $entity; } diff --git a/app/Http/Controllers/Reports/InvoiceItemReportController.php b/app/Http/Controllers/Reports/InvoiceItemReportController.php index 77deb05478dd..dc568dfad629 100644 --- a/app/Http/Controllers/Reports/InvoiceItemReportController.php +++ b/app/Http/Controllers/Reports/InvoiceItemReportController.php @@ -11,12 +11,13 @@ namespace App\Http\Controllers\Reports; +use Illuminate\Http\Response; +use App\Utils\Traits\MakesHash; +use App\Jobs\Report\SendToAdmin; +use App\Jobs\Report\PreviewReport; use App\Export\CSV\InvoiceItemExport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; -use App\Jobs\Report\SendToAdmin; -use App\Utils\Traits\MakesHash; -use Illuminate\Http\Response; class InvoiceItemReportController extends BaseController { @@ -62,14 +63,26 @@ class InvoiceItemReportController extends BaseController */ public function __invoke(GenericReportRequest $request) { + /** @var \App\Models\User $user */ + $user = auth()->user(); + if ($request->has('send_email') && $request->get('send_email')) { - SendToAdmin::dispatch(auth()->user()->company(), $request->all(), InvoiceItemExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), InvoiceItemExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - $export = new InvoiceItemExport(auth()->user()->company(), $request->all()); + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), InvoiceItemExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + + $export = new InvoiceItemExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/app/Http/Controllers/Reports/InvoiceReportController.php b/app/Http/Controllers/Reports/InvoiceReportController.php index 60457bd70c76..c00687fb6885 100644 --- a/app/Http/Controllers/Reports/InvoiceReportController.php +++ b/app/Http/Controllers/Reports/InvoiceReportController.php @@ -11,12 +11,13 @@ namespace App\Http\Controllers\Reports; +use Illuminate\Http\Response; +use App\Utils\Traits\MakesHash; +use App\Jobs\Report\SendToAdmin; use App\Export\CSV\InvoiceExport; +use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; -use App\Jobs\Report\SendToAdmin; -use App\Utils\Traits\MakesHash; -use Illuminate\Http\Response; class InvoiceReportController extends BaseController { @@ -62,14 +63,26 @@ class InvoiceReportController extends BaseController */ public function __invoke(GenericReportRequest $request) { + /** @var \App\Models\User $user */ + $user = auth()->user(); + if ($request->has('send_email') && $request->get('send_email')) { - SendToAdmin::dispatch(auth()->user()->company(), $request->all(), InvoiceExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), InvoiceExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - $export = new InvoiceExport(auth()->user()->company(), $request->all()); + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), InvoiceExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + // expect a list of visible fields, or use the default + $export = new InvoiceExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index 30a262154e0f..27b4f48041d3 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -985,20 +985,20 @@ nlog($csv); ])->post('/api/v1/reports/invoice_items', $data); $csv = $response->streamedContent(); - +nlog($csv); $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Invoice Invoice Number')); $this->assertEquals('Unpaid', $this->getFirstValueByColumn($csv, 'Payment Amount')); $this->assertEquals('', $this->getFirstValueByColumn($csv, 'Payment Date')); - $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Quantity')); - $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Cost')); - $this->assertEquals('1000', $this->getFirstValueByColumn($csv, 'Line Total')); - $this->assertEquals('0', $this->getFirstValueByColumn($csv, 'Discount')); - $this->assertEquals('item notes', $this->getFirstValueByColumn($csv, 'Notes')); - $this->assertEquals('product key', $this->getFirstValueByColumn($csv, 'Product')); + $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Item Quantity')); + $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Item Cost')); + $this->assertEquals('1000', $this->getFirstValueByColumn($csv, 'Item Line Total')); + $this->assertEquals('0', $this->getFirstValueByColumn($csv, 'Item Discount')); + $this->assertEquals('item notes', $this->getFirstValueByColumn($csv, 'Item Notes')); + $this->assertEquals('product key', $this->getFirstValueByColumn($csv, 'Item Product')); $this->assertEquals('custom 1', $this->getFirstValueByColumn($csv, 'Item Custom Value 1')); - $this->assertEquals('GST', $this->getFirstValueByColumn($csv, 'Tax Name 1')); - $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Tax Rate 1')); + $this->assertEquals('GST', $this->getFirstValueByColumn($csv, 'Item Tax Name 1')); + $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Item Tax Rate 1')); $data = [ diff --git a/tests/Feature/Export/ReportPreviewTest.php b/tests/Feature/Export/ReportPreviewTest.php index 7beecc2eced1..33c65b477770 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -54,6 +54,38 @@ class ReportPreviewTest extends TestCase } + public function testInvoiceItemJsonExport() + { + \App\Models\Invoice::factory()->count(5)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + ]); + + $data = [ + 'send_email' => false, + 'date_range' => 'all', + 'report_keys' => [], + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/reports/invoice_items?output=json', $data) + ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, \App\Export\CSV\InvoiceItemExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); + + nlog($r); + + } + public function testInvoiceJsonExport() { @@ -82,7 +114,7 @@ class ReportPreviewTest extends TestCase $r = Cache::pull('123'); $this->assertNotNull($r); -nlog($r); + } public function testExpenseJsonExport()