From ca90b497225bf3ac211601ce95e88b6db753eb16 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 21 Aug 2023 21:14:06 +1000 Subject: [PATCH 01/39] version bump --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index f3cb8c412987..3b867ccd76c3 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.6.31 \ No newline at end of file +5.7.0 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index 233559f73bbf..646f456a9cd1 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.6.31'), - 'app_tag' => env('APP_TAG','5.6.31'), + 'app_version' => env('APP_VERSION','5.7.0'), + 'app_tag' => env('APP_TAG','5.7.0'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), From bcddd423c77533fe4ba23ea3381b878ef4fcdb09 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Aug 2023 09:41:24 +1000 Subject: [PATCH 02/39] Adjustments for without_deleted_clients query --- app/Filters/QueryFilters.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Filters/QueryFilters.php b/app/Filters/QueryFilters.php index e58f88c8c388..718ba25a4f26 100644 --- a/app/Filters/QueryFilters.php +++ b/app/Filters/QueryFilters.php @@ -298,7 +298,7 @@ abstract class QueryFilters { return $this->builder->where(function ($query) { $query->whereHas('client', function ($sub_query) { - $sub_query->where('is_deleted', 0); + $sub_query->where('is_deleted', 0)->where('deleted_at', null); })->orWhere('client_id', null); }); } @@ -310,7 +310,7 @@ abstract class QueryFilters { return $this->builder->where(function ($query) { $query->whereHas('vendor', function ($sub_query) { - $sub_query->where('is_deleted', 0); + $sub_query->where('is_deleted', 0)->where('deleted_at', null); })->orWhere('vendor_id', null); }); } From 20b73914602ad64045295a4426ff0304626be455 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Aug 2023 17:19:50 +1000 Subject: [PATCH 03/39] v5.7.3 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 3b867ccd76c3..23900d674daa 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.7.0 \ No newline at end of file +5.7.3 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index 646f456a9cd1..1739429de555 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.7.0'), - 'app_tag' => env('APP_TAG','5.7.0'), + 'app_version' => env('APP_VERSION','5.7.3'), + 'app_tag' => env('APP_TAG','5.7.3'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), From 2d405bbda434e27f54087ef73918a2559e28e0f0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Aug 2023 18:00:54 +1000 Subject: [PATCH 04/39] Updates for translations --- lang/en/texts.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/en/texts.php b/lang/en/texts.php index a96969812c96..a00b5f151fc1 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -1150,7 +1150,7 @@ $LANG = array( 'plan_status' => 'Plan Status', 'plan_upgrade' => 'Upgrade', - 'plan_change' => 'Change Plan', + 'plan_change' => 'Manage Plan', 'pending_change_to' => 'Changes To', 'plan_changes_to' => ':plan on :date', 'plan_term_changes_to' => ':plan (:term) on :date', @@ -4330,7 +4330,7 @@ $LANG = array( 'include_drafts' => 'Include Drafts', 'include_drafts_help' => 'Include draft records in reports', 'is_invoiced' => 'Is Invoiced', - 'change_plan' => 'Change Plan', + 'change_plan' => 'Manage Plan', 'persist_data' => 'Persist Data', 'customer_count' => 'Customer Count', 'verify_customers' => 'Verify Customers', From 6f23fc31331e099399a6d6f4a8391dd69b489cd8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 22 Aug 2023 20:42:01 +1000 Subject: [PATCH 05/39] Working on postmark email histoyr --- app/Jobs/PostMark/ProcessPostmarkWebhook.php | 84 ++++++++++++----- app/Services/Client/EmailHistory.php | 95 ++++++++++++++++++++ app/Services/Email/Email.php | 2 +- composer.json | 3 +- composer.lock | 40 ++++++++- lang/en/texts.php | 3 +- 6 files changed, 202 insertions(+), 25 deletions(-) create mode 100644 app/Services/Client/EmailHistory.php diff --git a/app/Jobs/PostMark/ProcessPostmarkWebhook.php b/app/Jobs/PostMark/ProcessPostmarkWebhook.php index d99455ab322e..1a04673afc40 100644 --- a/app/Jobs/PostMark/ProcessPostmarkWebhook.php +++ b/app/Jobs/PostMark/ProcessPostmarkWebhook.php @@ -11,27 +11,24 @@ namespace App\Jobs\PostMark; -use App\DataMapper\Analytics\Mail\EmailBounce; -use App\DataMapper\Analytics\Mail\EmailSpam; -use App\Jobs\Util\SystemLogger; +use App\Models\SystemLog; use App\Libraries\MultiDB; -use App\Models\Company; +use Postmark\PostmarkClient; +use Illuminate\Bus\Queueable; +use App\Jobs\Util\SystemLogger; +use App\Models\QuoteInvitation; use App\Models\CreditInvitation; use App\Models\InvoiceInvitation; -use App\Models\Payment; -use App\Models\PurchaseOrderInvitation; -use App\Models\QuoteInvitation; -use App\Models\RecurringInvoiceInvitation; -use App\Models\SystemLog; -use App\Notifications\Ninja\EmailBounceNotification; -use App\Notifications\Ninja\EmailSpamNotification; -use App\Utils\Ninja; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Turbo124\Beacon\Facades\LightLogs; +use App\Models\PurchaseOrderInvitation; +use Illuminate\Queue\InteractsWithQueue; +use App\Models\RecurringInvoiceInvitation; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\Dispatchable; +use App\DataMapper\Analytics\Mail\EmailSpam; +use App\DataMapper\Analytics\Mail\EmailBounce; +use App\Notifications\Ninja\EmailSpamNotification; class ProcessPostmarkWebhook implements ShouldQueue { @@ -41,6 +38,16 @@ class ProcessPostmarkWebhook implements ShouldQueue public $invitation; + private array $default_response = [ + 'subject' => 'Message not found.', + 'status' => '', + 'recipient' => '', + 'type' => '', + 'delivery_message' => '', + 'server' => '', + 'server_ip' => '', + ]; + /** * Create a new job instance. * @@ -126,8 +133,10 @@ class ProcessPostmarkWebhook implements ShouldQueue $this->invitation->opened_date = now(); $this->invitation->save(); + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + (new SystemLogger( - $this->request, + $data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_OPENED, SystemLog::TYPE_WEBHOOK_RESPONSE, @@ -155,8 +164,10 @@ class ProcessPostmarkWebhook implements ShouldQueue $this->invitation->email_status = 'delivered'; $this->invitation->save(); + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + (new SystemLogger( - $this->request, + $data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_DELIVERY, SystemLog::TYPE_WEBHOOK_RESPONSE, @@ -204,7 +215,9 @@ class ProcessPostmarkWebhook implements ShouldQueue LightLogs::create($bounce)->send(); - (new SystemLogger($this->request, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + + (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); // if(config('ninja.notification.slack')) // $this->invitation->company->notification(new EmailBounceNotification($this->invitation->company->account))->ninja(); @@ -248,7 +261,9 @@ class ProcessPostmarkWebhook implements ShouldQueue LightLogs::create($spam)->send(); - (new SystemLogger($this->request, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + + (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); if (config('ninja.notification.slack')) { $this->invitation->company->notification(new EmailSpamNotification($this->invitation->company->account))->ninja(); @@ -273,4 +288,33 @@ class ProcessPostmarkWebhook implements ShouldQueue return $invitation; } } + + private function fetchMessage(): array + { + if(strlen($this->request['MessageID']) < 1){ + return $this->default_response; + } + + try { + + $postmark = new PostmarkClient(config('services.postmark.secret')); + $messageDetail = $postmark->getOutboundMessageDetails($this->request['MessageID']); + + return [ + 'subject' => $messageDetail->subject ?? '', + 'status' => $messageDetail->status ?? '', + 'recipient' => $messageDetail->messageevents[0]['Recipient'] ?? '', + 'type' => $messageDetail->messageevents[0]->Type ?? '', + 'subject' => $messageDetail->messageevents[0]->Details->DeliveryMessage ?? '', + 'server' => $messageDetail->messageevents[0]->Details->DestinationServer ?? '', + 'server_ip' => $messageDetail->messageevents[0]->Details->DestinationIP ?? '', + ]; + + } + catch (\Exception $e) { + + return $this->default_response; + + } + } } diff --git a/app/Services/Client/EmailHistory.php b/app/Services/Client/EmailHistory.php new file mode 100644 index 000000000000..b279c6c5f2c6 --- /dev/null +++ b/app/Services/Client/EmailHistory.php @@ -0,0 +1,95 @@ + 'Message not found.', + 'status' => '', + 'recipient' => '', + 'type' => '', + 'subject' => '', + 'server' => '', + 'server_ip' => '', + ]; + + public function __construct(public Client $client) + { + } + + public function run(): array + { + // $settings = $this->client->getMergedSettings(); + + // if($settings->email_sending_method == 'default'){ + // $this->postmark_token = config('services.postmark.token'); + // } + // elseif($settings->email_sending_method == 'client_postmark'){ + // $this->postmark_token = $settings->postmark_secret; + // } + // else{ + // return []; + // } + + // $this->postmark = new PostmarkClient($this->postmark_token); + + return SystemLog::query() + ->where('client_id', $this->client->id) + ->where('category_id', SystemLog::CATEGORY_MAIL) + ->orderBy('id','DESC') + ->cursor() + ->map(function ($system_log) { + + if($system_log->log['history'] ?? false){ + return json_decode($system_log->log['history'],true); + } + })->toArray(); + } + + private function fetchMessage(string $message_id): array + { + if(strlen($message_id) < 1){ + return $this->default_response; + } + + try { + + $messageDetail = $this->postmark->getOutboundMessageDetails($message_id); + + return [ + 'subject' => $messageDetail->subject ?? '', + 'status' => $messageDetail->status ?? '', + 'recipient' => $messageDetail->messageevents[0]['Recipient'] ?? '', + 'type' => $messageDetail->messageevents[0]->Type ?? '', + 'delivery_message' => $messageDetail->messageevents[0]->Details->DeliveryMessage ?? '', + 'server' => $messageDetail->messageevents[0]->Details->DestinationServer ?? '', + 'server_ip' => $messageDetail->messageevents[0]->Details->DestinationIP ?? '', + ]; + + } + catch (\Exception $e) { + + return $this->default_response; + + } + } +} \ No newline at end of file diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index 4c114d586d31..cebb8b7b6f79 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -240,7 +240,6 @@ class Email implements ShouldQueue } if ($this->client_mailgun_secret) { - $mailer->mailgun_config($this->client_mailgun_secret, $this->client_mailgun_domain, $this->client_mailgun_endpoint); } @@ -254,6 +253,7 @@ class Email implements ShouldQueue LightLogs::create(new EmailSuccess($this->company->company_key)) ->send(); + } catch(\Symfony\Component\Mime\Exception\RfcComplianceException $e) { nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); $this->fail(); diff --git a/composer.json b/composer.json index d9bb6348f79c..3204247f8398 100644 --- a/composer.json +++ b/composer.json @@ -76,6 +76,7 @@ "omnipay/paypal": "^3.0", "payfast/payfast-php-sdk": "^1.1", "pragmarx/google2fa": "^8.0", + "psr/http-message": "^1.0", "pusher/pusher-php-server": "^7.2", "razorpay/razorpay": "2.*", "sentry/sentry-laravel": "^3", @@ -96,7 +97,7 @@ "twilio/sdk": "^6.40", "webpatser/laravel-countries": "dev-master#75992ad", "wepay/php-sdk": "^0.3", - "psr/http-message": "^1.0" + "wildbit/postmark-php": "^4.0" }, "require-dev": { "php": "^8.1", diff --git a/composer.lock b/composer.lock index 31584f8b7590..accbb507064e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "673ca66ddfdb05c3ea29012594a196d3", + "content-hash": "70ade0ea4925946765213166010b0f32", "packages": [ { "name": "adrienrn/php-mimetyper", @@ -14230,6 +14230,44 @@ "source": "https://github.com/wepay/PHP-SDK/tree/master" }, "time": "2017-01-21T07:03:26+00:00" + }, + { + "name": "wildbit/postmark-php", + "version": "v4.0.5", + "source": { + "type": "git", + "url": "https://github.com/ActiveCampaign/postmark-php.git", + "reference": "b71efba061de7cf7e1f853d211b1c5edce4e3c5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ActiveCampaign/postmark-php/zipball/b71efba061de7cf7e1f853d211b1c5edce4e3c5b", + "reference": "b71efba061de7cf7e1f853d211b1c5edce4e3c5b", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0|^7.0", + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Postmark\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The officially supported client for Postmark (http://postmarkapp.com)", + "support": { + "issues": "https://github.com/ActiveCampaign/postmark-php/issues", + "source": "https://github.com/ActiveCampaign/postmark-php/tree/v4.0.5" + }, + "time": "2023-02-03T15:00:17+00:00" } ], "packages-dev": [ diff --git a/lang/en/texts.php b/lang/en/texts.php index a00b5f151fc1..183cbf5c8c04 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5158,8 +5158,7 @@ $LANG = array( 'unlinked_transaction' => 'Successfully unlinked transaction', 'view_dashboard_permission' => 'Allow user to access the dashboard, data is limited to available permissions', 'marked_sent_credits' => 'Successfully marked credits sent', - -); +); return $LANG; From e1e99cbd9e18591b1c566d2a9f74ec1c8d7d3a3c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 23 Aug 2023 14:36:55 +1000 Subject: [PATCH 06/39] Working on email history for PostMark --- app/Jobs/PostMark/ProcessPostmarkWebhook.php | 57 +++++++++++++------- app/Services/Client/EmailHistory.php | 3 +- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/app/Jobs/PostMark/ProcessPostmarkWebhook.php b/app/Jobs/PostMark/ProcessPostmarkWebhook.php index 1a04673afc40..18dde45e430c 100644 --- a/app/Jobs/PostMark/ProcessPostmarkWebhook.php +++ b/app/Jobs/PostMark/ProcessPostmarkWebhook.php @@ -38,15 +38,15 @@ class ProcessPostmarkWebhook implements ShouldQueue public $invitation; - private array $default_response = [ - 'subject' => 'Message not found.', - 'status' => '', - 'recipient' => '', - 'type' => '', - 'delivery_message' => '', - 'server' => '', - 'server_ip' => '', - ]; + private $entity; + + private array $default_response = [ + 'recipients' => '', + 'subject' => 'Message not found.', + 'entity' => '', + 'entity_id' => '', + 'events' => [], + ]; /** * Create a new job instance. @@ -275,14 +275,19 @@ class ProcessPostmarkWebhook implements ShouldQueue $invitation = false; if ($invitation = InvoiceInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'invoice'; return $invitation; } elseif ($invitation = QuoteInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'quote'; return $invitation; } elseif ($invitation = RecurringInvoiceInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'recurring_invoice'; return $invitation; } elseif ($invitation = CreditInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'credit'; return $invitation; } elseif ($invitation = PurchaseOrderInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'purchase_order'; return $invitation; } else { return $invitation; @@ -297,19 +302,33 @@ class ProcessPostmarkWebhook implements ShouldQueue try { - $postmark = new PostmarkClient(config('services.postmark.secret')); + $postmark = new PostmarkClient(config('services.postmark.token')); $messageDetail = $postmark->getOutboundMessageDetails($this->request['MessageID']); - + + $recipients = collect($messageDetail['recipients'])->flatten()->implode(','); + $subject = $messageDetail->subject ?? ''; + + $events = collect($messageDetail->messageevents)->map(function ($event) { + + return [ + 'recipient' => $event->Recipient ?? '', + 'status' => $event->Type ?? '', + 'delivery_message' => $event->Details->DeliveryMessage ?? $event->Details->Summary ?? '', + 'server' => $event->Details->DestinationServer ?? '', + 'server_ip' => $event->Details->DestinationIP ?? '', + 'date' => \Carbon\Carbon::parse($event->ReceivedAt)->format('Y-m-d H:m:s') ?? '', + ]; + + })->toArray(); + return [ - 'subject' => $messageDetail->subject ?? '', - 'status' => $messageDetail->status ?? '', - 'recipient' => $messageDetail->messageevents[0]['Recipient'] ?? '', - 'type' => $messageDetail->messageevents[0]->Type ?? '', - 'subject' => $messageDetail->messageevents[0]->Details->DeliveryMessage ?? '', - 'server' => $messageDetail->messageevents[0]->Details->DestinationServer ?? '', - 'server_ip' => $messageDetail->messageevents[0]->Details->DestinationIP ?? '', + 'recipients' => $recipients, + 'subject' => $subject, + 'entity' => $this->entity ?? '', + 'entity_id' => $this->invitation->{$this->entity}->hashed_id ?? '', + 'events' => $events, ]; - + } catch (\Exception $e) { diff --git a/app/Services/Client/EmailHistory.php b/app/Services/Client/EmailHistory.php index b279c6c5f2c6..f0715c49b3e3 100644 --- a/app/Services/Client/EmailHistory.php +++ b/app/Services/Client/EmailHistory.php @@ -16,6 +16,7 @@ use App\Models\SystemLog; use Postmark\PostmarkClient; use App\Services\AbstractService; +/** @deprecated */ class EmailHistory extends AbstractService { private string $postmark_token; @@ -27,7 +28,7 @@ class EmailHistory extends AbstractService 'status' => '', 'recipient' => '', 'type' => '', - 'subject' => '', + 'delivery_message' => '', 'server' => '', 'server_ip' => '', ]; From 88423df8cb9ebeb05c83a8392f486ee155143355 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 23 Aug 2023 15:01:37 +1000 Subject: [PATCH 07/39] Add email history tracking --- .../Controllers/EmailHistoryController.php | 65 +++++++++++++++++++ .../Email/ClientEmailHistoryRequest.php | 55 ++++++++++++++++ .../Email/EntityEmailHistoryRequest.php | 62 ++++++++++++++++++ routes/api.php | 3 + 4 files changed, 185 insertions(+) create mode 100644 app/Http/Controllers/EmailHistoryController.php create mode 100644 app/Http/Requests/Email/ClientEmailHistoryRequest.php create mode 100644 app/Http/Requests/Email/EntityEmailHistoryRequest.php diff --git a/app/Http/Controllers/EmailHistoryController.php b/app/Http/Controllers/EmailHistoryController.php new file mode 100644 index 000000000000..70d52b28d4d1 --- /dev/null +++ b/app/Http/Controllers/EmailHistoryController.php @@ -0,0 +1,65 @@ +id) + ->where('category_id', SystemLog::CATEGORY_MAIL) + ->orderBy('id','DESC') + ->map(function ($system_log){ + if($system_log->log['history'] ?? false) { + return json_decode($system_log->log['history'], true); + } + }); + + return response()->json($data, 200); + + } + + public function entityHistory(EntityEmailHistoryRequest $request) + { + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $data = SystemLog::where('company_id', $user->company()->id) + ->where('category_id', SystemLog::CATEGORY_MAIL) + ->whereJsonContains('log->history->entity_id', $this->encodePrimaryKey($request->entity_id)) + ->orderBy('id', 'DESC') + ->map(function ($system_log) { + if($system_log->log['history'] ?? false) { + return json_decode($system_log->log['history'], true); + } + }); + + return response()->json($data, 200); + + } +} \ No newline at end of file diff --git a/app/Http/Requests/Email/ClientEmailHistoryRequest.php b/app/Http/Requests/Email/ClientEmailHistoryRequest.php new file mode 100644 index 000000000000..45d60b458b99 --- /dev/null +++ b/app/Http/Requests/Email/ClientEmailHistoryRequest.php @@ -0,0 +1,55 @@ +user(); + + return $user->can('view', $this->client); + + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + ]; + } + + public function prepareForValidation() + { + $input = $this->all(); + + $this->replace($input); + } + +} diff --git a/app/Http/Requests/Email/EntityEmailHistoryRequest.php b/app/Http/Requests/Email/EntityEmailHistoryRequest.php new file mode 100644 index 000000000000..2c506e653dd6 --- /dev/null +++ b/app/Http/Requests/Email/EntityEmailHistoryRequest.php @@ -0,0 +1,62 @@ +user(); + + return [ + 'entity' => 'bail|required|string|in:invoice,quote,credit,recurring_invoice,purchase_order', + 'entity_id' => ['bail','required',Rule::exists($this->entity_plural, 'id')->where('company_id', $user->company()->id)], + ]; + } + + public function prepareForValidation() + { + $input = $this->all(); + + $this->entity_plural = Str::plural($input['entity']) ?? ''; + $input['entity_id'] = $this->decodePrimaryKey($input['entity_id']); + + $this->replace($input); + } + +} diff --git a/routes/api.php b/routes/api.php index 34e6c57c308b..aa4e108a5273 100644 --- a/routes/api.php +++ b/routes/api.php @@ -58,6 +58,7 @@ use App\Http\Controllers\TaskStatusController; use App\Http\Controllers\Bank\YodleeController; use App\Http\Controllers\CompanyUserController; use App\Http\Controllers\PaymentTermController; +use App\Http\Controllers\EmailHistoryController; use App\Http\Controllers\GroupSettingController; use App\Http\Controllers\OneTimeTokenController; use App\Http\Controllers\SubscriptionController; @@ -202,6 +203,8 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('documents/bulk', [DocumentController::class, 'bulk'])->name('documents.bulk'); Route::post('emails', [EmailController::class, 'send'])->name('email.send')->middleware('user_verified'); + Route::post('emails/clientHistory/{client}', [EmailHistoryController::class, 'clientHistory'])->name('email.clientHistory'); + Route::post('emails/entityHistory', [EmailHistoryController::class, 'entityHistory'])->name('email.entityHistory'); Route::resource('expenses', ExpenseController::class); // name = (expenses. index / create / show / update / destroy / edit Route::put('expenses/{expense}/upload', [ExpenseController::class, 'upload']); From 17872a3158757ac5baebb8f376db1eb38353d1cd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 23 Aug 2023 15:02:32 +1000 Subject: [PATCH 08/39] Update docs --- .../Controllers/EmailHistoryController.php | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/EmailHistoryController.php b/app/Http/Controllers/EmailHistoryController.php index 70d52b28d4d1..863ac93d6a3b 100644 --- a/app/Http/Controllers/EmailHistoryController.php +++ b/app/Http/Controllers/EmailHistoryController.php @@ -11,15 +11,11 @@ namespace App\Http\Controllers; -use App\Models\Quote; -use App\Models\Client; -use App\Models\Invoice; -use App\Models\SystemLog; -use App\Models\PurchaseOrder; -use App\Utils\Traits\MakesHash; -use App\Models\RecurringInvoice; use App\Http\Requests\Email\ClientEmailHistoryRequest; use App\Http\Requests\Email\EntityEmailHistoryRequest; +use App\Models\Client; +use App\Models\SystemLog; +use App\Utils\Traits\MakesHash; class EmailHistoryController extends BaseController { @@ -33,17 +29,22 @@ class EmailHistoryController extends BaseController { $data = SystemLog::where('client_id', $client->id) ->where('category_id', SystemLog::CATEGORY_MAIL) - ->orderBy('id','DESC') - ->map(function ($system_log){ - if($system_log->log['history'] ?? false) { - return json_decode($system_log->log['history'], true); - } + ->orderBy('id', 'DESC') + ->map(function ($system_log) { + if($system_log->log['history'] ?? false) { + return json_decode($system_log->log['history'], true); + } }); return response()->json($data, 200); } - + + /** + * May need to expand on this using + * just the message-id and search for the + * entity in the invitations + */ public function entityHistory(EntityEmailHistoryRequest $request) { /** @var \App\Models\User $user */ @@ -62,4 +63,4 @@ class EmailHistoryController extends BaseController return response()->json($data, 200); } -} \ No newline at end of file +} From 2f1968c899d9b75a445977e1141e4cc975662fc6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 23 Aug 2023 15:03:48 +1000 Subject: [PATCH 09/39] Fixes for static analysis --- app/PaymentDrivers/SquarePaymentDriver.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index 9a14358be634..5e6e3ecbb738 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -394,8 +394,10 @@ class SquarePaymentDriver extends BaseDriver //getsubscriptionid here $subscription_id = $this->checkWebhooks(); - if(!$subscription_id) - return nlog('No Subscription Found'); + if(!$subscription_id){ + nlog('No Subscription Found'); + return; + } $api_response = $this->square->getWebhookSubscriptionsApi()->testWebhookSubscription($subscription_id, $body); From 21f14694994894a3b59dfc2c315d1d3fd352719e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 23 Aug 2023 17:13:46 +1000 Subject: [PATCH 10/39] Tests for entity history --- .../Controllers/EmailHistoryController.php | 8 +- tests/Feature/InvoiceEmailTest.php | 97 ++++++++++++++++++- 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/EmailHistoryController.php b/app/Http/Controllers/EmailHistoryController.php index 863ac93d6a3b..e8f2d4894fda 100644 --- a/app/Http/Controllers/EmailHistoryController.php +++ b/app/Http/Controllers/EmailHistoryController.php @@ -30,9 +30,11 @@ class EmailHistoryController extends BaseController $data = SystemLog::where('client_id', $client->id) ->where('category_id', SystemLog::CATEGORY_MAIL) ->orderBy('id', 'DESC') + ->cursor() ->map(function ($system_log) { if($system_log->log['history'] ?? false) { - return json_decode($system_log->log['history'], true); + return $system_log->log['history']; + // return json_decode($system_log->log['history'], true); } }); @@ -54,9 +56,11 @@ class EmailHistoryController extends BaseController ->where('category_id', SystemLog::CATEGORY_MAIL) ->whereJsonContains('log->history->entity_id', $this->encodePrimaryKey($request->entity_id)) ->orderBy('id', 'DESC') + ->cursor() ->map(function ($system_log) { if($system_log->log['history'] ?? false) { - return json_decode($system_log->log['history'], true); + return $system_log->log['history']; + // return json_decode($system_log->log['history'], true); } }); diff --git a/tests/Feature/InvoiceEmailTest.php b/tests/Feature/InvoiceEmailTest.php index 9609840e8ae2..2eb3e2da172f 100644 --- a/tests/Feature/InvoiceEmailTest.php +++ b/tests/Feature/InvoiceEmailTest.php @@ -11,14 +11,15 @@ namespace Tests\Feature; +use Tests\TestCase; +use App\Models\SystemLog; +use Tests\MockAccountData; use App\Jobs\Entity\EmailEntity; use App\Utils\Traits\GeneratesCounter; use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Session; -use Tests\MockAccountData; -use Tests\TestCase; use Illuminate\Validation\ValidationException; +use Illuminate\Foundation\Testing\DatabaseTransactions; /** * @test @@ -46,6 +47,96 @@ class InvoiceEmailTest extends TestCase } + public function testClientEmailHistory() + { + $system_log = new SystemLog(); + $system_log->company_id = $this->company->id; + $system_log->client_id = $this->client->id; + $system_log->category_id = SystemLog::CATEGORY_MAIL; + $system_log->event_id = SystemLog::EVENT_MAIL_SEND; + $system_log->type_id = SystemLog::TYPE_WEBHOOK_RESPONSE; + $system_log->log = [ + 'history' => [ + 'entity_id' => $this->invoice->hashed_id, + 'entity_type' => 'invoice', + 'subject' => 'Invoice #1', + 'events' => [ + [ + 'recipient' => 'bob@gmail.com', + 'status' => 'Delivered', + 'delivery_message' => 'A message that was deliveryed', + 'server' => 'email.mx.com', + 'server_ip' => '127.0.0.1', + 'date' => \Carbon\Carbon::parse('2023-10-10')->format('Y-m-d H:m:s') ?? '', + ], + ], + ] + ]; + + $system_log->save(); + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/emails/clientHistory/'.$this->client->hashed_id); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('invoice', $arr[0]['entity_type']); + + } + + public function testEntityEmailHistory() + { + $system_log = new SystemLog(); + $system_log->company_id = $this->company->id; + $system_log->client_id = $this->client->id; + $system_log->category_id = SystemLog::CATEGORY_MAIL; + $system_log->event_id = SystemLog::EVENT_MAIL_SEND; + $system_log->type_id = SystemLog::TYPE_WEBHOOK_RESPONSE; + $system_log->log = [ + 'history' => [ + 'entity_id' => $this->invoice->hashed_id, + 'entity_type' => 'invoice', + 'subject' => 'Invoice #1', + 'events' => [ + [ + 'recipient' => 'bob@gmail.com', + 'status' => 'Delivered', + 'delivery_message' => 'A message that was deliveryed', + 'server' => 'email.mx.com', + 'server_ip' => '127.0.0.1', + 'date' => \Carbon\Carbon::parse('2023-10-10')->format('Y-m-d H:m:s') ?? '', + ], + ], + ] + ]; + + $system_log->save(); + + $data = [ + 'entity' => 'invoice', + 'entity_id' => $this->invoice->hashed_id, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/emails/entityHistory/', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals('invoice', $arr[0]['entity_type']); + $this->assertEquals($this->invoice->hashed_id, $arr[0]['entity_id']); + + } + + public function testTemplateValidation() { $data = [ From 2c7c60f9859bb553027b7529d5ec7d9ac2fb1374 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 23 Aug 2023 17:36:39 +1000 Subject: [PATCH 11/39] Additional tests for System Logs --- tests/Feature/InvoiceEmailTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Feature/InvoiceEmailTest.php b/tests/Feature/InvoiceEmailTest.php index 2eb3e2da172f..0105f25105aa 100644 --- a/tests/Feature/InvoiceEmailTest.php +++ b/tests/Feature/InvoiceEmailTest.php @@ -87,6 +87,12 @@ class InvoiceEmailTest extends TestCase $this->assertEquals('invoice', $arr[0]['entity_type']); + $count = SystemLog::where('client_id', $this->client->id) + ->where('category_id', SystemLog::CATEGORY_MAIL) + ->orderBy('id', 'DESC') + ->count(); + + $this->assertEquals(1, $count); } public function testEntityEmailHistory() @@ -134,6 +140,13 @@ class InvoiceEmailTest extends TestCase $this->assertEquals('invoice', $arr[0]['entity_type']); $this->assertEquals($this->invoice->hashed_id, $arr[0]['entity_id']); + $count = SystemLog::where('company_id', $this->company->id) + ->where('category_id', SystemLog::CATEGORY_MAIL) + ->whereJsonContains('log->history->entity_id', $this->invoice->hashed_id) + ->count(); + + $this->assertEquals(1, $count); + } From dee0b51771294a9a7de789e99738df759410a7b1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 23 Aug 2023 20:36:25 +1000 Subject: [PATCH 12/39] Fixes for reports --- app/Export/CSV/CreditExport.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/app/Export/CSV/CreditExport.php b/app/Export/CSV/CreditExport.php index 234a9f9c10e4..69c396ff8f72 100644 --- a/app/Export/CSV/CreditExport.php +++ b/app/Export/CSV/CreditExport.php @@ -86,15 +86,23 @@ class CreditExport extends BaseExport { $query = $this->init(); - $header = $this->buildHeader(); + $headerdisplay = $this->buildHeader(); + $header = []; + + foreach ($this->input['report_keys'] as $key => $value) { + $header[] = ['identifier' => $value, 'display_value' => $headerdisplay[$key]]; + } $report = $query->cursor() ->map(function ($credit) { $row = $this->buildRow($credit); return $this->processMetaData($row, $credit); })->toArray(); + + nlog(array_merge(['column' => $header], $report)); - return array_merge([$header], $report); + + return array_merge(['column' => $header], $report); } private function processMetaData(array $row, Credit $credit): array @@ -112,7 +120,8 @@ class CreditExport extends BaseExport $clean_row[$key]['id'] = $report_keys[1] ?? $report_keys[0]; $clean_row[$key]['hashed_id'] = $report_keys[0] == 'credit' ? null : $credit->{$report_keys[0]}->hashed_id ?? null; $clean_row[$key]['value'] = $row[$column_key]; - + $clean_row[$key]['identifier'] = $value; + if(in_array($clean_row[$key]['id'], ['amount', 'balance', 'partial', 'refunded', 'applied','unit_cost','cost','price'])) $clean_row[$key]['display_value'] = Number::formatMoney($row[$column_key], $credit->client); else @@ -134,14 +143,6 @@ class CreditExport extends BaseExport if (count($this->input['report_keys']) == 0) { $this->input['report_keys'] = array_values($this->entity_keys); - // $this->input['report_keys'] = collect(array_values($this->entity_keys))->map(function ($value){ - - // // if(in_array($value,['client_id','country_id'])) - // // return $value; - // // else - // return 'credit.'.$value; - // })->toArray(); - } $query = Credit::query() From fcb3e039cea746fbafcb8ad89621d44190c2b54f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 23 Aug 2023 20:36:56 +1000 Subject: [PATCH 13/39] Fixes for reports --- app/Export/CSV/CreditExport.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/Export/CSV/CreditExport.php b/app/Export/CSV/CreditExport.php index 69c396ff8f72..2cf25cb56899 100644 --- a/app/Export/CSV/CreditExport.php +++ b/app/Export/CSV/CreditExport.php @@ -99,10 +99,7 @@ class CreditExport extends BaseExport return $this->processMetaData($row, $credit); })->toArray(); - nlog(array_merge(['column' => $header], $report)); - - - return array_merge(['column' => $header], $report); + return array_merge(['columns' => $header], $report); } private function processMetaData(array $row, Credit $credit): array @@ -121,7 +118,7 @@ class CreditExport extends BaseExport $clean_row[$key]['hashed_id'] = $report_keys[0] == 'credit' ? null : $credit->{$report_keys[0]}->hashed_id ?? null; $clean_row[$key]['value'] = $row[$column_key]; $clean_row[$key]['identifier'] = $value; - + if(in_array($clean_row[$key]['id'], ['amount', 'balance', 'partial', 'refunded', 'applied','unit_cost','cost','price'])) $clean_row[$key]['display_value'] = Number::formatMoney($row[$column_key], $credit->client); else From b1de2e6b3b8a949cfe7c53ba5e2d086b96084a25 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 24 Aug 2023 08:56:20 +1000 Subject: [PATCH 14/39] Fixes for group settings --- app/Http/Controllers/GroupSettingController.php | 4 +++- tests/Feature/Export/ReportPreviewTest.php | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/GroupSettingController.php b/app/Http/Controllers/GroupSettingController.php index e69d6cab7d2b..ff22b55242c3 100644 --- a/app/Http/Controllers/GroupSettingController.php +++ b/app/Http/Controllers/GroupSettingController.php @@ -139,7 +139,9 @@ class GroupSettingController extends BaseController */ public function update(UpdateGroupSettingRequest $request, GroupSetting $group_setting) { - $group_setting = $this->group_setting_repo->save($request->all(), $group_setting); + /** Need this to prevent settings from being overwritten */ + if(!$request->file('company_logo')) + $group_setting = $this->group_setting_repo->save($request->all(), $group_setting); $this->uploadLogo($request->file('company_logo'), $group_setting->company, $group_setting); diff --git a/tests/Feature/Export/ReportPreviewTest.php b/tests/Feature/Export/ReportPreviewTest.php index 75516cea6e58..2f029a125cd7 100644 --- a/tests/Feature/Export/ReportPreviewTest.php +++ b/tests/Feature/Export/ReportPreviewTest.php @@ -73,5 +73,6 @@ class ReportPreviewTest extends TestCase ])->postJson('/api/v1/reports/credits?output=json', $data) ->assertStatus(200); + } } \ No newline at end of file From fec6fe8f31b094260b1a23c961d6ff1b141badd6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 24 Aug 2023 13:22:35 +1000 Subject: [PATCH 15/39] Refactor for report exports --- app/Export/CSV/BaseExport.php | 33 +++++++++---- app/Export/CSV/ClientExport.php | 3 -- app/Export/CSV/CreditExport.php | 87 +++++++-------------------------- app/Export/CSV/VendorExport.php | 4 -- 4 files changed, 42 insertions(+), 85 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index e4c743923c4c..f829d0931291 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -13,6 +13,7 @@ namespace App\Export\CSV; use App\Utils\Number; use App\Models\Client; +use App\Utils\Helpers; use App\Models\Company; use App\Models\Expense; use App\Models\Invoice; @@ -28,7 +29,7 @@ use League\Fractal\Serializer\ArraySerializer; class BaseExport { use MakesHash; - + public Company $company; public array $input; @@ -43,8 +44,6 @@ class BaseExport public string $client_description = 'All Clients'; - public array $forced_keys = []; - protected array $vendor_report_keys = [ 'address1' => 'vendor.address1', 'address2' => 'vendor.address2', @@ -271,6 +270,7 @@ class BaseExport "date" => "credit.date", "due_date" => "credit.due_date", "terms" => "credit.terms", + "discount" => "credit.discount", "footer" => "credit.footer", "status" => "credit.status", "public_notes" => "credit.public_notes", @@ -283,6 +283,10 @@ class BaseExport "surcharge2" => "credit.custom_surcharge2", "surcharge3" => "credit.custom_surcharge3", "surcharge4" => "credit.custom_surcharge4", + "custom_value1" => "credit.custom_value1", + "custom_value2" => "credit.custom_value2", + "custom_value3" => "credit.custom_value3", + "custom_value4" => "credit.custom_value4", "exchange_rate" => "credit.exchange_rate", "tax_amount" => "credit.total_taxes", "assigned_user" => "credit.assigned_user_id", @@ -832,11 +836,13 @@ class BaseExport public function buildHeader() :array { + $helper = new Helpers(); + $header = []; // nlog($this->input['report_keys']); - foreach (array_merge($this->input['report_keys'], $this->forced_keys) as $value) { + foreach ($this->input['report_keys'] as $value) { $key = array_search($value, $this->entity_keys); nlog("{$key} => {$value}"); @@ -913,11 +919,20 @@ class BaseExport $key = str_replace('contact.', '', $key); $key = str_replace('payment.', '', $key); $key = str_replace('expense.', '', $key); -// nlog($key); - if(in_array($key, ['quote1','quote2','quote3','quote4','credit1','credit2','credit3','credit4','purchase_order1','purchase_order2','purchase_order3','purchase_order4'])) + + // if( in_array($key, ['quote1','quote2','quote3','quote4','credit1','credit2','credit3','credit4','purchase_order1','purchase_order2','purchase_order3','purchase_order4'])) + if(stripos($value, 'custom_value') !== false) { - $number = substr($key, -1); - $header[] = ctrans('texts.item') . " ". ctrans("texts.custom_value{$number}"); + $parts = explode(".", $value); + + if(in_array($parts[0], ['credit','quote','invoice','purchase_order','recurring_invoice'])){ + $entity = "invoice".substr($parts[1], -1); + } + else { + $entity = $parts[0].substr($parts[1], -1); + } + + $header[] = $helper->makeCustomField($this->company->custom_fields, $entity); } else { @@ -925,8 +940,6 @@ class BaseExport } } - // nlog($header); - return $header; } } diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php index 6e4dded88159..0335be61c467 100644 --- a/app/Export/CSV/ClientExport.php +++ b/app/Export/CSV/ClientExport.php @@ -79,9 +79,6 @@ class ClientExport extends BaseExport 'client.industry', ]; - public array $forced_keys = [ - ]; - public function __construct(Company $company, array $input) { $this->company = $company; diff --git a/app/Export/CSV/CreditExport.php b/app/Export/CSV/CreditExport.php index 2cf25cb56899..ffc979513949 100644 --- a/app/Export/CSV/CreditExport.php +++ b/app/Export/CSV/CreditExport.php @@ -30,51 +30,6 @@ class CreditExport extends BaseExport public Writer $csv; - public array $entity_keys = [ - 'amount' => 'amount', - 'balance' => 'balance', - 'client' => 'client_id', - 'country' => 'country_id', - 'custom_surcharge1' => 'custom_surcharge1', - 'custom_surcharge2' => 'custom_surcharge2', - 'custom_surcharge3' => 'custom_surcharge3', - 'custom_surcharge4' => 'custom_surcharge4', - 'currency' => 'currency', - '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', - 'invoice' => 'invoice_id', - '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', - ]; - - private array $decorate_keys = [ - 'country', - 'client', - 'invoice', - 'currency', - ]; - public function __construct(Company $company, array $input) { $this->company = $company; @@ -86,13 +41,13 @@ class CreditExport extends BaseExport { $query = $this->init(); - $headerdisplay = $this->buildHeader(); $header = []; + $headerdisplay = $this->buildHeader(); foreach ($this->input['report_keys'] as $key => $value) { $header[] = ['identifier' => $value, 'display_value' => $headerdisplay[$key]]; } - + $report = $query->cursor() ->map(function ($credit) { $row = $this->buildRow($credit); @@ -105,21 +60,18 @@ class CreditExport extends BaseExport private function processMetaData(array $row, Credit $credit): array { $clean_row = []; - - foreach ($this->input['report_keys'] as $key => $value) { - + foreach (array_values($this->input['report_keys']) as $key => $value) { + $report_keys = explode(".", $value); - $column_key = str_replace("credit.", "", $value); - $column_key = array_search($column_key, $this->entity_keys); - + $column_key = $value; $clean_row[$key]['entity'] = $report_keys[0]; $clean_row[$key]['id'] = $report_keys[1] ?? $report_keys[0]; $clean_row[$key]['hashed_id'] = $report_keys[0] == 'credit' ? null : $credit->{$report_keys[0]}->hashed_id ?? null; $clean_row[$key]['value'] = $row[$column_key]; $clean_row[$key]['identifier'] = $value; - if(in_array($clean_row[$key]['id'], ['amount', 'balance', 'partial', 'refunded', 'applied','unit_cost','cost','price'])) + if(in_array($clean_row[$key]['id'], ['paid_to_date','total_taxes','amount', 'balance', 'partial', 'refunded', 'applied','unit_cost','cost','price'])) $clean_row[$key]['display_value'] = Number::formatMoney($row[$column_key], $credit->client); else $clean_row[$key]['display_value'] = $row[$column_key]; @@ -139,12 +91,13 @@ class CreditExport extends BaseExport $t->replace(Ninja::transformTranslations($this->company->settings)); if (count($this->input['report_keys']) == 0) { - $this->input['report_keys'] = array_values($this->entity_keys); + $this->input['report_keys'] = array_values($this->credit_report_keys); } $query = Credit::query() ->withTrashed() - ->with('client')->where('company_id', $this->company->id) + ->with('client') + ->where('company_id', $this->company->id) ->where('is_deleted', 0); $query = $this->addDateRange($query); @@ -160,11 +113,9 @@ class CreditExport extends BaseExport //insert the header $this->csv->insertOne($this->buildHeader()); - // nlog($this->input['report_keys']); $query->cursor() ->each(function ($credit) { - // nlog($this->buildRow($credit)); $this->csv->insertOne($this->buildRow($credit)); }); @@ -178,22 +129,22 @@ class CreditExport 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("credit.", "", $key), $this->entity_keys) ?? $key; - - if(!$keyval) - $keyval = $key; + + $keyval = $key; + $credit_key = str_replace("credit.", "", $key); + $searched_credit_key = array_search(str_replace("credit.", "", $key), $this->credit_report_keys) ?? $key; - if (array_key_exists($key, $transformed_credit)) { - $entity[$keyval] = $transformed_credit[$key]; - } elseif (array_key_exists($keyval, $transformed_credit)) { + if (isset($transformed_credit[$credit_key])) { + $entity[$keyval] = $transformed_credit[$credit_key]; + } elseif (isset($transformed_credit[$keyval])) { $entity[$keyval] = $transformed_credit[$keyval]; + } elseif(isset($transformed_credit[$searched_credit_key])){ + $entity[$keyval] = $transformed_credit[$searched_credit_key]; } else { $entity[$keyval] = $this->resolveKey($keyval, $credit, $this->credit_transformer); } + } return $this->decorateAdvancedFields($credit, $entity); diff --git a/app/Export/CSV/VendorExport.php b/app/Export/CSV/VendorExport.php index efc8bf32134e..011484b054a3 100644 --- a/app/Export/CSV/VendorExport.php +++ b/app/Export/CSV/VendorExport.php @@ -67,10 +67,6 @@ class VendorExport extends BaseExport 'vendor.currency', ]; - public array $forced_keys = [ - // 'vendor.status' - ]; - public function __construct(Company $company, array $input) { $this->company = $company; From 2bc75feb2104295d170816d664aed4c36e19a62a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 24 Aug 2023 13:51:48 +1000 Subject: [PATCH 16/39] Fixes for tests --- app/Export/CSV/BaseExport.php | 18 ++++++++++++------ .../Feature/Export/ReportCsvGenerationTest.php | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index f829d0931291..895ce82fb821 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -925,21 +925,27 @@ class BaseExport { $parts = explode(".", $value); - if(in_array($parts[0], ['credit','quote','invoice','purchase_order','recurring_invoice'])){ + if(count($parts) == 2 && in_array($parts[0], ['credit','quote','invoice','purchase_order','recurring_invoice'])){ $entity = "invoice".substr($parts[1], -1); + $header[] = "{$prefix}" . $helper->makeCustomField($this->company->custom_fields, $entity); } - else { - $entity = $parts[0].substr($parts[1], -1); + elseif(count($parts) == 2 && stripos($parts[0], 'contact') !== false) { + $entity = "contact".substr($parts[1], -1); + $custom_field_string = strlen($helper->makeCustomField($this->company->custom_fields, $entity)) > 1 ? $helper->makeCustomField($this->company->custom_fields, $entity) : ctrans("texts.{$parts[1]}"); + $header[] = ctrans("texts.{$parts[0]}") . " " . $custom_field_string; } - - $header[] = $helper->makeCustomField($this->company->custom_fields, $entity); + else{ + nlog("else".$key); + $header[] = "{$prefix}" . ctrans("texts.{$key}"); + } + } else { $header[] = "{$prefix}" . ctrans("texts.{$key}"); } } - +nlog($header); return $header; } } diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index a0ead0a357f2..47e447eaecf9 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -673,7 +673,7 @@ class ReportCsvGenerationTest extends TestCase ])->post('/api/v1/reports/clients', $data); $csv = $response->streamedContent(); - +nlog($csv); $reader = Reader::createFromString($csv); $reader->setHeaderOffset(0); From 4efc0dbb9a5179da2cb102b2550a460f0e230cd7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 24 Aug 2023 17:09:39 +1000 Subject: [PATCH 17/39] Fixes for tests --- app/Export/CSV/BaseExport.php | 24 +++++++++++++++---- .../Export/ReportCsvGenerationTest.php | 18 +++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 895ce82fb821..c65a5180f361 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -840,11 +840,11 @@ class BaseExport $header = []; - // nlog($this->input['report_keys']); - foreach ($this->input['report_keys'] as $value) { $key = array_search($value, $this->entity_keys); + $original_key = $key; + nlog("{$key} => {$value}"); $prefix = ''; @@ -920,20 +920,36 @@ class BaseExport $key = str_replace('payment.', '', $key); $key = str_replace('expense.', '', $key); - // if( in_array($key, ['quote1','quote2','quote3','quote4','credit1','credit2','credit3','credit4','purchase_order1','purchase_order2','purchase_order3','purchase_order4'])) if(stripos($value, 'custom_value') !== false) { $parts = explode(".", $value); if(count($parts) == 2 && in_array($parts[0], ['credit','quote','invoice','purchase_order','recurring_invoice'])){ $entity = "invoice".substr($parts[1], -1); - $header[] = "{$prefix}" . $helper->makeCustomField($this->company->custom_fields, $entity); + $prefix = ctrans("texts.".$parts[0]); + $fallback = "custom_value".substr($parts[1], -1); + $custom_field_label = $helper->makeCustomField($this->company->custom_fields, $entity); + + if(strlen($custom_field_label) > 1) + $header[] = $custom_field_label; + else { + $header[] = $prefix . " ". ctrans("texts.{$fallback}"); + } + } elseif(count($parts) == 2 && stripos($parts[0], 'contact') !== false) { + nlog("2"); $entity = "contact".substr($parts[1], -1); $custom_field_string = strlen($helper->makeCustomField($this->company->custom_fields, $entity)) > 1 ? $helper->makeCustomField($this->company->custom_fields, $entity) : ctrans("texts.{$parts[1]}"); $header[] = ctrans("texts.{$parts[0]}") . " " . $custom_field_string; } + elseif(count($parts) == 2 && in_array(substr($original_key, 0, -1), ['credit','quote','invoice','purchase_order','recurring_invoice'])){ + nlog("3"); + + $custom_field_string = strlen($helper->makeCustomField($this->company->custom_fields, "product".substr($original_key,-1))) > 1 ? $helper->makeCustomField($this->company->custom_fields, "product".substr($original_key,-1)) : ctrans("texts.{$parts[1]}"); + nlog(ctrans("texts.{$parts[0]}") . " " . $custom_field_string); + $header[] = ctrans("texts.{$parts[0]}") . " " . $custom_field_string; + } else{ nlog("else".$key); $header[] = "{$prefix}" . ctrans("texts.{$key}"); diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index 47e447eaecf9..b641fb4b2390 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -996,7 +996,7 @@ nlog($csv); $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('custom 1', $this->getFirstValueByColumn($csv, 'Custom Invoice 1')); + $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')); @@ -1406,14 +1406,14 @@ 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('100', $this->getFirstValueByColumn($csv, 'Credit Amount')); + $this->assertEquals('50', $this->getFirstValueByColumn($csv, 'Credit Balance')); + $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Credit Discount')); + $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Credit PO Number')); + $this->assertEquals('Public', $this->getFirstValueByColumn($csv, 'Credit Public Notes')); + $this->assertEquals('Private', $this->getFirstValueByColumn($csv, 'Credit Private Notes')); + $this->assertEquals('Terms', $this->getFirstValueByColumn($csv, 'Credit Terms')); $data = [ From d6cdb0102164ea75fd45cdc2f09c83334f93ef0d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 24 Aug 2023 20:41:06 +1000 Subject: [PATCH 18/39] Minor fixeS --- app/Export/CSV/BaseExport.php | 11 ++++------- app/Export/CSV/CreditExport.php | 9 ++++----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index c65a5180f361..741f21a2a335 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -845,7 +845,7 @@ class BaseExport $key = array_search($value, $this->entity_keys); $original_key = $key; - nlog("{$key} => {$value}"); + // nlog("{$key} => {$value}"); $prefix = ''; if(!$key) { @@ -938,20 +938,15 @@ class BaseExport } elseif(count($parts) == 2 && stripos($parts[0], 'contact') !== false) { - nlog("2"); $entity = "contact".substr($parts[1], -1); $custom_field_string = strlen($helper->makeCustomField($this->company->custom_fields, $entity)) > 1 ? $helper->makeCustomField($this->company->custom_fields, $entity) : ctrans("texts.{$parts[1]}"); $header[] = ctrans("texts.{$parts[0]}") . " " . $custom_field_string; } elseif(count($parts) == 2 && in_array(substr($original_key, 0, -1), ['credit','quote','invoice','purchase_order','recurring_invoice'])){ - nlog("3"); - $custom_field_string = strlen($helper->makeCustomField($this->company->custom_fields, "product".substr($original_key,-1))) > 1 ? $helper->makeCustomField($this->company->custom_fields, "product".substr($original_key,-1)) : ctrans("texts.{$parts[1]}"); - nlog(ctrans("texts.{$parts[0]}") . " " . $custom_field_string); $header[] = ctrans("texts.{$parts[0]}") . " " . $custom_field_string; } else{ - nlog("else".$key); $header[] = "{$prefix}" . ctrans("texts.{$key}"); } @@ -961,7 +956,9 @@ class BaseExport $header[] = "{$prefix}" . ctrans("texts.{$key}"); } } -nlog($header); + + nlog($header); + return $header; } } diff --git a/app/Export/CSV/CreditExport.php b/app/Export/CSV/CreditExport.php index ffc979513949..bfde3d169df2 100644 --- a/app/Export/CSV/CreditExport.php +++ b/app/Export/CSV/CreditExport.php @@ -41,13 +41,12 @@ class CreditExport extends BaseExport { $query = $this->init(); - $header = []; $headerdisplay = $this->buildHeader(); - foreach ($this->input['report_keys'] as $key => $value) { - $header[] = ['identifier' => $value, 'display_value' => $headerdisplay[$key]]; - } - + $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ + return ['identifier' => $value, 'display_value' => $headerdisplay[$key]]; + })->toArray(); + $report = $query->cursor() ->map(function ($credit) { $row = $this->buildRow($credit); From 8b400020ce8c0ed551ee83b6d56cab14e784173d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 27 Aug 2023 08:41:27 +1000 Subject: [PATCH 19/39] Updates for blacklist rules --- app/Http/Controllers/AccountController.php | 2 +- app/Http/Requests/Email/SendEmailRequest.php | 5 ++++- app/Http/ValidationRules/Account/BlackListRule.php | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 74b4dcd0c4d5..591041c31c66 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -72,7 +72,7 @@ class AccountController extends BaseController MultiDB::findAndSetDbByAccountKey($account->key); - $cu = CompanyUser::where('user_id', $account->users()->first()->id); + $cu = CompanyUser::query()->where('user_id', $account->users()->first()->id); $company_user = $cu->first(); diff --git a/app/Http/Requests/Email/SendEmailRequest.php b/app/Http/Requests/Email/SendEmailRequest.php index b0264eac2b3c..d917742dfac3 100644 --- a/app/Http/Requests/Email/SendEmailRequest.php +++ b/app/Http/Requests/Email/SendEmailRequest.php @@ -51,7 +51,10 @@ class SendEmailRequest extends Request { $input = $this->all(); - $settings = auth()->user()->company()->settings; + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $settings = $user->company()->settings; if (empty($input['template'])) { $input['template'] = ''; diff --git a/app/Http/ValidationRules/Account/BlackListRule.php b/app/Http/ValidationRules/Account/BlackListRule.php index daa13495e671..7d353c429b53 100644 --- a/app/Http/ValidationRules/Account/BlackListRule.php +++ b/app/Http/ValidationRules/Account/BlackListRule.php @@ -19,6 +19,7 @@ use Illuminate\Contracts\Validation\Rule; class BlackListRule implements Rule { private array $blacklist = [ + 'pretreer.com', 'candassociates.com', 'vusra.com', 'fourthgenet.com', From a33b12b28d702855145957dbbd7a645c177cf617 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 27 Aug 2023 09:20:40 +1000 Subject: [PATCH 20/39] Fixes for tests --- app/Export/CSV/CreditExport.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Export/CSV/CreditExport.php b/app/Export/CSV/CreditExport.php index bfde3d169df2..d9641895371b 100644 --- a/app/Export/CSV/CreditExport.php +++ b/app/Export/CSV/CreditExport.php @@ -44,8 +44,8 @@ class CreditExport extends BaseExport $headerdisplay = $this->buildHeader(); $header = collect($this->input['report_keys'])->map(function ($key, $value) use($headerdisplay){ - return ['identifier' => $value, 'display_value' => $headerdisplay[$key]]; - })->toArray(); + return ['identifier' => $value, 'display_value' => $headerdisplay[$value]]; + })->toArray(); $report = $query->cursor() ->map(function ($credit) { From 4cd4cc56e7d331ef9833f74e2ad46e3d316f9bd4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 27 Aug 2023 09:22:46 +1000 Subject: [PATCH 21/39] Fixes for Square --- app/PaymentDrivers/Square/SquareWebhook.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/PaymentDrivers/Square/SquareWebhook.php b/app/PaymentDrivers/Square/SquareWebhook.php index ebe46b0af4ee..c83b73fd4765 100644 --- a/app/PaymentDrivers/Square/SquareWebhook.php +++ b/app/PaymentDrivers/Square/SquareWebhook.php @@ -146,7 +146,7 @@ class SquareWebhook implements ShouldQueue $data = [ 'payment_type' => $this->source_type[$square_payment->source_type], - 'amount' => $payment_hash->amount_with_fee, + 'amount' => $payment_hash->amount_with_fee(), 'transaction_reference' => $square_payment->id, 'gateway_type_id' => GatewayType::BANK_TRANSFER, ]; From 0fff618e80d7555b6a9c835bf4a896287b82ac50 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 27 Aug 2023 09:56:25 +1000 Subject: [PATCH 22/39] v5.7.5 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 23900d674daa..72cf8913386b 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.7.3 \ No newline at end of file +5.7.5 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index 1739429de555..bce6cfbcfb65 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.7.3'), - 'app_tag' => env('APP_TAG','5.7.3'), + 'app_version' => env('APP_VERSION','5.7.5'), + 'app_tag' => env('APP_TAG','5.7.5'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), From 80e66d914643765cfa6de4f659f4ea76fef2a4a8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 27 Aug 2023 22:02:55 +1000 Subject: [PATCH 23/39] 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 24/39] 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 25/39] 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 26/39] 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 27/39] 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 28/39] 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 29/39] 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 30/39] 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 31/39] 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 32/39] 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 33/39] 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 34/39] 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 35/39] 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 36/39] 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 37/39] 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 38/39] 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 39/39] 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'));