From 80e66d914643765cfa6de4f659f4ea76fef2a4a8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 27 Aug 2023 22:02:55 +1000 Subject: [PATCH 01/17] Activity json export --- app/Export/CSV/ActivityExport.php | 103 +++++++++++------- .../Reports/ActivityReportController.php | 18 ++- tests/Feature/Export/ReportApiTest.php | 35 ++++++ 3 files changed, 117 insertions(+), 39 deletions(-) diff --git a/app/Export/CSV/ActivityExport.php b/app/Export/CSV/ActivityExport.php index 21aa0ecb0979..4ae02fb63e4e 100644 --- a/app/Export/CSV/ActivityExport.php +++ b/app/Export/CSV/ActivityExport.php @@ -20,6 +20,7 @@ use App\Libraries\MultiDB; use App\Models\DateFormat; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\App; +use Illuminate\Database\Eloquent\Builder; use App\Transformers\ActivityTransformer; class ActivityExport extends BaseExport @@ -39,10 +40,6 @@ class ActivityExport extends BaseExport 'address' => 'address', ]; - private array $decorate_keys = [ - - ]; - public function __construct(Company $company, array $input) { $this->company = $company; @@ -50,45 +47,27 @@ class ActivityExport extends BaseExport $this->entity_transformer = new ActivityTransformer(); } - public function run() + public function returnJson() { - MultiDB::setDb($this->company->db); - App::forgetInstance('translator'); - App::setLocale($this->company->locale()); - $t = app('translator'); - $t->replace(Ninja::transformTranslations($this->company->settings)); + $query = $this->init(); - $this->date_format = DateFormat::find($this->company->settings->date_format_id)->format; + $headerdisplay = $this->buildHeader(); - //load the CSV document from a string - $this->csv = Writer::createFromString(); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ + return ['identifier' => $value, 'display_value' => $headerdisplay[$value]]; + })->toArray(); - ksort($this->entity_keys); - - if (count($this->input['report_keys']) == 0) { - $this->input['report_keys'] = array_values($this->entity_keys); - } - - //insert the header - $this->csv->insertOne($this->buildHeader()); - - $query = Activity::query() - ->where('company_id', $this->company->id); - - $query = $this->addDateRange($query); - - $query->cursor() - ->each(function ($entity) { - $this->buildRow($entity); - }); - - return $this->csv->toString(); + $report = $query->cursor() + ->map(function ($credit) { + return $this->buildActivityRow($credit); + })->toArray(); + + return array_merge(['columns' => $header], $report); } - private function buildRow(Activity $activity) + private function buildActivityRow(Activity $activity): array { - - $this->csv->insertOne([ + return [ Carbon::parse($activity->created_at)->format($this->date_format), ctrans("texts.activity_{$activity->activity_type_id}",[ 'client' => $activity->client ? $activity->client->present()->name() : '', @@ -108,7 +87,57 @@ class ActivityExport extends BaseExport 'recurring_expense' => $activity->recurring_expense ? $activity->recurring_expense->number : '', ]), $activity->ip, - ]); + ]; + + } + + private 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)); + + $this->date_format = DateFormat::find($this->company->settings->date_format_id)->format; + + ksort($this->entity_keys); + + if (count($this->input['report_keys']) == 0) { + $this->input['report_keys'] = array_values($this->entity_keys); + } + + $query = Activity::query() + ->where('company_id', $this->company->id); + + $query = $this->addDateRange($query); + + return $query; + } + + 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 ($entity) { + $this->buildRow($entity); + }); + + return $this->csv->toString(); + } + + private function buildRow(Activity $activity) + { + + $this->csv->insertOne($this->buildActivityRow($activity)); } diff --git a/app/Http/Controllers/Reports/ActivityReportController.php b/app/Http/Controllers/Reports/ActivityReportController.php index 24e1feb2e587..ed241a9e38f2 100644 --- a/app/Http/Controllers/Reports/ActivityReportController.php +++ b/app/Http/Controllers/Reports/ActivityReportController.php @@ -14,6 +14,7 @@ namespace App\Http\Controllers\Reports; use App\Utils\Traits\MakesHash; use App\Jobs\Report\SendToAdmin; use App\Export\CSV\ActivityExport; +use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; @@ -31,14 +32,27 @@ class ActivityReportController 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(), ActivityExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), ActivityExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } // expect a list of visible fields, or use the default - $export = new ActivityExport(auth()->user()->company(), $request->all()); + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), ActivityExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + $export = new ActivityExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/tests/Feature/Export/ReportApiTest.php b/tests/Feature/Export/ReportApiTest.php index 14fd1f57297b..a6f45b825e3a 100644 --- a/tests/Feature/Export/ReportApiTest.php +++ b/tests/Feature/Export/ReportApiTest.php @@ -42,6 +42,39 @@ class ReportApiTest extends TestCase } + + public function testActivityCSVExport() + { + $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/activities', $data) + ->assertStatus(200); + + } + + public function testActivityCSVExportJson() + { + $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/activities?output=json', $data) + ->assertStatus(200); + + } + public function testUserSalesReportApiRoute() { $data = [ @@ -125,6 +158,8 @@ class ReportApiTest extends TestCase } + + public function testClientBalanceReportApiRoute() { $data = [ From 427bc341fb410f52f823f5838d2c1a6e23b7cf2d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 27 Aug 2023 22:09:26 +1000 Subject: [PATCH 02/17] Client export - json output --- app/Export/CSV/ClientExport.php | 45 +++++++++++++++---- .../Reports/ClientReportController.php | 27 ++++++++--- tests/Feature/Export/ReportApiTest.php | 14 ------ tests/Feature/Export/ReportPreviewTest.php | 33 ++++++++++++++ 4 files changed, 90 insertions(+), 29 deletions(-) diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php index 0335be61c467..caf90402d34f 100644 --- a/app/Export/CSV/ClientExport.php +++ b/app/Export/CSV/ClientExport.php @@ -17,6 +17,7 @@ use App\Models\Company; use App\Transformers\ClientContactTransformer; use App\Transformers\ClientTransformer; use App\Utils\Ninja; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; use League\Csv\Writer; @@ -87,7 +88,27 @@ class ClientExport extends BaseExport $this->contact_transformer = new ClientContactTransformer(); } - public function run() + 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(); + + $report = $query->cursor() + ->map(function ($client) { + return $this->buildRow($client); + })->toArray(); + + return array_merge(['columns' => $header], $report); + } + + + + public function init(): Builder { MultiDB::setDb($this->company->db); App::forgetInstance('translator'); @@ -95,15 +116,9 @@ class ClientExport extends BaseExport $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->input['report_keys'] = array_values($this->entity_keys); + $this->input['report_keys'] = array_values($this->client_report_keys); } - - //insert the header - $this->csv->insertOne($this->buildHeader()); $query = Client::query()->with('contacts') ->withTrashed() @@ -112,6 +127,20 @@ class ClientExport extends BaseExport $query = $this->addDateRange($query); + return $query; + + } + + 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 ($client) { $this->csv->insertOne($this->buildRow($client)); diff --git a/app/Http/Controllers/Reports/ClientReportController.php b/app/Http/Controllers/Reports/ClientReportController.php index 462b090046c0..bcbc2c02d98d 100644 --- a/app/Http/Controllers/Reports/ClientReportController.php +++ b/app/Http/Controllers/Reports/ClientReportController.php @@ -11,13 +11,14 @@ namespace App\Http\Controllers\Reports; +use App\Models\Client; +use Illuminate\Http\Response; +use App\Utils\Traits\MakesHash; use App\Export\CSV\ClientExport; +use App\Jobs\Report\SendToAdmin; +use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; -use App\Jobs\Report\SendToAdmin; -use App\Models\Client; -use App\Utils\Traits\MakesHash; -use Illuminate\Http\Response; class ClientReportController extends BaseController { @@ -63,14 +64,26 @@ class ClientReportController 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(), ClientExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), ClientExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } - // expect a list of visible fields, or use the default - $export = new ClientExport(auth()->user()->company(), $request->all()); + // expect a list of visible fields, or use the default + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), ClientExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + $export = new ClientExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/tests/Feature/Export/ReportApiTest.php b/tests/Feature/Export/ReportApiTest.php index a6f45b825e3a..6b91468241e9 100644 --- a/tests/Feature/Export/ReportApiTest.php +++ b/tests/Feature/Export/ReportApiTest.php @@ -59,21 +59,7 @@ class ReportApiTest extends TestCase } - public function testActivityCSVExportJson() - { - $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/activities?output=json', $data) - ->assertStatus(200); - - } public function testUserSalesReportApiRoute() { diff --git a/tests/Feature/Export/ReportPreviewTest.php b/tests/Feature/Export/ReportPreviewTest.php index 2f029a125cd7..eb08b560c550 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -44,6 +44,39 @@ class ReportPreviewTest extends TestCase } + + public function testClientExportJson() + { + $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/clients?output=json', $data) + ->assertStatus(200); + + } + + public function testActivityCSVExportJson() + { + $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/activities?output=json', $data) + ->assertStatus(200); + + } + public function testCreditExportPreview() { From ac8fffcdc2e31a83cbb5822652e4e327f3a55c18 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 28 Aug 2023 07:41:02 +1000 Subject: [PATCH 03/17] Additional exports --- app/Export/CSV/ContactExport.php | 95 ++++++++----------- .../Reports/ClientContactReportController.php | 27 +++++- tests/Feature/Export/ReportPreviewTest.php | 68 +++++++++++++ 3 files changed, 129 insertions(+), 61 deletions(-) diff --git a/app/Export/CSV/ContactExport.php b/app/Export/CSV/ContactExport.php index 04ca04797d82..5f5d2fb1bd9e 100644 --- a/app/Export/CSV/ContactExport.php +++ b/app/Export/CSV/ContactExport.php @@ -18,6 +18,7 @@ use App\Models\Company; use App\Transformers\ClientContactTransformer; use App\Transformers\ClientTransformer; use App\Utils\Ninja; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; use League\Csv\Writer; @@ -32,54 +33,6 @@ class ContactExport extends BaseExport public string $date_key = 'created_at'; - public array $entity_keys = [ - 'address1' => 'client.address1', - 'address2' => 'client.address2', - 'balance' => 'client.balance', - 'city' => 'client.city', - 'country' => 'client.country_id', - 'credit_balance' => 'client.credit_balance', - 'custom_value1' => 'client.custom_value1', - 'custom_value2' => 'client.custom_value2', - 'custom_value3' => 'client.custom_value3', - 'custom_value4' => 'client.custom_value4', - 'id_number' => 'client.id_number', - 'industry' => 'client.industry_id', - 'last_login' => 'client.last_login', - 'name' => 'client.name', - 'number' => 'client.number', - 'paid_to_date' => 'client.paid_to_date', - 'client_phone' => 'client.phone', - 'postal_code' => 'client.postal_code', - 'private_notes' => 'client.private_notes', - 'public_notes' => 'client.public_notes', - 'shipping_address1' => 'client.shipping_address1', - 'shipping_address2' => 'client.shipping_address2', - 'shipping_city' => 'client.shipping_city', - 'shipping_country' => 'client.shipping_country_id', - 'shipping_postal_code' => 'client.shipping_postal_code', - 'shipping_state' => 'client.shipping_state', - 'state' => 'client.state', - 'vat_number' => 'client.vat_number', - 'website' => 'client.website', - 'currency' => 'client.currency', - 'first_name' => 'contact.first_name', - 'last_name' => 'contact.last_name', - 'contact_phone' => 'contact.phone', - 'contact_custom_value1' => 'contact.custom_value1', - 'contact_custom_value2' => 'contact.custom_value2', - 'contact_custom_value3' => 'contact.custom_value3', - 'contact_custom_value4' => 'contact.custom_value4', - 'email' => 'contact.email', - ]; - - private array $decorate_keys = [ - 'client.country_id', - 'client.shipping_country_id', - 'client.currency', - 'client.industry', - ]; - public function __construct(Company $company, array $input) { $this->company = $company; @@ -88,29 +41,39 @@ class ContactExport extends BaseExport $this->contact_transformer = new ClientContactTransformer(); } - public function run() + private 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->input['report_keys'] = array_values($this->entity_keys); + $this->input['report_keys'] = array_values($this->client_report_keys); } - //insert the header - $this->csv->insertOne($this->buildHeader()); - $query = ClientContact::query() ->where('company_id', $this->company->id); $query = $this->addDateRange($query); + return $query; + + } + + 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 ($contact) { $this->csv->insertOne($this->buildRow($contact)); }); @@ -118,6 +81,26 @@ class ContactExport extends BaseExport return $this->csv->toString(); } + + 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(); + + $report = $query->cursor() + ->map(function ($contact) { + return $this->buildRow($contact); + })->toArray(); + + return array_merge(['columns' => $header], $report); + } + + private function buildRow(ClientContact $contact) :array { $transformed_contact = false; diff --git a/app/Http/Controllers/Reports/ClientContactReportController.php b/app/Http/Controllers/Reports/ClientContactReportController.php index 39f8b353433f..e29eefe195f2 100644 --- a/app/Http/Controllers/Reports/ClientContactReportController.php +++ b/app/Http/Controllers/Reports/ClientContactReportController.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\ContactExport; +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 ClientContactReportController extends BaseController { @@ -62,13 +63,29 @@ class ClientContactReportController 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(), ContactExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), ContactExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } + + // expect a list of visible fields, or use the default - $export = new ContactExport(auth()->user()->company(), $request->all()); + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), ContactExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + + // expect a list of visible fields, or use the default + $export = new ContactExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/tests/Feature/Export/ReportPreviewTest.php b/tests/Feature/Export/ReportPreviewTest.php index eb08b560c550..26cf25872981 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -11,11 +11,15 @@ namespace Tests\Feature\Export; +use App\Export\CSV\ActivityExport; use Tests\TestCase; use Tests\MockAccountData; use App\Utils\Traits\MakesHash; +use App\Export\CSV\ClientExport; use App\Export\CSV\CreditExport; +use App\Export\CSV\ContactExport; use App\Jobs\Report\PreviewReport; +use Illuminate\Support\Facades\Cache; use Illuminate\Routing\Middleware\ThrottleRequests; /** @@ -59,6 +63,56 @@ class ReportPreviewTest extends TestCase ])->postJson('/api/v1/reports/clients?output=json', $data) ->assertStatus(200); + $data = [ + 'send_email' => false, + 'date_range' => 'all', + 'report_keys' => ['client.name','client.balance'], + ]; + + + $p = (new PreviewReport($this->company, $data, ClientExport::class, 'client_export1'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('client_export1'); + + $this->assertNotNull($r); + + + } + + public function testClientContactExportJsonLimitedKeys() + { + + $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/client_contacts?output=json', $data) + ->assertStatus(200); + + $data = [ + 'send_email' => false, + 'date_range' => 'all', + 'report_keys' => ['client.name','client.balance','contact.email'], + ]; + + + $p = (new PreviewReport($this->company, $data, ContactExport::class, '123'))->handle(); + + $this->assertNull($p); + + + $r = Cache::pull('123'); + + $this->assertNotNull($r); + + nlog($r); } public function testActivityCSVExportJson() @@ -75,6 +129,16 @@ class ReportPreviewTest extends TestCase ])->postJson('/api/v1/reports/activities?output=json', $data) ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, ActivityExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); + + } public function testCreditExportPreview() @@ -90,6 +154,10 @@ class ReportPreviewTest extends TestCase $this->assertNull($p); + $r = Cache::pull('123'); + + $this->assertNotNull($r); + } public function testCreditPreview() From bc4e6711d9490ee7c6e1284de9451a5fb5227886 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 28 Aug 2023 08:00:43 +1000 Subject: [PATCH 04/17] fixes for tests --- app/Export/CSV/ClientExport.php | 7 -- app/Export/CSV/ContactExport.php | 7 +- app/Export/CSV/DocumentExport.php | 47 +++++++--- app/Export/CSV/ExpenseExport.php | 91 ++++++++----------- .../Export/ReportCsvGenerationTest.php | 18 ++-- tests/Feature/Export/ReportPreviewTest.php | 69 +++++++++++++- 6 files changed, 150 insertions(+), 89 deletions(-) diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php index caf90402d34f..608595196fea 100644 --- a/app/Export/CSV/ClientExport.php +++ b/app/Export/CSV/ClientExport.php @@ -73,13 +73,6 @@ class ClientExport extends BaseExport 'status' => 'status' ]; - private array $decorate_keys = [ - 'client.country_id', - 'client.shipping_country_id', - 'client.currency', - 'client.industry', - ]; - public function __construct(Company $company, array $input) { $this->company = $company; diff --git a/app/Export/CSV/ContactExport.php b/app/Export/CSV/ContactExport.php index 5f5d2fb1bd9e..5efddeaade90 100644 --- a/app/Export/CSV/ContactExport.php +++ b/app/Export/CSV/ContactExport.php @@ -112,14 +112,13 @@ class ContactExport extends BaseExport foreach (array_values($this->input['report_keys']) as $key) { $parts = explode('.', $key); - $keyval = array_search($key, $this->entity_keys); if ($parts[0] == 'client' && array_key_exists($parts[1], $transformed_client)) { - $entity[$keyval] = $transformed_client[$parts[1]]; + $entity[$key] = $transformed_client[$parts[1]]; } elseif ($parts[0] == 'contact' && array_key_exists($parts[1], $transformed_contact)) { - $entity[$keyval] = $transformed_contact[$parts[1]]; + $entity[$key] = $transformed_contact[$parts[1]]; } else { - $entity[$keyval] = ''; + $entity[$key] = ''; } } diff --git a/app/Export/CSV/DocumentExport.php b/app/Export/CSV/DocumentExport.php index b43a759b5254..26d55840e431 100644 --- a/app/Export/CSV/DocumentExport.php +++ b/app/Export/CSV/DocumentExport.php @@ -16,6 +16,7 @@ use App\Models\Company; use App\Models\Document; use App\Transformers\DocumentTransformer; use App\Utils\Ninja; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; use League\Csv\Writer; @@ -30,16 +31,11 @@ class DocumentExport extends BaseExport public array $entity_keys = [ 'record_type' => 'record_type', - // 'record_name' => 'record_name', 'name' => 'name', 'type' => 'type', 'created_at' => 'created_at', ]; - private array $decorate_keys = [ - - ]; - public function __construct(Company $company, array $input) { $this->company = $company; @@ -47,28 +43,55 @@ class DocumentExport extends BaseExport $this->entity_transformer = new DocumentTransformer(); } - public function run() + 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(); + + $report = $query->cursor() + ->map(function ($document) { + $row = $this->buildRow($document); + })->toArray(); + + return array_merge(['columns' => $header], $report); + } + + private 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->input['report_keys'] = array_values($this->entity_keys); } - //insert the header - $this->csv->insertOne($this->buildHeader()); - $query = Document::query()->where('company_id', $this->company->id); $query = $this->addDateRange($query); + return $query; + + } + + 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 ($entity) { $this->csv->insertOne($this->buildRow($entity)); diff --git a/app/Export/CSV/ExpenseExport.php b/app/Export/CSV/ExpenseExport.php index 70bb8c841edb..1eb5747cc16f 100644 --- a/app/Export/CSV/ExpenseExport.php +++ b/app/Export/CSV/ExpenseExport.php @@ -16,6 +16,7 @@ use App\Models\Company; use App\Models\Expense; use App\Transformers\ExpenseTransformer; use App\Utils\Ninja; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; use League\Csv\Writer; @@ -28,51 +29,6 @@ class ExpenseExport extends BaseExport public Writer $csv; - public array $entity_keys = [ - 'amount' => 'expense.amount', - 'category' => 'expense.category', - 'client' => 'expense.client_id', - 'custom_value1' => 'expense.custom_value1', - 'custom_value2' => 'expense.custom_value2', - 'custom_value3' => 'expense.custom_value3', - 'custom_value4' => 'expense.custom_value4', - 'currency' => 'expense.currency_id', - 'date' => 'expense.date', - 'exchange_rate' => 'expense.exchange_rate', - 'converted_amount' => 'expense.foreign_amount', - 'invoice_currency_id' => 'expense.invoice_currency_id', - 'payment_date' => 'expense.payment_date', - 'number' => 'expense.number', - 'payment_type_id' => 'expense.payment_type_id', - 'private_notes' => 'expense.private_notes', - 'project' => 'expense.project_id', - 'public_notes' => 'expense.public_notes', - 'tax_amount1' => 'expense.tax_amount1', - 'tax_amount2' => 'expense.tax_amount2', - 'tax_amount3' => 'expense.tax_amount3', - 'tax_name1' => 'expense.tax_name1', - 'tax_name2' => 'expense.tax_name2', - 'tax_name3' => 'expense.tax_name3', - 'tax_rate1' => 'expense.tax_rate1', - 'tax_rate2' => 'expense.tax_rate2', - 'tax_rate3' => 'expense.tax_rate3', - 'transaction_reference' => 'expense.transaction_reference', - 'vendor' => 'expense.vendor_id', - 'invoice' => 'expense.invoice_id', - 'user' => 'expense.user', - 'assigned_user' => 'expense.assigned_user', - ]; - - private array $decorate_keys = [ - 'client', - 'currency', - 'invoice', - 'category', - 'vendor', - 'project', - 'payment_type_id', - ]; - public function __construct(Company $company, array $input) { $this->company = $company; @@ -80,24 +36,38 @@ class ExpenseExport extends BaseExport $this->expense_transformer = new ExpenseTransformer(); } - public function run() + + 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(); + + $report = $query->cursor() + ->map(function ($resource) { + return $this->buildRow($resource); + })->toArray(); + + return array_merge(['columns' => $header], $report); + } + + private 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->input['report_keys'] = array_values($this->entity_keys); + $this->input['report_keys'] = array_values($this->expense_report_keys); } - //insert the header - $this->csv->insertOne($this->buildHeader()); - $query = Expense::query() ->with('client') ->withTrashed() @@ -106,6 +76,20 @@ class ExpenseExport extends BaseExport $query = $this->addDateRange($query); + return $query; + + } + + 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 ($expense) { $this->csv->insertOne($this->buildRow($expense)); @@ -122,7 +106,6 @@ class ExpenseExport extends BaseExport foreach (array_values($this->input['report_keys']) as $key) { $parts = explode('.', $key); - $keyval = array_search($key, $this->entity_keys); if (is_array($parts) && $parts[0] == 'expense' && array_key_exists($parts[1], $transformed_expense)) { $entity[$key] = $transformed_expense[$parts[1]]; diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index b641fb4b2390..9601d0edf144 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -1347,17 +1347,17 @@ nlog($csv); $reader = Reader::createFromString($csv); $reader->setHeaderOffset(0); - $res = $reader->fetchColumnByName('First Name'); + $res = $reader->fetchColumnByName('Contact First Name'); $res = iterator_to_array($res, true); $this->assertEquals('john', $res[1]); - $res = $reader->fetchColumnByName('Last Name'); + $res = $reader->fetchColumnByName('Contact Last Name'); $res = iterator_to_array($res, true); $this->assertEquals('doe', $res[1]); - $res = $reader->fetchColumnByName('Email'); + $res = $reader->fetchColumnByName('Contact Email'); $res = iterator_to_array($res, true); $this->assertEquals('john@doe.com', $res[1]); @@ -1696,10 +1696,10 @@ nlog($csv); $csv = $response->streamedContent(); - $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Amount')); - $this->assertEquals('Public', $this->getFirstValueByColumn($csv, 'Public Notes')); - $this->assertEquals('Private', $this->getFirstValueByColumn($csv, 'Private Notes')); - $this->assertEquals($this->user->present()->name(), $this->getFirstValueByColumn($csv, 'User')); + $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Expense Amount')); + $this->assertEquals('Public', $this->getFirstValueByColumn($csv, 'Expense Public Notes')); + $this->assertEquals('Private', $this->getFirstValueByColumn($csv, 'Expense Private Notes')); + $this->assertEquals($this->user->present()->name(), $this->getFirstValueByColumn($csv, 'Expense User')); $data = [ @@ -1758,8 +1758,8 @@ nlog($csv); $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('Vendor 1', $this->getFirstValueByColumn($csv, 'Vendor Name')); - $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Amount')); - $this->assertEquals('USD', $this->getFirstValueByColumn($csv, 'Currency')); + $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Expense Amount')); + $this->assertEquals('USD', $this->getFirstValueByColumn($csv, 'Expense Currency')); } diff --git a/tests/Feature/Export/ReportPreviewTest.php b/tests/Feature/Export/ReportPreviewTest.php index 26cf25872981..ad12f3497265 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -11,13 +11,18 @@ namespace Tests\Feature\Export; -use App\Export\CSV\ActivityExport; use Tests\TestCase; +use App\Models\Client; +use App\Models\Expense; +use App\Models\Document; use Tests\MockAccountData; use App\Utils\Traits\MakesHash; use App\Export\CSV\ClientExport; use App\Export\CSV\CreditExport; use App\Export\CSV\ContactExport; +use App\Export\CSV\ExpenseExport; +use App\Export\CSV\ActivityExport; +use App\Export\CSV\DocumentExport; use App\Jobs\Report\PreviewReport; use Illuminate\Support\Facades\Cache; use Illuminate\Routing\Middleware\ThrottleRequests; @@ -49,6 +54,66 @@ class ReportPreviewTest extends TestCase } + public function testExpenseJsonExport() + { + Expense::factory()->count(5)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->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/expenses?output=json', $data) + ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, ExpenseExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); +nlog($r); + } + + public function testDocumentJsonExport() + { + Document::factory()->count(5)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'documentable_type' => Client::class, + 'documentable_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/documents?output=json', $data) + ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, DocumentExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); +nlog($r); + } + public function testClientExportJson() { $data = [ @@ -107,12 +172,10 @@ class ReportPreviewTest extends TestCase $this->assertNull($p); - $r = Cache::pull('123'); $this->assertNotNull($r); - nlog($r); } public function testActivityCSVExportJson() From 19f400252b8131c12ff564dcd43d573f58faa1e4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 28 Aug 2023 15:13:55 +1000 Subject: [PATCH 05/17] Working on exports --- app/Export/CSV/BaseExport.php | 32 +++- app/Export/CSV/InvoiceExport.php | 144 +++++++----------- .../Export/ReportCsvGenerationTest.php | 49 +++--- tests/Feature/Export/ReportPreviewTest.php | 30 ++++ 4 files changed, 137 insertions(+), 118 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 741f21a2a335..848519c822e6 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -133,6 +133,7 @@ class BaseExport "private_notes" => "invoice.private_notes", "uses_inclusive_taxes" => "invoice.uses_inclusive_taxes", "is_amount_discount" => "invoice.is_amount_discount", + "discount" => "invoice.discount", "partial" => "invoice.partial", "partial_due_date" => "invoice.partial_due_date", "surcharge1" => "invoice.custom_surcharge1", @@ -143,6 +144,16 @@ class BaseExport "tax_amount" => "invoice.total_taxes", "assigned_user" => "invoice.assigned_user_id", "user" => "invoice.user_id", + "custom_value1" => "invoice.custom_value1", + "custom_value2" => "invoice.custom_value2", + "custom_value3" => "invoice.custom_value3", + "custom_value4" => "invoice.custom_value4", + 'tax_name1' => 'invoice.tax_name1', + 'tax_name2' => 'invoice.tax_name2', + 'tax_name3' => 'invoice.tax_name3', + 'tax_rate1' => 'invoice.tax_rate1', + 'tax_rate2' => 'invoice.tax_rate2', + 'tax_rate3' => 'invoice.tax_rate3', ]; protected array $recurring_invoice_report_keys = [ @@ -160,6 +171,7 @@ class BaseExport "private_notes" => "recurring_invoice.private_notes", "uses_inclusive_taxes" => "recurring_invoice.uses_inclusive_taxes", "is_amount_discount" => "recurring_invoice.is_amount_discount", + "discount" => "recurring_invoice.discount", "partial" => "recurring_invoice.partial", "partial_due_date" => "recurring_invoice.partial_due_date", "surcharge1" => "recurring_invoice.custom_surcharge1", @@ -171,7 +183,17 @@ class BaseExport "assigned_user" => "recurring_invoice.assigned_user_id", "user" => "recurring_invoice.user_id", "frequency_id" => "recurring_invoice.frequency_id", - "next_send_date" => "recurring_invoice.next_send_date" + "next_send_date" => "recurring_invoice.next_send_date", + "custom_value1" => "recurring_invoice.custom_value1", + "custom_value2" => "recurring_invoice.custom_value2", + "custom_value3" => "recurring_invoice.custom_value3", + "custom_value4" => "recurring_invoice.custom_value4", + 'tax_name1' => 'recurring_invoice.tax_name1', + 'tax_name2' => 'recurring_invoice.tax_name2', + 'tax_name3' => 'recurring_invoice.tax_name3', + 'tax_rate1' => 'recurring_invoice.tax_rate1', + 'tax_rate2' => 'recurring_invoice.tax_rate2', + 'tax_rate3' => 'recurring_invoice.tax_rate3', ]; protected array $purchase_order_report_keys = [ @@ -259,6 +281,12 @@ class BaseExport "tax_amount" => "quote.total_taxes", "assigned_user" => "quote.assigned_user_id", "user" => "quote.user_id", + 'tax_name1' => 'quote.tax_name1', + 'tax_name2' => 'quote.tax_name2', + 'tax_name3' => 'quote.tax_name3', + 'tax_rate1' => 'quote.tax_rate1', + 'tax_rate2' => 'quote.tax_rate2', + 'tax_rate3' => 'quote.tax_rate3', ]; protected array $credit_report_keys = [ @@ -957,7 +985,7 @@ class BaseExport } } - nlog($header); + // nlog($header); return $header; } diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php index 853da158d23d..dfaeff357c2e 100644 --- a/app/Export/CSV/InvoiceExport.php +++ b/app/Export/CSV/InvoiceExport.php @@ -20,6 +20,7 @@ use App\Libraries\MultiDB; use App\Export\CSV\BaseExport; use Illuminate\Support\Facades\App; use App\Transformers\InvoiceTransformer; +use Illuminate\Contracts\Database\Eloquent\Builder; class InvoiceExport extends BaseExport { @@ -29,56 +30,6 @@ class InvoiceExport extends BaseExport public Writer $csv; - public array $entity_keys = [ - 'amount' => 'amount', - 'balance' => 'balance', - 'client' => 'client_id', - '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_id' => 'currency_id', - 'payment_number' => 'payment_number', - 'payment_date' => 'payment_date', - 'payment_amount' => 'payment_amount', - 'method' => 'method', - ]; - - private array $decorate_keys = [ - 'country', - 'client', - 'currency_id', - 'status', - 'vendor', - 'project', - ]; - - public function __construct(Company $company, array $input) { $this->company = $company; @@ -86,24 +37,19 @@ class InvoiceExport 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->input['report_keys'] = array_values($this->entity_keys); + $this->input['report_keys'] = array_values($this->invoice_report_keys); } - //insert the header - $this->csv->insertOne($this->buildHeader()); - $query = Invoice::query() ->withTrashed() ->with('client') @@ -112,10 +58,43 @@ class InvoiceExport extends BaseExport $query = $this->addDateRange($query); - if(isset($this->input['status'])){ + if(isset($this->input['status'])) { $query = $this->addInvoiceStatusFilter($query, $this->input['status']); } + 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(); + + $report = $query->cursor() + ->map(function ($resource) { + return $this->buildRow($resource); + })->toArray(); + + return array_merge(['columns' => $header], $report); + } + + 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->csv->insertOne($this->buildRow($invoice)); @@ -131,24 +110,15 @@ class InvoiceExport extends BaseExport $entity = []; foreach (array_values($this->input['report_keys']) as $key) { - $keyval = array_search($key, $this->entity_keys); - if(!$keyval) { - $keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key; + $parts = explode('.', $key); + + if (is_array($parts) && $parts[0] == 'invoice' && array_key_exists($parts[1], $transformed_invoice)) { + $entity[$key] = $transformed_invoice[$parts[1]]; + } else { + $entity[$key] = $this->resolveKey($key, $invoice, $this->invoice_transformer); } - if(!$keyval) { - $keyval = $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]; - } - else { - $entity[$keyval] = $this->resolveKey($keyval, $invoice, $this->invoice_transformer); - } } return $this->decorateAdvancedFields($invoice, $entity); @@ -156,32 +126,22 @@ class InvoiceExport extends BaseExport private function decorateAdvancedFields(Invoice $invoice, array $entity) :array { - if (in_array('country_id', $this->input['report_keys'])) { - $entity['country'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : ''; + if (in_array('invoice.country_id', $this->input['report_keys'])) { + $entity['invoice.country_id'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : ''; } - if (in_array('currency_id', $this->input['report_keys'])) { - $entity['currency_id'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code; + if (in_array('invoice.currency_id', $this->input['report_keys'])) { + $entity['invoice.currency_id'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code; } - if (in_array('client_id', $this->input['report_keys'])) { - $entity['client'] = $invoice->client->present()->name(); + if (in_array('invoice.client_id', $this->input['report_keys'])) { + $entity['invoice.clientstatus_id'] = $invoice->client->present()->name(); } - if (in_array('status_id', $this->input['report_keys'])) { - $entity['status'] = $invoice->stringStatus($invoice->status_id); + if (in_array('invoice.status', $this->input['report_keys'])) { + $entity['invoice.status_id'] = $invoice->stringStatus($invoice->status_id); } - // $payment_exists = $invoice->payments()->exists(); - - // $entity['payment_number'] = $payment_exists ? $invoice->payments()->pluck('number')->implode(',') : ''; - - // $entity['payment_date'] = $payment_exists ? $invoice->payments()->pluck('date')->implode(',') : ''; - - // $entity['payment_amount'] = $payment_exists ? Number::formatMoney($invoice->payments()->sum('paymentables.amount'), $invoice->company) : ctrans('texts.unpaid'); - - // $entity['method'] = $payment_exists ? $invoice->payments()->first()->translatedType() : ""; - return $entity; } } diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index 9601d0edf144..30a262154e0f 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -849,7 +849,7 @@ nlog($csv); ])->post('/api/v1/reports/invoices', $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')); @@ -1491,30 +1491,31 @@ nlog($csv); $response->assertStatus(200); $csv = $response->streamedContent(); +nlog($csv); - $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Amount')); - $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Balance')); - $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Discount')); - $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'PO Number')); - $this->assertEquals('Public', $this->getFirstValueByColumn($csv, 'Public Notes')); - $this->assertEquals('Private', $this->getFirstValueByColumn($csv, 'Private Notes')); - $this->assertEquals('Terms', $this->getFirstValueByColumn($csv, 'Terms')); - $this->assertEquals('2020-01-01', $this->getFirstValueByColumn($csv, 'Date')); - $this->assertEquals('2021-01-02', $this->getFirstValueByColumn($csv, 'Due Date')); - $this->assertEquals('2021-01-03', $this->getFirstValueByColumn($csv, 'Partial Due Date')); - $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Partial/Deposit')); - $this->assertEquals('Custom 1', $this->getFirstValueByColumn($csv, 'Custom Value 1')); - $this->assertEquals('Custom 2', $this->getFirstValueByColumn($csv, 'Custom Value 2')); - $this->assertEquals('Custom 3', $this->getFirstValueByColumn($csv, 'Custom Value 3')); - $this->assertEquals('Custom 4', $this->getFirstValueByColumn($csv, 'Custom Value 4')); - $this->assertEquals('Footer', $this->getFirstValueByColumn($csv, 'Footer')); - $this->assertEquals('Tax 1', $this->getFirstValueByColumn($csv, 'Tax Name 1')); - $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Tax Rate 1')); - $this->assertEquals('Tax 2', $this->getFirstValueByColumn($csv, 'Tax Name 2')); - $this->assertEquals('20', $this->getFirstValueByColumn($csv, 'Tax Rate 2')); - $this->assertEquals('Tax 3', $this->getFirstValueByColumn($csv, 'Tax Name 3')); - $this->assertEquals('30', $this->getFirstValueByColumn($csv, 'Tax Rate 3')); - $this->assertEquals('Sent', $this->getFirstValueByColumn($csv, 'Status')); + $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Invoice Amount')); + $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Invoice Balance')); + $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Invoice Discount')); + $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Invoice PO Number')); + $this->assertEquals('Public', $this->getFirstValueByColumn($csv, 'Invoice Public Notes')); + $this->assertEquals('Private', $this->getFirstValueByColumn($csv, 'Invoice Private Notes')); + $this->assertEquals('Terms', $this->getFirstValueByColumn($csv, 'Invoice Terms')); + $this->assertEquals('2020-01-01', $this->getFirstValueByColumn($csv, 'Invoice Date')); + $this->assertEquals('2021-01-02', $this->getFirstValueByColumn($csv, 'Invoice Due Date')); + $this->assertEquals('2021-01-03', $this->getFirstValueByColumn($csv, 'Invoice Partial Due Date')); + $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Invoice Partial/Deposit')); + $this->assertEquals('Custom 1', $this->getFirstValueByColumn($csv, 'Invoice Custom Value 1')); + $this->assertEquals('Custom 2', $this->getFirstValueByColumn($csv, 'Invoice Custom Value 2')); + $this->assertEquals('Custom 3', $this->getFirstValueByColumn($csv, 'Invoice Custom Value 3')); + $this->assertEquals('Custom 4', $this->getFirstValueByColumn($csv, 'Invoice Custom Value 4')); + $this->assertEquals('Footer', $this->getFirstValueByColumn($csv, 'Invoice Footer')); + $this->assertEquals('Tax 1', $this->getFirstValueByColumn($csv, 'Invoice Tax Name 1')); + $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Invoice Tax Rate 1')); + $this->assertEquals('Tax 2', $this->getFirstValueByColumn($csv, 'Invoice Tax Name 2')); + $this->assertEquals('20', $this->getFirstValueByColumn($csv, 'Invoice Tax Rate 2')); + $this->assertEquals('Tax 3', $this->getFirstValueByColumn($csv, 'Invoice Tax Name 3')); + $this->assertEquals('30', $this->getFirstValueByColumn($csv, 'Invoice Tax Rate 3')); + $this->assertEquals('Sent', $this->getFirstValueByColumn($csv, 'Invoice Status')); } diff --git a/tests/Feature/Export/ReportPreviewTest.php b/tests/Feature/Export/ReportPreviewTest.php index ad12f3497265..26dd600260ad 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -23,6 +23,7 @@ use App\Export\CSV\ContactExport; use App\Export\CSV\ExpenseExport; use App\Export\CSV\ActivityExport; use App\Export\CSV\DocumentExport; +use App\Export\CSV\InvoiceExport; use App\Jobs\Report\PreviewReport; use Illuminate\Support\Facades\Cache; use Illuminate\Routing\Middleware\ThrottleRequests; @@ -54,6 +55,35 @@ class ReportPreviewTest extends TestCase } + public function testInvoiceJsonExport() + { + \App\Models\Invoice::factory()->count(5)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->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/invoices?output=json', $data) + ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, InvoiceExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); +nlog($r); + } + public function testExpenseJsonExport() { Expense::factory()->count(5)->create([ From 873c3730970c5ba36b8e7559d6f8109efff8fe7f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 28 Aug 2023 20:24:48 +1000 Subject: [PATCH 06/17] Updates for invoice status resolution --- app/Export/CSV/InvoiceExport.php | 5 +++-- app/Mail/Engine/PaymentEmailEngine.php | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php index dfaeff357c2e..246abe243f36 100644 --- a/app/Export/CSV/InvoiceExport.php +++ b/app/Export/CSV/InvoiceExport.php @@ -126,6 +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}") : ''; } @@ -135,11 +136,11 @@ class InvoiceExport extends BaseExport } if (in_array('invoice.client_id', $this->input['report_keys'])) { - $entity['invoice.clientstatus_id'] = $invoice->client->present()->name(); + $entity['invoice.client_id'] = $invoice->client->present()->name(); } if (in_array('invoice.status', $this->input['report_keys'])) { - $entity['invoice.status_id'] = $invoice->stringStatus($invoice->status_id); + $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id); } return $entity; diff --git a/app/Mail/Engine/PaymentEmailEngine.php b/app/Mail/Engine/PaymentEmailEngine.php index d60fa8939f3e..68f17926ea41 100644 --- a/app/Mail/Engine/PaymentEmailEngine.php +++ b/app/Mail/Engine/PaymentEmailEngine.php @@ -159,6 +159,7 @@ class PaymentEmailEngine extends BaseEmailEngine $data['$entity'] = ['value' => '', 'label' => ctrans('texts.payment')]; $data['$payment.amount'] = ['value' => Number::formatMoney($this->payment->amount, $this->client) ?: ' ', 'label' => ctrans('texts.amount')]; $data['$payment.refunded'] = ['value' => Number::formatMoney($this->payment->refunded, $this->client) ?: ' ', 'label' => ctrans('texts.refund')]; + $data['$payment.unapplied'] = ['value' => Number::formatMoney(($this->payment->amount - $this->payment->refunded - $this->payment->applied), $this->client) ?: ' ', 'label' => ctrans('texts.refund')]; $data['$amount'] = &$data['$payment.amount']; $data['$payment.date'] = ['value' => $this->translateDate($this->payment->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.payment_date')]; $data['$transaction_reference'] = ['value' => $this->payment->transaction_reference, 'label' => ctrans('texts.transaction_reference')]; From 74645a3ae5f21b964d695a4f87b9e422366ae1c4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 28 Aug 2023 20:26:11 +1000 Subject: [PATCH 07/17] Fixes for report previews --- tests/Feature/Export/ReportPreviewTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Feature/Export/ReportPreviewTest.php b/tests/Feature/Export/ReportPreviewTest.php index 26dd600260ad..7beecc2eced1 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -60,6 +60,7 @@ class ReportPreviewTest extends TestCase \App\Models\Invoice::factory()->count(5)->create([ 'company_id' => $this->company->id, 'user_id' => $this->user->id, + 'client_id' => $this->client->id, ]); $data = [ From 659b6b8c8fd2c5897d447745c322f6be04636822 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 28 Aug 2023 22:44:04 +1000 Subject: [PATCH 08/17] Fixes for Invoice Item Exports --- app/Export/CSV/BaseExport.php | 28 ++- app/Export/CSV/InvoiceExport.php | 2 +- app/Export/CSV/InvoiceItemExport.php | 193 ++++++++---------- .../Reports/InvoiceItemReportController.php | 25 ++- .../Reports/InvoiceReportController.php | 25 ++- .../Export/ReportCsvGenerationTest.php | 18 +- tests/Feature/Export/ReportPreviewTest.php | 34 ++- 7 files changed, 192 insertions(+), 133 deletions(-) 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() From e168f5725eddbc5ecd28665313819e348135966c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 29 Aug 2023 00:04:32 +1000 Subject: [PATCH 09/17] Fixes for Quote Item Exports --- app/Export/CSV/BaseExport.php | 4 - app/Export/CSV/InvoiceItemExport.php | 36 +--- app/Export/CSV/QuoteItemExport.php | 174 +++++++----------- .../Export/ReportCsvGenerationTest.php | 16 +- tests/Feature/Export/ReportPreviewTest.php | 6 +- 5 files changed, 80 insertions(+), 156 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index a52b657b1f72..b9c25da20741 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -200,10 +200,6 @@ class BaseExport 'amount' => 'purchase_order.amount', 'balance' => 'purchase_order.balance', 'vendor' => 'purchase_order.vendor_id', - // 'custom_surcharge1' => 'purchase_order.custom_surcharge1', - // 'custom_surcharge2' => 'purchase_order.custom_surcharge2', - // 'custom_surcharge3' => 'purchase_order.custom_surcharge3', - // 'custom_surcharge4' => 'purchase_order.custom_surcharge4', 'custom_value1' => 'purchase_order.custom_value1', 'custom_value2' => 'purchase_order.custom_value2', 'custom_value3' => 'purchase_order.custom_value3', diff --git a/app/Export/CSV/InvoiceItemExport.php b/app/Export/CSV/InvoiceItemExport.php index 33fb942c846e..d7db46889269 100644 --- a/app/Export/CSV/InvoiceItemExport.php +++ b/app/Export/CSV/InvoiceItemExport.php @@ -12,7 +12,6 @@ namespace App\Export\CSV; use App\Libraries\MultiDB; -use App\Models\Client; use App\Models\Company; use App\Models\Invoice; use App\Transformers\InvoiceTransformer; @@ -126,10 +125,6 @@ class InvoiceItemExport extends BaseExport $key = str_replace("item.", "", $key); - // $keyval = $key; - - // $key = str_replace("custom_value", "invoice", $key); - if($key == 'type_id') $key = 'type'; @@ -144,26 +139,12 @@ class InvoiceItemExport extends BaseExport } } } -nlog($item_array); - // $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); - - // 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->storage_array[] = $entity; - // $this->csv->insertOne($entity); } } @@ -174,15 +155,7 @@ nlog($item_array); $entity = []; foreach (array_values($this->input['report_keys']) as $key) { - // $keyval = array_search($key, $this->entity_keys); - - // if(!$keyval) { - // $keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key; - // } - - // if(!$keyval) { - // $keyval = $key; - // } + $parts = explode('.', $key); if(is_array($parts) && $parts[0] == 'item') @@ -193,10 +166,7 @@ nlog($item_array); }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 { + else { $entity[$key] = $this->resolveKey($key, $invoice, $this->invoice_transformer); } } diff --git a/app/Export/CSV/QuoteItemExport.php b/app/Export/CSV/QuoteItemExport.php index 9af20df7b5ca..a2f78d16ce41 100644 --- a/app/Export/CSV/QuoteItemExport.php +++ b/app/Export/CSV/QuoteItemExport.php @@ -16,6 +16,7 @@ use App\Models\Company; use App\Models\Quote; use App\Transformers\QuoteTransformer; use App\Utils\Ninja; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; use League\Csv\Writer; @@ -28,63 +29,7 @@ class QuoteItemExport extends BaseExport public Writer $csv; - public array $entity_keys = [ - 'amount' => 'amount', - 'balance' => 'balance', - 'client' => 'client_id', - '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', - 'cost' => 'item.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', - 'quote1' => 'item.custom_value1', - 'quote2' => 'item.custom_value2', - 'quote3' => 'item.custom_value3', - 'quote4' => 'item.custom_value4', - 'tax_category' => 'item.tax_id', - 'type' => 'item.type_id', - ]; + private array $storage_array; private array $decorate_keys = [ 'client', @@ -98,37 +43,70 @@ class QuoteItemExport extends BaseExport $this->quote_transformer = new QuoteTransformer(); } - 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)); + + if (count($this->input['report_keys']) == 0) { + $this->input['report_keys'] = array_values($this->mergeItemsKeys('quote_report_keys')); + } + $query = Quote::query() + ->withTrashed() + ->with('client')->where('company_id', $this->company->id) + ->where('is_deleted', 0); + + $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() + { + //load the CSV document from a string $this->csv = Writer::createFromString(); - if (count($this->input['report_keys']) == 0) { - $this->input['report_keys'] = array_values($this->entity_keys); - } + $query = $this->init(); //insert the header $this->csv->insertOne($this->buildHeader()); - $query = Quote::query() - ->withTrashed() - ->with('client')->where('company_id', $this->company->id) - ->where('is_deleted', 0); - - $query = $this->addDateRange($query); - + $query->cursor() ->each(function ($quote) { $this->iterateItems($quote); }); + $this->csv->insertAll($this->storage_array); + return $this->csv->toString(); + } private function iterateItems(Quote $quote) @@ -137,51 +115,34 @@ class QuoteItemExport extends BaseExport $transformed_items = []; - $transformed_items = []; - foreach ($quote->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 = str_replace("custom_value", "quote", $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] = ''; } } } - - $entity = []; - - 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] = ""; - } - } - + $transformed_items = array_merge($transformed_quote, $item_array); $entity = $this->decorateAdvancedFields($quote, $transformed_items); - $this->csv->insertOne($entity); + $this->storage_array[] = $entity; } } @@ -192,26 +153,23 @@ class QuoteItemExport extends BaseExport $entity = []; foreach (array_values($this->input['report_keys']) as $key) { - $keyval = array_search($key, $this->entity_keys); + + $parts = explode('.', $key); - if(!$keyval) { - $keyval = array_search(str_replace("quote.", "", $key), $this->entity_keys) ?? $key; + if(is_array($parts) && $parts[0] == 'item') { + continue; } - if(!$keyval) { - $keyval = $key; - } - - if (array_key_exists($key, $transformed_quote)) { - $entity[$keyval] = $transformed_quote[$key]; - } elseif (array_key_exists($keyval, $transformed_quote)) { - $entity[$keyval] = $transformed_quote[$keyval]; - } - else { - $entity[$keyval] = $this->resolveKey($keyval, $quote, $this->quote_transformer); + if (is_array($parts) && $parts[0] == 'quote' && array_key_exists($parts[1], $transformed_quote)) { + $entity[$key] = $transformed_quote[$parts[1]]; + } elseif (array_key_exists($key, $transformed_quote)) { + $entity[$key] = $transformed_quote[$key]; + } else { + $entity[$key] = $this->resolveKey($key, $quote, $this->quote_transformer); } } + return $this->decorateAdvancedFields($quote, $entity); } private function decorateAdvancedFields(Quote $quote, array $entity) :array diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index 27b4f48041d3..a6868262d10f 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -1082,15 +1082,15 @@ nlog($csv); $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Quote Number')); - $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 33c65b477770..50e7b3e2eb7c 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -82,7 +82,7 @@ class ReportPreviewTest extends TestCase $this->assertNotNull($r); - nlog($r); + //nlog($r); } @@ -143,7 +143,7 @@ class ReportPreviewTest extends TestCase $r = Cache::pull('123'); $this->assertNotNull($r); -nlog($r); +//nlog($r); } public function testDocumentJsonExport() @@ -174,7 +174,7 @@ nlog($r); $r = Cache::pull('123'); $this->assertNotNull($r); -nlog($r); +//nlog($r); } public function testClientExportJson() From 4537e1be179ee584cf1b093b1fde1bbb20430d5b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 29 Aug 2023 08:53:25 +1000 Subject: [PATCH 10/17] Static analysis cleanup --- app/Http/Controllers/BankTransactionController.php | 10 ++++++++-- app/Repositories/BaseRepository.php | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/BankTransactionController.php b/app/Http/Controllers/BankTransactionController.php index 69261d4882db..acfdf62c495a 100644 --- a/app/Http/Controllers/BankTransactionController.php +++ b/app/Http/Controllers/BankTransactionController.php @@ -71,15 +71,21 @@ class BankTransactionController extends BaseController public function create(CreateBankTransactionRequest $request) { - $bank_transaction = BankTransactionFactory::create(auth()->user()->company()->id, auth()->user()->id); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $bank_transaction = BankTransactionFactory::create($user->company()->id, $user->id); return $this->itemResponse($bank_transaction); } public function store(StoreBankTransactionRequest $request) { + /** @var \App\Models\User $user */ + $user = auth()->user(); + //stub to store the model - $bank_transaction = $this->bank_transaction_repo->save($request->all(), BankTransactionFactory::create(auth()->user()->company()->id, auth()->user()->id)); + $bank_transaction = $this->bank_transaction_repo->save($request->all(), BankTransactionFactory::create($user->company()->id, $user->id)); return $this->itemResponse($bank_transaction); } diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index b2c5aaad97d2..f392d24f3693 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -153,7 +153,7 @@ class BaseRepository $model->client_id = $data['client_id']; } - $client = Client::with('group_settings')->where('id', $model->client_id)->withTrashed()->firstOrFail(); + $client = Client::query()->with('group_settings')->where('id', $model->client_id)->withTrashed()->firstOrFail(); $state = []; From 4b5dc3b43ffaedb7d5305f1e893218f49141cce1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 29 Aug 2023 13:00:15 +1000 Subject: [PATCH 11/17] Additional tests for reminders and custom labels --- tests/Feature/ReminderTest.php | 217 +++++++++++++++++++++++++++++++-- 1 file changed, 208 insertions(+), 9 deletions(-) diff --git a/tests/Feature/ReminderTest.php b/tests/Feature/ReminderTest.php index c55c302d8c22..7522e5822d6a 100644 --- a/tests/Feature/ReminderTest.php +++ b/tests/Feature/ReminderTest.php @@ -11,15 +11,23 @@ namespace Tests\Feature; -use App\Jobs\Util\ReminderJob; -use App\Models\Invoice; -use App\Utils\Traits\MakesHash; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; -use Illuminate\Routing\Middleware\ThrottleRequests; -use Illuminate\Support\Carbon; -use Tests\MockAccountData; use Tests\TestCase; +use App\Models\User; +use App\Models\Client; +use App\Models\Account; +use App\Models\Company; +use App\Models\Invoice; +use Tests\MockAccountData; +use App\Models\CompanyToken; +use App\Models\ClientContact; +use App\Jobs\Util\ReminderJob; +use Illuminate\Support\Carbon; +use App\Utils\Traits\MakesHash; +use App\DataMapper\CompanySettings; +use App\Factory\CompanyUserFactory; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Routing\Middleware\ThrottleRequests; +use Illuminate\Foundation\Testing\DatabaseTransactions; /** * @test @@ -49,10 +57,201 @@ class ReminderTest extends TestCase $this->withoutExceptionHandling(); } + public $company; + + public $user; + + public $payload; + + public $account; + + public $client; + + public $token; + + public $cu; + + public $invoice; + + private function buildData($settings = null) + { + $this->account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000, + ]); + + $this->account->num_users = 3; + $this->account->save(); + + $this->user = User::factory()->create([ + 'account_id' => $this->account->id, + 'confirmation_code' => 'xyz123', + 'email' => $this->faker->unique()->safeEmail(), + ]); + + if(!$settings) + { + $settings = CompanySettings::defaults(); + $settings->client_online_payment_notification = false; + $settings->client_manual_payment_notification = false; + } + + $this->company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings, + ]); + + $this->company->settings = $settings; + $this->company->save(); + + $this->cu = CompanyUserFactory::create($this->user->id, $this->company->id, $this->account->id); + $this->cu->is_owner = true; + $this->cu->is_admin = true; + $this->cu->is_locked = false; + $this->cu->save(); + + $this->token = \Illuminate\Support\Str::random(64); + + $company_token = new CompanyToken; + $company_token->user_id = $this->user->id; + $company_token->company_id = $this->company->id; + $company_token->account_id = $this->account->id; + $company_token->name = 'test token'; + $company_token->token = $this->token; + $company_token->is_system = true; + + $company_token->save(); + + $this->client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'is_deleted' => 0, + 'name' => 'bob', + 'address1' => '1234', + 'balance' => 100, + 'paid_to_date' => 50, + ]); + + ClientContact::factory()->create([ + 'user_id' => $this->user->id, + 'client_id' => $this->client->id, + 'company_id' => $this->company->id, + 'is_primary' => 1, + 'first_name' => 'john', + 'last_name' => 'doe', + 'email' => 'john@doe.com' + ]); + + $this->invoice = Invoice::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $this->client->id, + 'date' => now()->addSeconds($this->client->timezone_offset())->format('Y-m-d'), + 'next_send_date' => null, + 'due_date' => Carbon::now()->addSeconds($this->client->timezone_offset())->addDays(5)->format('Y-m-d'), + 'last_sent_date' => now()->addSeconds($this->client->timezone_offset()), + 'reminder_last_sent' => null, + 'status_id' => 2, + 'amount' => 10, + 'balance' => 10, + ]); + + } + + public function testsForTranslationsInReminders() + { + + $translations = new \stdClass; + $translations->late_fee_added = "Fee added :date"; + + $settings = $this->company->settings; + $settings->enable_reminder1 = true; + $settings->schedule_reminder1 = 'after_invoice_date'; + $settings->num_days_reminder1 = 1; + $settings->enable_reminder2 = true; + $settings->schedule_reminder2 = 'after_invoice_date'; + $settings->num_days_reminder2 = 2; + $settings->enable_reminder3 = true; + $settings->schedule_reminder3 = 'after_invoice_date'; + $settings->num_days_reminder3 = 3; + $settings->timezone_id = '29'; + $settings->entity_send_time = 0; + $settings->endless_reminder_frequency_id = ''; + $settings->enable_reminder_endless = false; + $settings->translations = $translations; + $settings->late_fee_amount1 = '101'; + $settings->late_fee_amount2 = '102'; + $settings->late_fee_amount3 = '103'; + + $this->buildData(($settings)); + + $this->assertEquals("Fee added :date", $this->company->settings->translations->late_fee_added); + $fetched_settings = $this->client->getMergedSettings(); + $this->assertEquals("Fee added :date", $fetched_settings->translations->late_fee_added); + + $this->invoice->service()->setReminder($settings)->save(); + + $this->invoice = $this->invoice->fresh(); + + $this->assertEquals(now()->addSeconds($this->client->timezone_offset())->format('Y-m-d'), $this->invoice->date); + $this->assertNotNull($this->invoice->next_send_date); + $this->assertEquals(now()->addDay()->addSeconds($this->client->timezone_offset())->format('Y-m-d 00:00:00'), $this->invoice->next_send_date); + + $this->travelTo(now()->addDay()->startOfDay()->addHour()); + + (new ReminderJob())->handle(); + $this->invoice = $this->invoice->fresh(); + $this->assertNotNull($this->invoice->reminder1_sent); + $this->assertNotNull($this->invoice->reminder_last_sent); + + $fee = collect($this->invoice->line_items)->where('type_id', 5)->first(); + + $this->assertEquals(101, $fee->cost); + $this->assertEquals('Fee added '.now()->format('d/M/Y'), $fee->notes); + + $this->travelTo(now()->addDay()->startOfDay()->addHour()); + + (new ReminderJob())->handle(); + $this->invoice = $this->invoice->fresh(); + $this->assertNotNull($this->invoice->reminder2_sent); + $this->assertNotNull($this->invoice->reminder_last_sent); + + $fee = collect($this->invoice->line_items)->where('cost', 102)->first(); + + $this->assertEquals(102, $fee->cost); + $this->assertEquals('Fee added '.now()->format('d/M/Y'), $fee->notes); + + $this->travelTo(now()->addDay()->startOfDay()->addHour()); + + (new ReminderJob())->handle(); + $this->invoice = $this->invoice->fresh(); + $this->assertNotNull($this->invoice->reminder3_sent); + $this->assertNotNull($this->invoice->reminder_last_sent); + + $fee = collect($this->invoice->line_items)->where('cost', 103)->first(); + + $this->assertEquals(103, $fee->cost); + $this->assertEquals('Fee added '.now()->format('d/M/Y'), $fee->notes); + + + + + + + + // $this->travelTo(now()->addHours(1)); +// } + + + $this->travelBack(); + + } public function testForReminderFiringCorrectly() { - + $this->invoice->status_id = 2; + $this->invoice->amount = 10; + $this->invoice->balance = 10; $this->invoice->next_send_date = null; $this->invoice->date = now()->format('Y-m-d'); $this->invoice->last_sent_date = now(); From 7b6515c8c254bf210ffed5ea7a309cf44b4e26b6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 29 Aug 2023 13:40:35 +1000 Subject: [PATCH 12/17] Fixes for bank transaction delinking --- app/Repositories/ExpenseRepository.php | 5 +++++ app/Services/Payment/DeletePayment.php | 1 + 2 files changed, 6 insertions(+) diff --git a/app/Repositories/ExpenseRepository.php b/app/Repositories/ExpenseRepository.php index b1b8794b98b7..6af0d453c6b6 100644 --- a/app/Repositories/ExpenseRepository.php +++ b/app/Repositories/ExpenseRepository.php @@ -113,6 +113,11 @@ class ExpenseRepository extends BaseRepository $expense->saveQuietly(); $expense->transaction->expense_id = $exp_ids; + + if(strlen($exp_ids) <= 2) { + $expense->transaction->status_id = 1; + } + $expense->transaction->saveQuietly(); } diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index 27119ec5ca12..023e8148f34f 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -60,6 +60,7 @@ class DeletePayment BankTransaction::query()->where('payment_id', $this->payment->id)->cursor()->each(function ($bt){ $bt->payment_id = null; + $bt->status_id = 1; $bt->save(); }); From c4c5547281ed085af4b96c5796d23b4e26963944 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 29 Aug 2023 17:50:25 +1000 Subject: [PATCH 13/17] Working on payment exports --- app/Export/CSV/PaymentExport.php | 61 ++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php index dab8054c132f..8299cc4b59d6 100644 --- a/app/Export/CSV/PaymentExport.php +++ b/app/Export/CSV/PaymentExport.php @@ -16,6 +16,7 @@ use App\Models\Company; use App\Models\Payment; use App\Transformers\PaymentTransformer; use App\Utils\Ninja; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; use League\Csv\Writer; @@ -68,24 +69,19 @@ class PaymentExport extends BaseExport $this->entity_transformer = new PaymentTransformer(); } - public function run() + private 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->input['report_keys'] = array_values($this->entity_keys); + $this->input['report_keys'] = array_values($this->payment_report_keys); } - //insert the header - $this->csv->insertOne($this->buildHeader()); - $query = Payment::query() ->withTrashed() ->where('company_id', $this->company->id) @@ -93,6 +89,39 @@ class PaymentExport 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(); + + $report = $query->cursor() + ->map(function ($resource) { + return $this->buildRow($resource); + })->toArray(); + + return array_merge(['columns' => $header], $report); + + + } + + 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 ($entity) { $this->csv->insertOne($this->buildRow($entity)); @@ -108,15 +137,17 @@ class PaymentExport 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); + + $parts = explode('.', $key); - if(!$keyval) { - $keyval = array_search(str_replace("payment.", "", $key), $this->entity_keys) ?? $key; - } + // if(!$keyval) { + // $keyval = array_search(str_replace("payment.", "", $key), $this->entity_keys) ?? $key; + // } - if(!$keyval) { - $keyval = $key; - } + // if(!$keyval) { + // $keyval = $key; + // } if (array_key_exists($key, $transformed_entity)) { $entity[$keyval] = $transformed_entity[$key]; From 9d55ff2d49d7559239d6146cdefb00cf71b2ffe6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 29 Aug 2023 18:46:38 +1000 Subject: [PATCH 14/17] Fixes for payment exports --- app/Export/CSV/PaymentExport.php | 89 +++++++++---------- .../Reports/ExpenseReportController.php | 26 ++++-- .../Reports/PaymentReportController.php | 25 ++++-- tests/Feature/Export/ReportApiTest.php | 2 - .../Export/ReportCsvGenerationTest.php | 11 ++- tests/Feature/Export/ReportPreviewTest.php | 34 ++++++- 6 files changed, 117 insertions(+), 70 deletions(-) diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php index 8299cc4b59d6..930be240b75f 100644 --- a/app/Export/CSV/PaymentExport.php +++ b/app/Export/CSV/PaymentExport.php @@ -28,39 +28,39 @@ class PaymentExport extends BaseExport public Writer $csv; - public array $entity_keys = [ - 'amount' => 'amount', - 'applied' => 'applied', - 'client' => 'client_id', - 'currency' => 'currency_id', - 'custom_value1' => 'custom_value1', - 'custom_value2' => 'custom_value2', - 'custom_value3' => 'custom_value3', - 'custom_value4' => 'custom_value4', - 'date' => 'date', - 'exchange_currency' => 'exchange_currency_id', - 'gateway' => 'gateway_type_id', - 'number' => 'number', - 'private_notes' => 'private_notes', - 'project' => 'project_id', - 'refunded' => 'refunded', - 'status' => 'status_id', - 'transaction_reference' => 'transaction_reference', - 'type' => 'type_id', - 'vendor' => 'vendor_id', - 'invoices' => 'invoices', - ]; + // public array $entity_keys = [ + // 'amount' => 'amount', + // 'applied' => 'applied', + // 'client' => 'client_id', + // 'currency' => 'currency_id', + // 'custom_value1' => 'custom_value1', + // 'custom_value2' => 'custom_value2', + // 'custom_value3' => 'custom_value3', + // 'custom_value4' => 'custom_value4', + // 'date' => 'date', + // 'exchange_currency' => 'exchange_currency_id', + // 'gateway' => 'gateway_type_id', + // 'number' => 'number', + // 'private_notes' => 'private_notes', + // 'project' => 'project_id', + // 'refunded' => 'refunded', + // 'status' => 'status_id', + // 'transaction_reference' => 'transaction_reference', + // 'type' => 'type_id', + // 'vendor' => 'vendor_id', + // 'invoices' => 'invoices', + // ]; - private array $decorate_keys = [ - 'vendor', - 'status', - 'project', - 'client', - 'currency', - 'exchange_currency', - 'type', - 'invoices', - ]; + // private array $decorate_keys = [ + // 'vendor', + // 'status', + // 'project', + // 'client', + // 'currency', + // 'exchange_currency', + // 'type', + // 'invoices', + // ]; public function __construct(Company $company, array $input) { @@ -137,26 +137,17 @@ class PaymentExport extends BaseExport $entity = []; foreach (array_values($this->input['report_keys']) as $key) { - // $keyval = array_search($key, $this->entity_keys); - + $parts = explode('.', $key); - // if(!$keyval) { - // $keyval = array_search(str_replace("payment.", "", $key), $this->entity_keys) ?? $key; - // } - - // if(!$keyval) { - // $keyval = $key; - // } - - if (array_key_exists($key, $transformed_entity)) { - $entity[$keyval] = $transformed_entity[$key]; - } elseif (array_key_exists($keyval, $transformed_entity)) { - $entity[$keyval] = $transformed_entity[$keyval]; - } - else { - $entity[$keyval] = $this->resolveKey($keyval, $payment, $this->entity_transformer); + if (is_array($parts) && $parts[0] == 'payment' && array_key_exists($parts[1], $transformed_entity)) { + $entity[$key] = $transformed_entity[$parts[1]]; + } elseif (array_key_exists($key, $transformed_entity)) { + $entity[$key] = $transformed_entity[$key]; + } else { + $entity[$key] = $this->resolveKey($key, $payment, $this->entity_transformer); } + } return $this->decorateAdvancedFields($payment, $entity); diff --git a/app/Http/Controllers/Reports/ExpenseReportController.php b/app/Http/Controllers/Reports/ExpenseReportController.php index 2953f653a391..f1276cdaffdd 100644 --- a/app/Http/Controllers/Reports/ExpenseReportController.php +++ b/app/Http/Controllers/Reports/ExpenseReportController.php @@ -11,13 +11,14 @@ namespace App\Http\Controllers\Reports; +use App\Models\Client; +use Illuminate\Http\Response; +use App\Utils\Traits\MakesHash; +use App\Jobs\Report\SendToAdmin; use App\Export\CSV\ExpenseExport; +use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; -use App\Jobs\Report\SendToAdmin; -use App\Models\Client; -use App\Utils\Traits\MakesHash; -use Illuminate\Http\Response; class ExpenseReportController extends BaseController { @@ -63,14 +64,27 @@ class ExpenseReportController 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(), ExpenseExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), ExpenseExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } // expect a list of visible fields, or use the default - $export = new ExpenseExport(auth()->user()->company(), $request->all()); + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), ExpenseExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + $export = new ExpenseExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/app/Http/Controllers/Reports/PaymentReportController.php b/app/Http/Controllers/Reports/PaymentReportController.php index 44ea8d5e2059..cca6925ef98a 100644 --- a/app/Http/Controllers/Reports/PaymentReportController.php +++ b/app/Http/Controllers/Reports/PaymentReportController.php @@ -11,13 +11,14 @@ namespace App\Http\Controllers\Reports; +use App\Models\Client; +use Illuminate\Http\Response; +use App\Utils\Traits\MakesHash; +use App\Jobs\Report\SendToAdmin; use App\Export\CSV\PaymentExport; +use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; -use App\Jobs\Report\SendToAdmin; -use App\Models\Client; -use App\Utils\Traits\MakesHash; -use Illuminate\Http\Response; class PaymentReportController extends BaseController { @@ -63,14 +64,26 @@ class PaymentReportController 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(), PaymentExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), PaymentExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } // expect a list of visible fields, or use the default - $export = new PaymentExport(auth()->user()->company(), $request->all()); + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), PaymentExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + $export = new PaymentExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/tests/Feature/Export/ReportApiTest.php b/tests/Feature/Export/ReportApiTest.php index 6b91468241e9..2846cdd70799 100644 --- a/tests/Feature/Export/ReportApiTest.php +++ b/tests/Feature/Export/ReportApiTest.php @@ -59,8 +59,6 @@ class ReportApiTest extends TestCase } - - public function testUserSalesReportApiRoute() { $data = [ diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index a6868262d10f..d2b236e91d4b 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -649,11 +649,11 @@ class ReportCsvGenerationTest extends TestCase $csv = $response->streamedContent(); - $this->assertEquals(500, $this->getFirstValueByColumn($csv, 'Amount')); - $this->assertEquals(0, $this->getFirstValueByColumn($csv, 'Applied')); - $this->assertEquals(0, $this->getFirstValueByColumn($csv, 'Refunded')); - $this->assertEquals('2020-01-01', $this->getFirstValueByColumn($csv, 'Date')); - $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Transaction Reference')); + $this->assertEquals(500, $this->getFirstValueByColumn($csv, 'Payment Amount')); + $this->assertEquals(0, $this->getFirstValueByColumn($csv, 'Payment Applied')); + $this->assertEquals(0, $this->getFirstValueByColumn($csv, 'Payment Refunded')); + $this->assertEquals('2020-01-01', $this->getFirstValueByColumn($csv, 'Payment Date')); + $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Payment Transaction Reference')); } @@ -1491,7 +1491,6 @@ nlog($csv); $response->assertStatus(200); $csv = $response->streamedContent(); -nlog($csv); $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Invoice Amount')); $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Invoice Balance')); diff --git a/tests/Feature/Export/ReportPreviewTest.php b/tests/Feature/Export/ReportPreviewTest.php index 50e7b3e2eb7c..df9163834c58 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -21,9 +21,10 @@ use App\Export\CSV\ClientExport; use App\Export\CSV\CreditExport; use App\Export\CSV\ContactExport; use App\Export\CSV\ExpenseExport; +use App\Export\CSV\InvoiceExport; +use App\Export\CSV\PaymentExport; use App\Export\CSV\ActivityExport; use App\Export\CSV\DocumentExport; -use App\Export\CSV\InvoiceExport; use App\Jobs\Report\PreviewReport; use Illuminate\Support\Facades\Cache; use Illuminate\Routing\Middleware\ThrottleRequests; @@ -54,6 +55,37 @@ class ReportPreviewTest extends TestCase } + + public function testPaymentJsonExport() + { + \App\Models\Payment::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/payments?output=json', $data) + ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, PaymentExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); + + } + public function testInvoiceItemJsonExport() { \App\Models\Invoice::factory()->count(5)->create([ From 514bd47da9bcfc9c5882959cfb99707446b81f22 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 29 Aug 2023 19:15:36 +1000 Subject: [PATCH 15/17] Fixes for product json exports --- app/Export/CSV/BaseExport.php | 30 +++++++- app/Export/CSV/ProductExport.php | 72 ++++++++++--------- .../Reports/ProductReportController.php | 26 +++++-- .../Export/ReportCsvGenerationTest.php | 2 +- tests/Feature/Export/ReportPreviewTest.php | 31 ++++++++ 5 files changed, 118 insertions(+), 43 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index b9c25da20741..5691958041f2 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -228,6 +228,26 @@ class BaseExport 'currency_id' => 'purchase_order.currency_id', ]; + protected array $product_report_keys = [ + 'project' => 'project_id', + 'vendor' => 'vendor_id', + 'custom_value1' => 'custom_value1', + 'custom_value2' => 'custom_value2', + 'custom_value3' => 'custom_value3', + 'custom_value4' => 'custom_value4', + 'product_key' => 'product_key', + 'notes' => 'notes', + 'cost' => 'cost', + 'price' => 'price', + 'quantity' => 'quantity', + 'tax_rate1' => 'tax_rate1', + 'tax_rate2' => 'tax_rate2', + 'tax_rate3' => 'tax_rate3', + 'tax_name1' => 'tax_name1', + 'tax_name2' => 'tax_name2', + 'tax_name3' => 'tax_name3', + ]; + protected array $item_report_keys = [ "quantity" => "item.quantity", "cost" => "item.cost", @@ -881,10 +901,10 @@ class BaseExport $header = []; foreach ($this->input['report_keys'] as $value) { - + $key = array_search($value, $this->entity_keys); $original_key = $key; - + // nlog("{$key} => {$value}"); $prefix = ''; @@ -943,6 +963,11 @@ class BaseExport $key = array_search($value, $this->purchase_order_report_keys); } + if(!$key) { + $prefix = ''; + $key = array_search($value, $this->product_report_keys); + } + if(!$key) { $prefix = ''; } @@ -959,6 +984,7 @@ class BaseExport $key = str_replace('contact.', '', $key); $key = str_replace('payment.', '', $key); $key = str_replace('expense.', '', $key); + $key = str_replace('product.', '', $key); if(stripos($value, 'custom_value') !== false) { diff --git a/app/Export/CSV/ProductExport.php b/app/Export/CSV/ProductExport.php index f1a24d49b48b..a4c5bbf7535c 100644 --- a/app/Export/CSV/ProductExport.php +++ b/app/Export/CSV/ProductExport.php @@ -17,6 +17,7 @@ use App\Models\Document; use App\Models\Product; use App\Transformers\ProductTransformer; use App\Utils\Ninja; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; use League\Csv\Writer; @@ -28,31 +29,6 @@ class ProductExport extends BaseExport public Writer $csv; - public array $entity_keys = [ - 'project' => 'project_id', - 'vendor' => 'vendor_id', - 'custom_value1' => 'custom_value1', - 'custom_value2' => 'custom_value2', - 'custom_value3' => 'custom_value3', - 'custom_value4' => 'custom_value4', - 'product_key' => 'product_key', - 'notes' => 'notes', - 'cost' => 'cost', - 'price' => 'price', - 'quantity' => 'quantity', - 'tax_rate1' => 'tax_rate1', - 'tax_rate2' => 'tax_rate2', - 'tax_rate3' => 'tax_rate3', - 'tax_name1' => 'tax_name1', - 'tax_name2' => 'tax_name2', - 'tax_name3' => 'tax_name3', - ]; - - private array $decorate_keys = [ - 'vendor', - 'project', - ]; - public function __construct(Company $company, array $input) { $this->company = $company; @@ -60,24 +36,37 @@ class ProductExport extends BaseExport $this->entity_transformer = new ProductTransformer(); } - public function run() + 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(); + + $report = $query->cursor() + ->map(function ($resource) { + return $this->buildRow($resource); + })->toArray(); + + return array_merge(['columns' => $header], $report); + } + + private 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->input['report_keys'] = array_values($this->entity_keys); + $this->input['report_keys'] = array_values($this->product_report_keys); } - //insert the header - $this->csv->insertOne($this->buildHeader()); - $query = Product::query() ->withTrashed() ->where('company_id', $this->company->id) @@ -85,6 +74,21 @@ class ProductExport extends BaseExport $query = $this->addDateRange($query); + return $query; + + } + + 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 ($entity) { $this->csv->insertOne($this->buildRow($entity)); @@ -100,7 +104,7 @@ class ProductExport extends BaseExport $entity = []; foreach (array_values($this->input['report_keys']) as $key) { - $keyval = array_search($key, $this->entity_keys); + $keyval = array_search($key, $this->product_report_keys); if (array_key_exists($key, $transformed_entity)) { $entity[$keyval] = $transformed_entity[$key]; diff --git a/app/Http/Controllers/Reports/ProductReportController.php b/app/Http/Controllers/Reports/ProductReportController.php index dcc964677a5f..664bd51a698f 100644 --- a/app/Http/Controllers/Reports/ProductReportController.php +++ b/app/Http/Controllers/Reports/ProductReportController.php @@ -11,13 +11,14 @@ namespace App\Http\Controllers\Reports; +use App\Models\Client; +use Illuminate\Http\Response; +use App\Utils\Traits\MakesHash; +use App\Jobs\Report\SendToAdmin; use App\Export\CSV\ProductExport; +use App\Jobs\Report\PreviewReport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; -use App\Jobs\Report\SendToAdmin; -use App\Models\Client; -use App\Utils\Traits\MakesHash; -use Illuminate\Http\Response; class ProductReportController extends BaseController { @@ -63,14 +64,27 @@ class ProductReportController 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(), ProductExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), ProductExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } // expect a list of visible fields, or use the default - $export = new ProductExport(auth()->user()->company(), $request->all()); + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), ProductExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + $export = new ProductExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index d2b236e91d4b..1de5988f879f 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -527,7 +527,7 @@ class ReportCsvGenerationTest extends TestCase ])->post('/api/v1/reports/products', $data); $csv = $response->streamedContent(); - +nlog($csv); $this->assertEquals('product_key', $this->getFirstValueByColumn($csv, 'Product')); $this->assertEquals('notes', $this->getFirstValueByColumn($csv, 'Notes')); $this->assertEquals(100, $this->getFirstValueByColumn($csv, 'Cost')); diff --git a/tests/Feature/Export/ReportPreviewTest.php b/tests/Feature/Export/ReportPreviewTest.php index df9163834c58..830174908d1e 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -23,6 +23,7 @@ use App\Export\CSV\ContactExport; use App\Export\CSV\ExpenseExport; use App\Export\CSV\InvoiceExport; use App\Export\CSV\PaymentExport; +use App\Export\CSV\ProductExport; use App\Export\CSV\ActivityExport; use App\Export\CSV\DocumentExport; use App\Jobs\Report\PreviewReport; @@ -56,6 +57,36 @@ class ReportPreviewTest extends TestCase } + public function testProductJsonExport() + { + \App\Models\Product::factory()->count(5)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->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/products?output=json', $data) + ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, ProductExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); + + } + + public function testPaymentJsonExport() { \App\Models\Payment::factory()->count(5)->create([ From 154ddbe95dd694ba086280300697d63d96f8e40d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 29 Aug 2023 22:56:36 +1000 Subject: [PATCH 16/17] Finalize Report previews --- app/Export/CSV/BaseExport.php | 1 + app/Export/CSV/PurchaseOrderExport.php | 78 +++++---- app/Export/CSV/PurchaseOrderItemExport.php | 158 ++++++------------ app/Export/CSV/QuoteExport.php | 122 ++++++-------- .../PurchaseOrderItemReportController.php | 25 ++- .../Reports/PurchaseOrderReportController.php | 24 ++- .../Reports/QuoteReportController.php | 23 ++- app/Jobs/Report/PreviewReport.php | 3 +- .../Export/ReportCsvGenerationTest.php | 64 +++---- tests/Feature/Export/ReportPreviewTest.php | 129 ++++++++++++++ tests/MockAccountData.php | 2 + 11 files changed, 379 insertions(+), 250 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 5691958041f2..6be20cafa90f 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -290,6 +290,7 @@ class BaseExport "private_notes" => "quote.private_notes", "uses_inclusive_taxes" => "quote.uses_inclusive_taxes", "is_amount_discount" => "quote.is_amount_discount", + "discount" => "quote.discount", "partial" => "quote.partial", "partial_due_date" => "quote.partial_due_date", "surcharge1" => "quote.custom_surcharge1", diff --git a/app/Export/CSV/PurchaseOrderExport.php b/app/Export/CSV/PurchaseOrderExport.php index bfba71830f3c..ffee39eee336 100644 --- a/app/Export/CSV/PurchaseOrderExport.php +++ b/app/Export/CSV/PurchaseOrderExport.php @@ -11,14 +11,15 @@ namespace App\Export\CSV; -use App\Libraries\MultiDB; -use App\Models\Company; -use App\Models\PurchaseOrder; -use App\Transformers\PurchaseOrderTransformer; use App\Utils\Ninja; use App\Utils\Number; -use Illuminate\Support\Facades\App; use League\Csv\Writer; +use App\Models\Company; +use App\Libraries\MultiDB; +use App\Models\PurchaseOrder; +use Illuminate\Support\Facades\App; +use App\Transformers\PurchaseOrderTransformer; +use Illuminate\Contracts\Database\Eloquent\Builder; class PurchaseOrderExport extends BaseExport { @@ -81,24 +82,19 @@ class PurchaseOrderExport extends BaseExport $this->purchase_order_transformer = new PurchaseOrderTransformer(); } - 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->input['report_keys'] = array_values($this->entity_keys); + $this->input['report_keys'] = array_values($this->purchase_order_report_keys); } - - //insert the header - $this->csv->insertOne($this->buildHeader()); - $query = PurchaseOrder::query() ->withTrashed() ->with('vendor') @@ -107,9 +103,38 @@ class PurchaseOrderExport extends BaseExport $query = $this->addDateRange($query); - // if(isset($this->input['status'])) { - // $query = $this->addPurchaseOrderStatusFilter($query, $this->input['status']); - // } + 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(); + + $report = $query->cursor() + ->map(function ($resource) { + return $this->buildRow($resource); + })->toArray(); + + return array_merge(['columns' => $header], $report); + } + + + 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 ($purchase_order) { @@ -126,23 +151,16 @@ class PurchaseOrderExport extends BaseExport $entity = []; foreach (array_values($this->input['report_keys']) as $key) { - $keyval = array_search($key, $this->entity_keys); - if(!$keyval) { - $keyval = array_search(str_replace("purchase_order.", "", $key), $this->entity_keys) ?? $key; - } + $parts = explode('.', $key); - if(!$keyval) { - $keyval = $key; - } - - if (array_key_exists($key, $transformed_purchase_order)) { - $entity[$keyval] = $transformed_purchase_order[$key]; - } elseif (array_key_exists($keyval, $transformed_purchase_order)) { - $entity[$keyval] = $transformed_purchase_order[$keyval]; + if (is_array($parts) && $parts[0] == 'purchase_order' && array_key_exists($parts[1], $transformed_purchase_order)) { + $entity[$key] = $transformed_purchase_order[$parts[1]]; } else { - $entity[$keyval] = $this->resolveKey($keyval, $purchase_order, $this->purchase_order_transformer); + $entity[$key] = $this->resolveKey($key, $purchase_order, $this->purchase_order_transformer); } + + } return $this->decorateAdvancedFields($purchase_order, $entity); diff --git a/app/Export/CSV/PurchaseOrderItemExport.php b/app/Export/CSV/PurchaseOrderItemExport.php index 2bb1eef01591..9b55f78a4e9c 100644 --- a/app/Export/CSV/PurchaseOrderItemExport.php +++ b/app/Export/CSV/PurchaseOrderItemExport.php @@ -16,6 +16,7 @@ use App\Models\Company; use App\Models\PurchaseOrder; use App\Transformers\PurchaseOrderTransformer; use App\Utils\Ninja; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; use League\Csv\Writer; @@ -30,70 +31,7 @@ class PurchaseOrderItemExport extends BaseExport private bool $force_keys = false; - public array $entity_keys = [ - 'amount' => 'amount', - 'balance' => 'balance', - 'vendor' => 'vendor_id', - 'vendor_number' => 'vendor.number', - 'vendor_id_number' => 'vendor.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', - 'purchase_order1' => 'item.custom_value1', - 'purchase_order2' => 'item.custom_value2', - 'purchase_order3' => 'item.custom_value3', - 'purchase_order4' => 'item.custom_value4', - 'tax_category' => 'item.tax_id', - 'type' => 'item.type_id', - ]; - - private array $decorate_keys = [ - 'client', - 'currency_id', - 'status' - ]; + private array $storage_array = []; public function __construct(Company $company, array $input) { @@ -102,25 +40,20 @@ class PurchaseOrderItemExport extends BaseExport $this->purchase_order_transformer = new PurchaseOrderTransformer(); } - public function run() + private 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->force_keys = true; + $this->input['report_keys'] = array_values($this->mergeItemsKeys('purchase_order_report_keys')); } - //insert the header - $this->csv->insertOne($this->buildHeader()); - $query = PurchaseOrder::query() ->withTrashed() ->with('vendor')->where('company_id', $this->company->id) @@ -128,12 +61,47 @@ class PurchaseOrderItemExport 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() + { + //load the CSV document from a string + $this->csv = Writer::createFromString(); + + $query = $this->init(); + + //insert the header + $this->csv->insertOne($this->buildHeader()); + $query->cursor() ->each(function ($purchase_order) { $this->iterateItems($purchase_order); }); + $this->csv->insertAll($this->storage_array); + return $this->csv->toString(); + } private function iterateItems(PurchaseOrder $purchase_order) @@ -141,7 +109,7 @@ class PurchaseOrderItemExport extends BaseExport $transformed_purchase_order = $this->buildRow($purchase_order); $transformed_items = []; - +nlog($purchase_order->toArray()); foreach ($purchase_order->line_items as $item) { $item_array = []; @@ -151,10 +119,6 @@ class PurchaseOrderItemExport extends BaseExport $key = str_replace("item.", "", $key); - $keyval = $key; - - $keyval = str_replace("custom_value", "purchase_order", $key); - if($key == 'type_id') { $keyval = 'type'; } @@ -164,29 +128,17 @@ class PurchaseOrderItemExport extends BaseExport } if (property_exists($item, $key)) { - $item_array[$keyval] = $item->{$key}; + $item_array[$key] = $item->{$key}; } else { - $item_array[$keyval] = ''; + $item_array[$key] = ''; } } } - $entity = []; - - 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] = ""; - } - } - $transformed_items = array_merge($transformed_purchase_order, $item_array); $entity = $this->decorateAdvancedFields($purchase_order, $transformed_items); - $this->csv->insertOne($entity); + $this->storage_array[] = $entity; } } @@ -197,22 +149,18 @@ class PurchaseOrderItemExport extends BaseExport $entity = []; foreach (array_values($this->input['report_keys']) as $key) { - $keyval = array_search($key, $this->entity_keys); + $parts = explode('.', $key); - if(!$keyval) { - $keyval = array_search(str_replace("purchase_order.", "", $key), $this->entity_keys) ?? $key; + if(is_array($parts) && $parts[0] == 'item') { + continue; } - if(!$keyval) { - $keyval = $key; - } - - if (array_key_exists($key, $transformed_purchase_order)) { - $entity[$keyval] = $transformed_purchase_order[$key]; - } elseif (array_key_exists($keyval, $transformed_purchase_order)) { - $entity[$keyval] = $transformed_purchase_order[$keyval]; + if (is_array($parts) && $parts[0] == 'purchase_order' && array_key_exists($parts[1], $transformed_purchase_order)) { + $entity[$key] = $transformed_purchase_order[$parts[1]]; + } elseif (array_key_exists($key, $transformed_purchase_order)) { + $entity[$key] = $transformed_purchase_order[$key]; } else { - $entity[$keyval] = $this->resolveKey($keyval, $purchase_order, $this->purchase_order_transformer); + $entity[$key] = $this->resolveKey($key, $purchase_order, $this->purchase_order_transformer); } } diff --git a/app/Export/CSV/QuoteExport.php b/app/Export/CSV/QuoteExport.php index 2f7d5d736c44..a84becb20eb2 100644 --- a/app/Export/CSV/QuoteExport.php +++ b/app/Export/CSV/QuoteExport.php @@ -16,6 +16,7 @@ use App\Models\Company; use App\Models\Quote; use App\Transformers\QuoteTransformer; use App\Utils\Ninja; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; use League\Csv\Writer; @@ -28,43 +29,6 @@ class QuoteExport extends BaseExport public Writer $csv; - public array $entity_keys = [ - 'amount' => 'amount', - 'balance' => 'balance', - 'client' => 'client_id', - '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', - 'valid_until' => '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', - 'invoice' => 'invoice_id', - ]; - private array $decorate_keys = [ 'client', 'currency', @@ -78,24 +42,20 @@ class QuoteExport extends BaseExport $this->quote_transformer = new QuoteTransformer(); } - public function run() + private 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->input['report_keys'] = array_values($this->entity_keys); + $this->input['report_keys'] = array_values($this->quote_report_keys); } - //insert the header - $this->csv->insertOne($this->buildHeader()); - $query = Quote::query() ->withTrashed() ->with('client') @@ -104,6 +64,40 @@ class QuoteExport 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(); + + $report = $query->cursor() + ->map(function ($resource) { + return $this->buildRow($resource); + })->toArray(); + + return array_merge(['columns' => $header], $report); + + + } + + public function run() + { + //load the CSV document from a string + $this->csv = Writer::createFromString(); + + $query = $this->init(); + + //insert the header + $this->csv->insertOne($this->buildHeader()); + $query->cursor() ->each(function ($quote) { $this->csv->insertOne($this->buildRow($quote)); @@ -114,50 +108,42 @@ class QuoteExport extends BaseExport private function buildRow(Quote $quote) :array { - $transformed_entity = $this->quote_transformer->transform($quote); + $transformed_invoice = $this->quote_transformer->transform($quote); $entity = []; foreach (array_values($this->input['report_keys']) as $key) { - $keyval = array_search($key, $this->entity_keys); - if(!$keyval) { - $keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key; + $parts = explode('.', $key); + + if (is_array($parts) && $parts[0] == 'quote' && array_key_exists($parts[1], $transformed_invoice)) { + $entity[$key] = $transformed_invoice[$parts[1]]; + } else { + $entity[$key] = $this->resolveKey($key, $quote, $this->quote_transformer); } - if(!$keyval) { - $keyval = $key; - } - - if (array_key_exists($key, $transformed_entity)) { - $entity[$keyval] = $transformed_entity[$key]; - } elseif (array_key_exists($keyval, $transformed_entity)) { - $entity[$keyval] = $transformed_entity[$keyval]; - } - else { - $entity[$keyval] = $this->resolveKey($keyval, $quote, $this->quote_transformer); - } } return $this->decorateAdvancedFields($quote, $entity); } + private function decorateAdvancedFields(Quote $quote, array $entity) :array { - if (in_array('currency_id', $this->input['report_keys'])) { - $entity['currency'] = $quote->client->currency()->code; + if (in_array('quote.currency_id', $this->input['report_keys'])) { + $entity['quote.currency'] = $quote->client->currency()->code; } - if (in_array('client_id', $this->input['report_keys'])) { - $entity['client'] = $quote->client->present()->name(); + if (in_array('quote.client_id', $this->input['report_keys'])) { + $entity['quote.client'] = $quote->client->present()->name(); } - if (in_array('status_id', $this->input['report_keys'])) { - $entity['status'] = $quote->stringStatus($quote->status_id); + if (in_array('quote.status', $this->input['report_keys'])) { + $entity['quote.status'] = $quote->stringStatus($quote->status_id); } - if (in_array('invoice_id', $this->input['report_keys'])) { - $entity['invoice'] = $quote->invoice ? $quote->invoice->number : ''; + if (in_array('quote.invoice_id', $this->input['report_keys'])) { + $entity['quote.invoice'] = $quote->invoice ? $quote->invoice->number : ''; } return $entity; diff --git a/app/Http/Controllers/Reports/PurchaseOrderItemReportController.php b/app/Http/Controllers/Reports/PurchaseOrderItemReportController.php index a708f1300ab4..71509576f582 100644 --- a/app/Http/Controllers/Reports/PurchaseOrderItemReportController.php +++ b/app/Http/Controllers/Reports/PurchaseOrderItemReportController.php @@ -11,11 +11,12 @@ namespace App\Http\Controllers\Reports; -use App\Export\CSV\PurchaseOrderItemExport; -use App\Http\Controllers\BaseController; -use App\Http\Requests\Report\GenericReportRequest; -use App\Jobs\Report\SendToAdmin; use App\Utils\Traits\MakesHash; +use App\Jobs\Report\SendToAdmin; +use App\Jobs\Report\PreviewReport; +use App\Http\Controllers\BaseController; +use App\Export\CSV\PurchaseOrderItemExport; +use App\Http\Requests\Report\GenericReportRequest; class PurchaseOrderItemReportController extends BaseController { @@ -30,14 +31,26 @@ class PurchaseOrderItemReportController 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(), PurchaseOrderItemExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), PurchaseOrderItemExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } // expect a list of visible fields, or use the default - $export = new PurchaseOrderItemExport(auth()->user()->company(), $request->all()); + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), PurchaseOrderItemExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + $export = new PurchaseOrderItemExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/app/Http/Controllers/Reports/PurchaseOrderReportController.php b/app/Http/Controllers/Reports/PurchaseOrderReportController.php index 4bd0057aa91a..5899b20cfcef 100644 --- a/app/Http/Controllers/Reports/PurchaseOrderReportController.php +++ b/app/Http/Controllers/Reports/PurchaseOrderReportController.php @@ -11,11 +11,12 @@ namespace App\Http\Controllers\Reports; +use App\Utils\Traits\MakesHash; +use App\Jobs\Report\SendToAdmin; +use App\Jobs\Report\PreviewReport; use App\Export\CSV\PurchaseOrderExport; use App\Http\Controllers\BaseController; use App\Http\Requests\Report\GenericReportRequest; -use App\Jobs\Report\SendToAdmin; -use App\Utils\Traits\MakesHash; class PurchaseOrderReportController extends BaseController { @@ -30,14 +31,29 @@ class PurchaseOrderReportController 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(), PurchaseOrderExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), PurchaseOrderExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } // expect a list of visible fields, or use the default - $export = new PurchaseOrderExport(auth()->user()->company(), $request->all()); + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), PurchaseOrderExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + + $export = new PurchaseOrderExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/app/Http/Controllers/Reports/QuoteReportController.php b/app/Http/Controllers/Reports/QuoteReportController.php index 26c92952da10..9034b1a939c4 100644 --- a/app/Http/Controllers/Reports/QuoteReportController.php +++ b/app/Http/Controllers/Reports/QuoteReportController.php @@ -11,12 +11,13 @@ namespace App\Http\Controllers\Reports; +use Illuminate\Http\Response; use App\Export\CSV\QuoteExport; +use App\Utils\Traits\MakesHash; +use App\Jobs\Report\SendToAdmin; +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 QuoteReportController extends BaseController { @@ -62,14 +63,26 @@ class QuoteReportController 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(), QuoteExport::class, $this->filename); + SendToAdmin::dispatch($user->company(), $request->all(), QuoteExport::class, $this->filename); return response()->json(['message' => 'working...'], 200); } // expect a list of visible fields, or use the default - $export = new QuoteExport(auth()->user()->company(), $request->all()); + if($request->has('output') && $request->input('output') == 'json') { + + $hash = \Illuminate\Support\Str::uuid(); + + PreviewReport::dispatch($user->company(), $request->all(), QuoteExport::class, $hash); + + return response()->json(['message' => $hash], 200); + } + + $export = new QuoteExport($user->company(), $request->all()); $csv = $export->run(); diff --git a/app/Jobs/Report/PreviewReport.php b/app/Jobs/Report/PreviewReport.php index abddca938758..ce07cbfc1c34 100644 --- a/app/Jobs/Report/PreviewReport.php +++ b/app/Jobs/Report/PreviewReport.php @@ -39,7 +39,8 @@ class PreviewReport implements ShouldQueue /** @var \App\Export\CSV\CreditExport $export */ $export = new $this->report_class($this->company, $this->request); $report = $export->returnJson(); - + nlog($report); + nlog($this->report_class); // nlog($report); Cache::put($this->hash, $report, 60 * 60); } diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index 1de5988f879f..b670d60e55f9 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -1222,15 +1222,15 @@ nlog($csv); $this->assertEquals('Vendor 1', $this->getFirstValueByColumn($csv, 'Vendor Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Purchase Order Number')); - $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')); } @@ -1642,30 +1642,32 @@ nlog($csv); $response->assertStatus(200); $csv = $response->streamedContent(); + + nlog($csv); - $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Amount')); - $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Balance')); - $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Discount')); - $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'PO Number')); - $this->assertEquals('Public', $this->getFirstValueByColumn($csv, 'Public Notes')); - $this->assertEquals('Private', $this->getFirstValueByColumn($csv, 'Private Notes')); - $this->assertEquals('Terms', $this->getFirstValueByColumn($csv, 'Terms')); - $this->assertEquals('2020-01-01', $this->getFirstValueByColumn($csv, 'Date')); - $this->assertEquals('2020-01-01', $this->getFirstValueByColumn($csv, 'Valid Until')); - $this->assertEquals('2021-01-03', $this->getFirstValueByColumn($csv, 'Partial Due Date')); - $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Partial/Deposit')); - $this->assertEquals('Custom 1', $this->getFirstValueByColumn($csv, 'Custom Value 1')); - $this->assertEquals('Custom 2', $this->getFirstValueByColumn($csv, 'Custom Value 2')); - $this->assertEquals('Custom 3', $this->getFirstValueByColumn($csv, 'Custom Value 3')); - $this->assertEquals('Custom 4', $this->getFirstValueByColumn($csv, 'Custom Value 4')); - $this->assertEquals('Footer', $this->getFirstValueByColumn($csv, 'Footer')); - $this->assertEquals('Tax 1', $this->getFirstValueByColumn($csv, 'Tax Name 1')); - $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Tax Rate 1')); - $this->assertEquals('Tax 2', $this->getFirstValueByColumn($csv, 'Tax Name 2')); - $this->assertEquals('20', $this->getFirstValueByColumn($csv, 'Tax Rate 2')); - $this->assertEquals('Tax 3', $this->getFirstValueByColumn($csv, 'Tax Name 3')); - $this->assertEquals('30', $this->getFirstValueByColumn($csv, 'Tax Rate 3')); - $this->assertEquals('Expired', $this->getFirstValueByColumn($csv, 'Status')); + $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Quote Amount')); + $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Quote Balance')); + $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Quote Discount')); + $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Quote PO Number')); + $this->assertEquals('Public', $this->getFirstValueByColumn($csv, 'Quote Public Notes')); + $this->assertEquals('Private', $this->getFirstValueByColumn($csv, 'Quote Private Notes')); + $this->assertEquals('Terms', $this->getFirstValueByColumn($csv, 'Quote Terms')); + $this->assertEquals('2020-01-01', $this->getFirstValueByColumn($csv, 'Quote Date')); + $this->assertEquals('2020-01-01', $this->getFirstValueByColumn($csv, 'Quote Valid Until')); + $this->assertEquals('2021-01-03', $this->getFirstValueByColumn($csv, 'Quote Partial Due Date')); + $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Quote Partial/Deposit')); + $this->assertEquals('Custom 1', $this->getFirstValueByColumn($csv, 'Quote Custom Value 1')); + $this->assertEquals('Custom 2', $this->getFirstValueByColumn($csv, 'Quote Custom Value 2')); + $this->assertEquals('Custom 3', $this->getFirstValueByColumn($csv, 'Quote Custom Value 3')); + $this->assertEquals('Custom 4', $this->getFirstValueByColumn($csv, 'Quote Custom Value 4')); + $this->assertEquals('Footer', $this->getFirstValueByColumn($csv, 'Quote Footer')); + $this->assertEquals('Tax 1', $this->getFirstValueByColumn($csv, 'Quote Tax Name 1')); + $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Quote Tax Rate 1')); + $this->assertEquals('Tax 2', $this->getFirstValueByColumn($csv, 'Quote Tax Name 2')); + $this->assertEquals('20', $this->getFirstValueByColumn($csv, 'Quote Tax Rate 2')); + $this->assertEquals('Tax 3', $this->getFirstValueByColumn($csv, 'Quote Tax Name 3')); + $this->assertEquals('30', $this->getFirstValueByColumn($csv, 'Quote Tax Rate 3')); + $this->assertEquals('Expired', $this->getFirstValueByColumn($csv, 'Quote Status')); } diff --git a/tests/Feature/Export/ReportPreviewTest.php b/tests/Feature/Export/ReportPreviewTest.php index 830174908d1e..5d8c77291bb3 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -16,6 +16,7 @@ use App\Models\Client; use App\Models\Expense; use App\Models\Document; use Tests\MockAccountData; +use App\Export\CSV\QuoteExport; use App\Utils\Traits\MakesHash; use App\Export\CSV\ClientExport; use App\Export\CSV\CreditExport; @@ -28,6 +29,7 @@ use App\Export\CSV\ActivityExport; use App\Export\CSV\DocumentExport; use App\Jobs\Report\PreviewReport; use Illuminate\Support\Facades\Cache; +use App\Export\CSV\PurchaseOrderExport; use Illuminate\Routing\Middleware\ThrottleRequests; /** @@ -117,6 +119,72 @@ class ReportPreviewTest extends TestCase } + + public function testPurchaseOrderItemJsonExport() + { + \App\Models\PurchaseOrder::factory()->count(5)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'vendor_id' => $this->vendor->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/purchase_order_items?output=json', $data) + ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, \App\Export\CSV\PurchaseOrderItemExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); + + //nlog($r); + + } + + public function testQuoteItemJsonExport() + { + \App\Models\Quote::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/quote_items?output=json', $data) + ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, \App\Export\CSV\QuoteItemExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); + + //nlog($r); + + } + + public function testInvoiceItemJsonExport() { \App\Models\Invoice::factory()->count(5)->create([ @@ -150,6 +218,67 @@ class ReportPreviewTest extends TestCase } + + public function testPurchaseOrderJsonExport() + { + \App\Models\PurchaseOrder::factory()->count(5)->create([ + 'company_id' => $this->company->id, + 'user_id' => $this->user->id, + 'vendor_id' => $this->vendor->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/purchase_orders?output=json', $data) + ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, PurchaseOrderExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); + + } + + public function testQuoteJsonExport() + { + \App\Models\Quote::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/quotes?output=json', $data) + ->assertStatus(200); + + $p = (new PreviewReport($this->company, $data, QuoteExport::class, '123'))->handle(); + + $this->assertNull($p); + + $r = Cache::pull('123'); + + $this->assertNotNull($r); + + } + public function testInvoiceJsonExport() { \App\Models\Invoice::factory()->count(5)->create([ diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 3d780ea1fad3..218520c45fda 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -574,6 +574,8 @@ trait MockAccountData $this->purchase_order->tax_rate2 = 0; $this->purchase_order->tax_rate3 = 0; + $this->purchase_order->line_items = InvoiceItemFactory::generate(5); + $this->purchase_order->uses_inclusive_taxes = false; $this->purchase_order->save(); From 24dccc9fee6b80f7f6ab52c0e959648b2710e0d7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 29 Aug 2023 22:57:47 +1000 Subject: [PATCH 17/17] Test cleanup --- tests/Feature/Export/ReportCsvGenerationTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index b670d60e55f9..32e264c3e251 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -527,7 +527,7 @@ class ReportCsvGenerationTest extends TestCase ])->post('/api/v1/reports/products', $data); $csv = $response->streamedContent(); -nlog($csv); +// nlog($csv); $this->assertEquals('product_key', $this->getFirstValueByColumn($csv, 'Product')); $this->assertEquals('notes', $this->getFirstValueByColumn($csv, 'Notes')); $this->assertEquals(100, $this->getFirstValueByColumn($csv, 'Cost')); @@ -673,7 +673,7 @@ nlog($csv); ])->post('/api/v1/reports/clients', $data); $csv = $response->streamedContent(); -nlog($csv); +// nlog($csv); $reader = Reader::createFromString($csv); $reader->setHeaderOffset(0); @@ -748,7 +748,7 @@ nlog($csv); $arr = $response->json(); - nlog($arr['message']); + // nlog($arr['message']); $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), @@ -849,7 +849,7 @@ nlog($csv); ])->post('/api/v1/reports/invoices', $data); $csv = $response->streamedContent(); -nlog($csv); +// 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')); @@ -985,7 +985,7 @@ nlog($csv); ])->post('/api/v1/reports/invoice_items', $data); $csv = $response->streamedContent(); -nlog($csv); +// 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')); @@ -1643,7 +1643,7 @@ nlog($csv); $csv = $response->streamedContent(); - nlog($csv); + //nlog($csv); $this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Quote Amount')); $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Quote Balance'));