From c51dd313b9d7b87c2390bdd3c8bf37c73cfe55a8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 17 Jan 2023 11:00:12 +1100 Subject: [PATCH] Tests for emailing client statements --- app/DataMapper/BaseSettings.php | 12 +-- app/DataMapper/CompanySettings.php | 3 +- .../Controllers/ClientStatementController.php | 12 ++- app/Http/Controllers/PreviewController.php | 4 - app/Models/BaseModel.php | 23 ++--- app/Services/Client/ClientService.php | 95 ++++++++++++++++--- app/Services/Client/Statement.php | 15 +-- app/Services/Scheduler/SchedulerService.php | 36 +------ app/Utils/HtmlEngine.php | 2 - lang/en/texts.php | 3 +- tests/Feature/ClientApiTest.php | 58 +++++++++++ 11 files changed, 172 insertions(+), 91 deletions(-) diff --git a/app/DataMapper/BaseSettings.php b/app/DataMapper/BaseSettings.php index d97304ee4a..2a67d68627 100644 --- a/app/DataMapper/BaseSettings.php +++ b/app/DataMapper/BaseSettings.php @@ -12,15 +12,16 @@ namespace App\DataMapper; /** - * ClientSettings. + * BaseSettings. */ class BaseSettings { + //@deprecated public function __construct($obj) { - foreach ($obj as $key => $value) { - $obj->{$key} = $value; - } + // foreach ($obj as $key => $value) { + // $obj->{$key} = $value; + // } } public static function setCasts($obj, $casts) @@ -57,7 +58,4 @@ class BaseSettings } } - public static function castSingleAttribute($key, $data) - { - } } diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 97459a8e5c..e65eedfa9e 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -734,8 +734,9 @@ class CompanySettings extends BaseSettings * and always ensure an up to date class is returned. * * @param $obj + * @deprecated */ - public function __construct($obj) + public function __construct() { // parent::__construct($obj); } diff --git a/app/Http/Controllers/ClientStatementController.php b/app/Http/Controllers/ClientStatementController.php index 052ddc91a9..1e4db875f9 100644 --- a/app/Http/Controllers/ClientStatementController.php +++ b/app/Http/Controllers/ClientStatementController.php @@ -109,16 +109,24 @@ class ClientStatementController extends BaseController */ public function statement(CreateStatementRequest $request) { + $send_email = false; + + if($request->has('send_email') && $request->send_email == 'true') + $send_email = true; + $pdf = $request->client()->service()->statement( - $request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table', 'status']) + $request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table', 'status']), $send_email ); + if($send_email) + return response()->json(['message' => ctrans('texts.email_queued')], 200); + if ($pdf) { return response()->streamDownload(function () use ($pdf) { echo $pdf; }, ctrans('texts.statement').'.pdf', ['Content-Type' => 'application/pdf']); } - return response()->json(['message' => 'Something went wrong. Please check logs.']); + return response()->json(['message' => ctrans('texts.error_title')], 500); } } diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 2b65e6b615..46c25b79f7 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -16,7 +16,6 @@ use App\Factory\CreditFactory; use App\Factory\InvoiceFactory; use App\Factory\QuoteFactory; use App\Factory\RecurringInvoiceFactory; -use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Http\Requests\Preview\PreviewInvoiceRequest; use App\Jobs\Util\PreviewPdf; use App\Libraries\MultiDB; @@ -44,7 +43,6 @@ use App\Utils\Traits\MakesInvoiceHtml; use App\Utils\Traits\Pdf\PageNumbering; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Lang; use Illuminate\Support\Facades\Response; use Turbo124\Beacon\Facades\LightLogs; @@ -109,8 +107,6 @@ class PreviewController extends BaseController $class = "App\Models\\$entity"; - $pdf_class = "App\Jobs\\$entity\\Create{$entity}Pdf"; - $entity_obj = $class::whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first(); if (! $entity_obj) { diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index fdd51dff88..11b8becb2b 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -12,15 +12,12 @@ namespace App\Models; use App\DataMapper\ClientSettings; -use App\DataMapper\CompanySettings; -use App\Jobs\Entity\CreateEntityPdf; use App\Utils\Traits\MakesHash; use App\Utils\Traits\UserSessionAttributes; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; @@ -115,17 +112,17 @@ class BaseModel extends Model * reference to the parent class. * * @param $key The key of property - * @return + * @deprecated */ - public function getSettingsByKey($key) - { - /* Does Setting Exist @ client level */ - if (isset($this->getSettings()->{$key})) { - return $this->getSettings()->{$key}; - } else { - return (new CompanySettings($this->company->settings))->{$key}; - } - } + // public function getSettingsByKey($key) + // { + // /* Does Setting Exist @ client level */ + // if (isset($this->getSettings()->{$key})) { + // return $this->getSettings()->{$key}; + // } else { + // return (new CompanySettings($this->company->settings))->{$key}; + // } + // } public function setSettingsByEntity($entity, $settings) { diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index e60fee96e2..3fb7b920f0 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -15,23 +15,28 @@ use App\Models\Client; use App\Models\Credit; use App\Services\Client\Merge; use App\Services\Client\PaymentMethod; +use App\Services\Email\EmailObject; +use App\Services\Email\EmailService; use App\Utils\Number; -use Illuminate\Database\Eloquent\Collection; +use App\Utils\Traits\MakesDates; +use Illuminate\Mail\Mailables\Address; +use Illuminate\Support\Facades\DB; class ClientService { - private $client; + use MakesDates; - public function __construct(Client $client) - { - $this->client = $client; - } + private string $client_start_date; + + private string $client_end_date; + + public function __construct(private Client $client){} public function updateBalance(float $amount) { try { - \DB::connection(config('database.default'))->transaction(function () use($amount) { + DB::connection(config('database.default'))->transaction(function () use($amount) { $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); $this->client->balance += $amount; @@ -51,7 +56,7 @@ class ClientService { try { - \DB::connection(config('database.default'))->transaction(function () use($balance, $paid_to_date) { + DB::connection(config('database.default'))->transaction(function () use($balance, $paid_to_date) { $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); $this->client->balance += $balance; @@ -64,16 +69,14 @@ class ClientService nlog("DB ERROR " . $throwable->getMessage()); } - - return $this; + } public function updatePaidToDate(float $amount) { - // $this->client->paid_to_date += $amount; - \DB::connection(config('database.default'))->transaction(function () use($amount) { + DB::connection(config('database.default'))->transaction(function () use($amount) { $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first(); $this->client->paid_to_date += $amount; @@ -82,17 +85,21 @@ class ClientService }, 1); return $this; + } public function adjustCreditBalance(float $amount) { + $this->client->credit_balance += $amount; return $this; + } public function getCreditBalance() :float { + $credits = Credit::withTrashed()->where('client_id', $this->client->id) ->where('is_deleted', false) ->where(function ($query) { @@ -102,6 +109,7 @@ class ClientService ->orderBy('created_at', 'ASC'); return Number::roundValue($credits->sum('balance'), $this->client->currency()->precision); + } public function getCredits() @@ -132,12 +140,71 @@ class ClientService * Generate the client statement. * * @param array $options + * @param bool $send_email determines if we should send this statement direct to the client */ - public function statement(array $options = []) + public function statement(array $options = [], bool $send_email = false) { - return (new Statement($this->client, $options))->run(); + $statement = (new Statement($this->client, $options)); + + $pdf = $statement->run(); + + if($send_email) + return $this->emailStatement($pdf, $statement->options); + + return $pdf; } + /** + * Emails the statement to the client + * + * @param mixed $pdf The pdf blob + * @param array $options The statement options array + * @return void + */ + private function emailStatement($pdf, array $options): void + { + + $this->client_start_date = $this->translateDate($options['start_date'], $this->client->date_format(), $this->client->locale()); + $this->client_end_date = $this->translateDate($options['end_date'], $this->client->date_format(), $this->client->locale()); + + $email_service = new EmailService($this->buildStatementMailableData($pdf), $this->client->company); + + $email_service->send(); + + } + + /** + * Builds and returns an EmailObject for Client Statements + * + * @param mixed $pdf The PDF to send + * @return EmailObject The EmailObject to send + */ + public function buildStatementMailableData($pdf) :EmailObject + { + + $email_object = new EmailObject; + $email_object->to = [new Address($this->client->present()->email(), $this->client->present()->name())]; + $email_object->attachments = [['file' => base64_encode($pdf), 'name' => ctrans('texts.statement') . ".pdf"]]; + $email_object->settings = $this->client->getMergedSettings(); + $email_object->company = $this->client->company; + $email_object->client = $this->client; + $email_object->email_template_subject = 'email_subject_statement'; + $email_object->email_template_body = 'email_template_statement'; + $email_object->variables = [ + '$client' => $this->client->present()->name(), + '$start_date' => $this->client_start_date, + '$end_date' => $this->client_end_date, + ]; + + return $email_object; + + } + + /** + * Saves the client instance + * + * @return Client The Client Model + */ public function save() :Client { $this->client->save(); diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index 2a91f7096f..9b0d52e84a 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -32,25 +32,16 @@ class Statement { use PdfMakerTrait; - protected Client $client; - /** * @var Invoice|Payment|null */ protected $entity; - protected array $options; - protected bool $rollback = false; - public function __construct(Client $client, array $options) - { - $this->client = $client; + public function __construct(protected Client $client, public array $options){} - $this->options = $options; - } - - public function run(): ?string + public function run() :?string { $this ->setupOptions() @@ -110,7 +101,7 @@ class Statement $maker = null; $state = null; - + return $pdf; } diff --git a/app/Services/Scheduler/SchedulerService.php b/app/Services/Scheduler/SchedulerService.php index d8b1612e55..f8c507ee9f 100644 --- a/app/Services/Scheduler/SchedulerService.php +++ b/app/Services/Scheduler/SchedulerService.php @@ -11,18 +11,10 @@ namespace App\Services\Scheduler; -use App\DataMapper\EmailTemplateDefaults; -use App\Mail\Client\ClientStatement; use App\Models\Client; use App\Models\Scheduler; -use App\Services\Email\EmailMailable; -use App\Services\Email\EmailObject; -use App\Services\Email\EmailService; -use App\Utils\Ninja; use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; -use Illuminate\Mail\Mailables\Address; -use Illuminate\Support\Str; class SchedulerService { @@ -54,7 +46,6 @@ class SchedulerService if(count($this->scheduler->parameters['clients']) >= 1) $query->where('id', $this->transformKeys($this->scheduler->parameters['clients'])); - $query->cursor() ->each(function ($_client){ @@ -62,10 +53,7 @@ class SchedulerService $statement_properties = $this->calculateStatementProperties(); //work out the date range - $pdf = $_client->service()->statement($statement_properties); - - $email_service = new EmailService($this->buildMailableData($pdf), $_client->company); - $email_service->send(); + $pdf = $_client->service()->statement($statement_properties,true); //calculate next run dates; @@ -77,9 +65,6 @@ class SchedulerService { $start_end = $this->calculateStartAndEndDates(); - $this->client_start_date = $this->translateDate($start_end[0], $this->client->date_format(), $this->client->locale()); - $this->client_end_date = $this->translateDate($start_end[1], $this->client->date_format(), $this->client->locale()); - return [ 'start_date' =>$start_end[0], 'end_date' =>$start_end[1], @@ -104,26 +89,7 @@ class SchedulerService }; } - private function buildMailableData($pdf) - { - $email_object = new EmailObject; - $email_object->to = [new Address($this->client->present()->email(), $this->client->present()->name())]; - $email_object->attachments = [['file' => base64_encode($pdf), 'name' => ctrans('texts.statement') . ".pdf"]]; - $email_object->settings = $this->client->getMergedSettings(); - $email_object->company = $this->client->company; - $email_object->client = $this->client; - $email_object->email_template_subject = 'email_subject_statement'; - $email_object->email_template_body = 'email_template_statement'; - $email_object->variables = [ - '$client' => $this->client->present()->name(), - '$start_date' => $this->client_start_date, - '$end_date' => $this->client_end_date, - ]; - - return $email_object; - - } } \ No newline at end of file diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 51093b6f40..d85c5b5324 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -20,12 +20,10 @@ use App\Models\GatewayType; use App\Models\InvoiceInvitation; use App\Models\QuoteInvitation; use App\Models\RecurringInvoiceInvitation; -use App\Services\PdfMaker\Designs\Utilities\DesignHelpers; use App\Utils\Ninja; use App\Utils\Number; use App\Utils\Traits\AppSetup; use App\Utils\Traits\MakesDates; -use App\Utils\transformTranslations; use Exception; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; diff --git a/lang/en/texts.php b/lang/en/texts.php index f983eaf023..bb2ad322e4 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -4924,7 +4924,8 @@ $LANG = array( 'action_add_to_invoice' => 'Add To Invoice', 'danger_zone' => 'Danger Zone', 'import_completed' => 'Import completed', - 'client_statement_body' => 'Your statement from :start_date to :end_date is attached.' + 'client_statement_body' => 'Your statement from :start_date to :end_date is attached.', + 'email_queued' => 'Email queued', ); diff --git a/tests/Feature/ClientApiTest.php b/tests/Feature/ClientApiTest.php index 40b11fc70a..62dde8293c 100644 --- a/tests/Feature/ClientApiTest.php +++ b/tests/Feature/ClientApiTest.php @@ -53,6 +53,64 @@ class ClientApiTest extends TestCase Model::reguard(); } + public function testClientStatement() + { + + $response = null; + + $data = [ + 'client_id' => $this->client->hashed_id, + 'start_date' => '2000-01-01', + 'end_date' => '2023-01-01', + 'show_aging_table' => true, + 'show_payments_table' => true, + 'status' => 'paid', + ]; + + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/client_statement', $data); + } catch (ValidationException $e) { + $message = json_decode($e->validator->getMessageBag(), 1); + nlog($message); + } + + $response->assertStatus(200); + + } + + public function testClientStatementEmail() + { + + $response = null; + + $data = [ + 'client_id' => $this->client->hashed_id, + 'start_date' => '2000-01-01', + 'end_date' => '2023-01-01', + 'show_aging_table' => true, + 'show_payments_table' => true, + 'status' => 'paid', + ]; + + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/client_statement?send_email=true', $data); + } catch (ValidationException $e) { + $message = json_decode($e->validator->getMessageBag(), 1); + nlog($message); + } + + $response->assertStatus(200); + + } + + public function testCsvImportRepositoryPersistance() { Client::unguard();