From 9195addb372c8a1fc060d1a1af2dee142aa27167 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Tue, 28 May 2024 22:12:28 +0100 Subject: [PATCH 1/3] "Real" entity numbers in Invoice Settings preview This refactors the GeneratesCounter trait slightly, such that arbitrary entity numbers can be formatted according to the given padding/pattern. With that small abstraction we can use the trait in the PdfMock instance to show the exmple entity number using real patterns without actually incrementing it or checking the number is available in the database. --- app/Services/Pdf/PdfMock.php | 12 ++++++++++- app/Utils/Traits/GeneratesCounter.php | 29 ++++++++++++++++++++------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index 4d7f6a7a50dc..503472d2accf 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -27,11 +27,13 @@ use App\Models\PurchaseOrderInvitation; use App\Models\Quote; use App\Models\QuoteInvitation; use App\Models\Vendor; +use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\MakesHash; class PdfMock { use MakesHash; + use GeneratesCounter; private mixed $mock; @@ -206,6 +208,14 @@ class PdfMock */ public function getStubVariables(): array { + // Although $this->mock is the Invoice/etc entity, we need the invitation to get company details. + $entity_number = $this->getFormattedEntityNumber( + $this->mock->invitation, + 29, + $this->settings->counter_padding, + $this->settings->invoice_number_pattern + ); + return ['values' => [ '$client.shipping_postal_code' => '46420', @@ -370,7 +380,7 @@ class PdfMock '$company.phone' => $this->settings->phone, '$company.state' => $this->settings->state, '$credit.number' => '0029', - '$entity_number' => '0029', + '$entity_number' => $entity_number, '$credit_number' => '0029', '$global_margin' => '6.35mm', '$contact.phone' => '681-480-9828', diff --git a/app/Utils/Traits/GeneratesCounter.php b/app/Utils/Traits/GeneratesCounter.php index 731e39a504ea..39448d02dbb3 100644 --- a/app/Utils/Traits/GeneratesCounter.php +++ b/app/Utils/Traits/GeneratesCounter.php @@ -288,7 +288,6 @@ trait GeneratesCounter */ public function getNextProjectNumber(Project $project): string { - $entity_number = $this->getNextEntityNumber(Project::class, $project->client, false); return $this->replaceUserVars($project, $entity_number); @@ -412,7 +411,7 @@ trait GeneratesCounter * * @param string $pattern * @param string $prefix - * @return string The padded and prefixed entity number + * @return string The padded, prefixed and unique entity number */ private function checkEntityNumber($class, $entity, $counter, $padding, $pattern, $prefix = ''): string { @@ -420,11 +419,7 @@ trait GeneratesCounter $check_counter = 1; do { - $number = $this->padCounter($counter, $padding); - - $number = $this->applyNumberPattern($entity, $number, $pattern); - - $number = $this->prefixCounter($number, $prefix); + $number = $this->getFormattedEntityNumber($entity, $counter, $padding, $pattern); $check = $class::where('company_id', $entity->company_id)->where('number', $number)->withTrashed()->exists(); @@ -443,6 +438,26 @@ trait GeneratesCounter return $number; } + /** + * Formats the entity number according to pattern, prefix and padding. + * + * @param Collection $entity The entity ie App\Models\Client, Invoice, Quote etc + * @param int $counter The counter + * @param int $padding The padding + * @param string $pattern + * @param string $prefix + * + * @return string The padded and prefixed entity number + */ + public function getFormattedEntityNumber($entity, $counter, $padding, $pattern, $prefix = ''): string + { + $number = $this->padCounter($counter, $padding); + + $number = $this->applyNumberPattern($entity, $number, $pattern); + + return $this->prefixCounter($number, $prefix); + } + /*Check if a number is available for use. */ public function checkNumberAvailable($class, $entity, $number): bool { From e1d08d34bfe775d8cd82a67c3729722103289c0d Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Wed, 29 May 2024 04:01:50 +0100 Subject: [PATCH 2/3] Apply formatted numbers to design editor preview If a custom Invoice Number Pattern is defined, this will update the mock invoice with a formatted entity number, rather than the EAN13 from faker in the invoice factory. This doesn't apply once a real invoice has been created, but it can still be useful for designing during initial setup. --- app/Http/Controllers/PreviewController.php | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 08537427a5d5..a23465d688d1 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -29,6 +29,7 @@ use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; use App\Utils\Ninja; use App\Utils\PhantomJS\Phantom; +use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesInvoiceHtml; use App\Utils\Traits\Pdf\PageNumbering; @@ -40,6 +41,7 @@ use Twig\Error\SyntaxError; class PreviewController extends BaseController { + use GeneratesCounter; use MakesHash; use MakesInvoiceHtml; use PageNumbering; @@ -404,23 +406,24 @@ class PreviewController extends BaseController /** @var \App\Models\Client $client */ $client = Client::factory()->create([ - 'user_id' => auth()->user()->id, + 'user_id' => $user->id, 'company_id' => $company->id, ]); /** @var \App\Models\ClientContact $contact */ $contact = ClientContact::factory()->create([ - 'user_id' => auth()->user()->id, + 'user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, 'is_primary' => 1, 'send_email' => true, ]); - /** @var \App\Models\Invoice $invoice */ + $settings = $company->settings; + /** @var \App\Models\Invoice $invoice */ $invoice = Invoice::factory()->create([ - 'user_id' => auth()->user()->id, + 'user_id' => $user->id, 'company_id' => $company->id, 'client_id' => $client->id, 'terms' => $company->settings->invoice_terms, @@ -428,8 +431,18 @@ class PreviewController extends BaseController 'public_notes' => 'Sample Public Notes', ]); + if ($settings->invoice_number_pattern) { + $invoice->number = $this->getFormattedEntityNumber( + $invoice, + rand(1, 9999), + $settings->counter_padding ?: 4, + $settings->invoice_number_pattern, + ); + $invoice->save(); + } + $invitation = InvoiceInvitation::factory()->create([ - 'user_id' => auth()->user()->id, + 'user_id' => $user->id, 'company_id' => $company->id, 'invoice_id' => $invoice->id, 'client_contact_id' => $contact->id, @@ -454,7 +467,7 @@ class PreviewController extends BaseController 'template' => $design->elements([ 'client' => $invoice->client, 'entity' => $invoice, - 'pdf_variables' => (array) $invoice->company->settings->pdf_variables, + 'pdf_variables' => (array) $settings->pdf_variables, 'products' => request()->design['design']['product'], ]), 'variables' => $html->generateLabelsAndValues(), From bd49aa1a159b9cb3e5cd5a8bbcb43df192160712 Mon Sep 17 00:00:00 2001 From: Dave Shoreman Date: Wed, 29 May 2024 17:06:44 +0100 Subject: [PATCH 3/3] Ensure correct pattern for non-invoice entities A simple `in_array` check would work here, except there are a lot of valid keys for the number pattern settings so it's easier this way. --- app/Services/Pdf/PdfMock.php | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index 503472d2accf..495a7e6a812b 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -208,13 +208,19 @@ class PdfMock */ public function getStubVariables(): array { - // Although $this->mock is the Invoice/etc entity, we need the invitation to get company details. - $entity_number = $this->getFormattedEntityNumber( - $this->mock->invitation, - 29, - $this->settings->counter_padding, - $this->settings->invoice_number_pattern - ); + $entity_pattern = $this->entity_string.'_number_pattern'; + $entity_number = '0029'; + + if (!empty($this->settings->{$entity_pattern})) { + // Although $this->mock is the Invoice/etc entity, + // we need the invitation to get company details. + $entity_number = $this->getFormattedEntityNumber( + $this->mock->invitation, + (int) $entity_number, + $this->settings->counter_padding, + $this->settings->{$entity_pattern}, + ); + } return ['values' => [