From 1a503cf290eef94216f25b3f323975e7b032ca08 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 19 Apr 2023 12:31:27 +1000 Subject: [PATCH] Add credits as an optional display for statements --- app/DataMapper/CompanySettings.php | 6 ++ .../Controllers/ClientStatementController.php | 2 +- .../Statements/CreateStatementRequest.php | 1 + app/Models/Credit.php | 7 -- app/Services/Client/Statement.php | 33 ++++++- app/Services/Pdf/PdfBuilder.php | 85 +++++++++++++++++++ app/Services/PdfMaker/Design.php | 58 +++++++++++++ .../Designs/Utilities/DesignHelpers.php | 4 + app/Utils/HtmlEngine.php | 3 +- lang/en/texts.php | 1 + resources/views/pdf-designs/bold.html | 5 +- resources/views/pdf-designs/business.html | 5 +- resources/views/pdf-designs/calm.html | 25 +----- resources/views/pdf-designs/clean.html | 4 +- resources/views/pdf-designs/creative.html | 4 +- resources/views/pdf-designs/elegant.html | 4 +- resources/views/pdf-designs/hipster.html | 4 +- resources/views/pdf-designs/modern.html | 11 +-- resources/views/pdf-designs/plain.html | 4 +- resources/views/pdf-designs/playful.html | 4 +- resources/views/pdf-designs/tech.html | 4 +- 21 files changed, 226 insertions(+), 48 deletions(-) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index efb4dd4434..89013ae050 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -962,6 +962,12 @@ class CompanySettings extends BaseSettings '$method', '$statement_amount', ], + 'statement_credit_columns' => [ + '$credit.number', + '$credit.date', + '$total', + '$credit.balance', + ], ]; return json_decode(json_encode($variables)); diff --git a/app/Http/Controllers/ClientStatementController.php b/app/Http/Controllers/ClientStatementController.php index 4b41d43ba3..38f1577bb3 100644 --- a/app/Http/Controllers/ClientStatementController.php +++ b/app/Http/Controllers/ClientStatementController.php @@ -107,7 +107,7 @@ class ClientStatementController extends BaseController } $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', 'show_credits_table']), $send_email ); diff --git a/app/Http/Requests/Statements/CreateStatementRequest.php b/app/Http/Requests/Statements/CreateStatementRequest.php index 2d2adac6e7..9df2dee4e4 100644 --- a/app/Http/Requests/Statements/CreateStatementRequest.php +++ b/app/Http/Requests/Statements/CreateStatementRequest.php @@ -35,6 +35,7 @@ class CreateStatementRequest extends Request 'client_id' => 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id, 'show_payments_table' => 'boolean', 'show_aging_table' => 'boolean', + 'show_credits_table' => 'boolean', 'status' => 'string', ]; } diff --git a/app/Models/Credit.php b/app/Models/Credit.php index c10df19ccc..323486d34b 100644 --- a/app/Models/Credit.php +++ b/app/Models/Credit.php @@ -299,10 +299,6 @@ class Credit extends BaseModel 'custom_surcharge2', 'custom_surcharge3', 'custom_surcharge4', - // 'custom_surcharge_tax1', - // 'custom_surcharge_tax2', - // 'custom_surcharge_tax3', - // 'custom_surcharge_tax4', 'design_id', 'assigned_user_id', 'exchange_rate', @@ -311,9 +307,6 @@ class Credit extends BaseModel ]; protected $casts = [ - // 'date' => 'date:Y-m-d', - // 'due_date' => 'date:Y-m-d', - // 'partial_due_date' => 'date:Y-m-d', 'line_items' => 'object', 'backup' => 'object', 'updated_at' => 'timestamp', diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index 4881aa8315..01c7deee6a 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -28,6 +28,7 @@ use App\Utils\PhantomJS\Phantom; use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use App\Models\Credit; class Statement { @@ -71,6 +72,7 @@ class Statement 'variables' => $variables, 'invoices' => $this->getInvoices(), 'payments' => $this->getPayments(), + 'credits' => $this->getCredits(), 'aging' => $this->getAging(), ], \App\Services\PdfMaker\Design::STATEMENT), 'variables' => $variables, @@ -205,13 +207,18 @@ class Statement $this->options['show_aging_table'] = false; } + if (! \array_key_exists('show_credits_table', $this->options)) { + $this->options['show_credits_table'] = false; + } + + return $this; } /** * The collection of invoices for the statement. * - * @return Invoice[]|\Illuminate\Database\Eloquent\Collection + * @return Invoice[]|\Illuminate\Support\LazyCollection */ protected function getInvoices(): \Illuminate\Support\LazyCollection { @@ -255,7 +262,7 @@ class Statement /** * The collection of payments for the statement. * - * @return Payment[]|\Illuminate\Database\Eloquent\Collection + * @return Payment[]|\Illuminate\Support\LazyCollection */ protected function getPayments(): \Illuminate\Support\LazyCollection { @@ -270,6 +277,28 @@ class Statement ->cursor(); } + /** + * The collection of credits for the statement. + * + * @return Credit[]|\Illuminate\Support\LazyCollection + */ + protected function getCredits(): \Illuminate\Support\LazyCollection + { + return Credit::withTrashed() + ->with('client.country', 'invoices') + ->where('is_deleted', false) + ->where('company_id', $this->client->company_id) + ->where('client_id', $this->client->id) + ->whereIn('status_id', [Credit::STATUS_SENT, Credit::STATUS_PARTIAL, Credit::STATUS_APPLIED]) + ->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])]) + ->where(function ($query) { + $query->whereDate('due_date', '>=', $this->options['end_date']) + ->orWhereNull('due_date'); + }) + ->orderBy('date', 'ASC') + ->cursor(); + } + /** * Get correct invitation ID. * diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index ffe2ea2b0c..3977594862 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -187,6 +187,14 @@ class PdfBuilder 'id' => 'statement-payment-table-totals', 'elements' => $this->statementPaymentTableTotals(), ], + 'statement-credits-table' => [ + 'id' => 'statement-credits-table', + 'elements' => $this->statementCreditTable(), + ], + 'statement-credit-table-totals' => [ + 'id' => 'statement-credit-table-totals', + 'elements' => $this->statementCreditTableTotals(), + ], 'statement-aging-table' => [ 'id' => 'statement-aging-table', 'elements' => $this->statementAgingTable(), @@ -214,6 +222,56 @@ class PdfBuilder ['element' => 'p', 'content' => '$outstanding_label: ' . $this->service->config->formatMoney($outstanding)], ]; } + + /** + * Parent method for building payments table within statement. + * + * @return array + */ + public function statementCreditTable(): array + { + if (is_null($this->service->options['credits'])) { + return []; + } + + if (\array_key_exists('show_credits_table', $this->service->options) && $this->service->options['show_credits_table'] === false) { + return []; + } + + $tbody = []; + + foreach ($this->service->options['credits'] as $credit) { + $element = ['element' => 'tr', 'elements' => []]; + + $element['elements'][] = ['element' => 'td', 'content' => $credit->number]; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($credit->date, $this->service->config->client->date_format(), $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($credit->amount) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($credit->balance) ?: ' ']; + + $tbody[] = $element; + } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_invoice')], + ['element' => 'tbody', 'elements' => $tbody], + ]; + + } + + /** + * Parent method for building invoice table totals + * for statements. + * + * @return array + */ + public function statementCreditTableTotals(): array + { + $outstanding = $this->service->options['credits']->sum('balance'); + + return [ + ['element' => 'p', 'content' => '$credit.balance_label: ' . $this->service->config->formatMoney($outstanding)], + ]; + } /** @@ -312,6 +370,33 @@ class PdfBuilder } + /** + * Generates the statement aging table + * + * @return array + * + */ + public function statementCreditsTable(): array + { + if (\array_key_exists('show_credits_table', $this->service->options) && $this->service->options['show_credits_table'] === false) { + return []; + } + + $elements = [ + ['element' => 'thead', 'elements' => []], + ['element' => 'tbody', 'elements' => [ + ['element' => 'tr', 'elements' => []], + ]], + ]; + + foreach ($this->service->options['credits'] as $column => $value) { + $elements[0]['elements'][] = ['element' => 'th', 'content' => $column]; + $elements[1]['elements'][] = ['element' => 'td', 'content' => $value]; + } + + return $elements; + } + /** * Generates the purchase order sections * diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index b2ffd3792d..1c180aa87a 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -49,6 +49,8 @@ class Design extends BaseDesign public $invoices; + public $credits; + public $payments; public $settings_object; @@ -144,6 +146,14 @@ class Design extends BaseDesign 'id' => 'task-table', 'elements' => $this->taskTable(), ], + 'statement-credit-table' => [ + 'id' => 'statement-credit-table', + 'elements' => $this->statementCreditTable(), + ], + 'statement-credit-table-totals' => [ + 'id' => 'statement-credit-table-totals', + 'elements' => $this->statementInvoiceTableTotals(), + ], 'statement-invoice-table' => [ 'id' => 'statement-invoice-table', 'elements' => $this->statementInvoiceTable(), @@ -616,6 +626,54 @@ class Design extends BaseDesign ]; } + /** + * Parent method for building payments table within statement. + * + * @return array + */ + public function statementCreditTable(): array + { + if (is_null($this->credits) && $this->type !== self::STATEMENT) { + return []; + } + + if (\array_key_exists('show_credits_table', $this->options) && $this->options['show_credits_table'] === false) { + return []; + } + + $tbody = []; + + foreach ($this->credits as $credit) { + $element = ['element' => 'tr', 'elements' => []]; + + $element['elements'][] = ['element' => 'td', 'content' => $credit->number]; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($credit->date, $this->client->date_format(), $this->client->locale()) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($credit->amount, $this->client)]; + $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($credit->balance, $this->client)]; + + $tbody[] = $element; + } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_credit')], + ['element' => 'tbody', 'elements' => $tbody], + ]; + + } + + public function statementCreditTableTotals(): array + { + if ($this->type !== self::STATEMENT) { + return []; + } + + $outstanding = $this->credits->sum('balance'); + + return [ + ['element' => 'p', 'content' => '$credit.balance_label: ' . Number::formatMoney($outstanding, $this->client)], + ]; + } + public function statementPaymentTableTotals(): array { if (is_null($this->payments) || !$this->payments->first() || $this->type !== self::STATEMENT) { diff --git a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php index bef1215546..1e9a4ccab5 100644 --- a/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php +++ b/app/Services/PdfMaker/Designs/Utilities/DesignHelpers.php @@ -54,6 +54,10 @@ trait DesignHelpers $this->payments = $this->context['payments']; } + if (isset($this->context['credits'])) { + $this->credits = $this->context['credits']; + } + if (isset($this->context['aging'])) { $this->aging = $this->context['aging']; } diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index cb51089e32..ea3386faf6 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -360,7 +360,8 @@ class HtmlEngine $data['$credit.po_number'] = &$data['$invoice.po_number']; $data['$credit.date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.credit_date')]; $data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.balance')]; - $data['$credit.balance'] = &$data['$balance']; + $data['$credit.balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')]; + $data['$invoice.balance'] = &$data['$balance']; $data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->client) ?: ' ', 'label' => ctrans('texts.taxes')]; diff --git a/lang/en/texts.php b/lang/en/texts.php index 295c80ac88..0767486b46 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5058,6 +5058,7 @@ $LANG = array( 'converted_payment_balance' => 'Converted Payment Balance', 'total_hours' => 'Total Hours', 'date_picker_hint' => 'Use +days to set the date in the future', + 'app_help_link' => 'More information', ); diff --git a/resources/views/pdf-designs/bold.html b/resources/views/pdf-designs/bold.html index e0c7021aad..14c6350fe4 100644 --- a/resources/views/pdf-designs/bold.html +++ b/resources/views/pdf-designs/bold.html @@ -435,7 +435,8 @@
- +
+
$status_logo
@@ -465,7 +466,7 @@ $entity_images 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', - 'vendor-details', 'client-details' + 'client-details', 'vendor-details', 'swiss-qr', 'shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => { diff --git a/resources/views/pdf-designs/business.html b/resources/views/pdf-designs/business.html index a291ee4ff1..b5377d5805 100644 --- a/resources/views/pdf-designs/business.html +++ b/resources/views/pdf-designs/business.html @@ -402,6 +402,8 @@
+
+
$status_logo
@@ -432,7 +434,8 @@ $entity_images let tables = [ 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', - 'vendor-details','client-details' + 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', + 'client-details', 'vendor-details', 'swiss-qr', 'shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => { diff --git a/resources/views/pdf-designs/calm.html b/resources/views/pdf-designs/calm.html index 26f30e0165..0499ee3c47 100644 --- a/resources/views/pdf-designs/calm.html +++ b/resources/views/pdf-designs/calm.html @@ -413,6 +413,8 @@
+
+
$status_logo
@@ -442,7 +444,7 @@ 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', - 'vendor-details', 'client-details' + 'client-details', 'vendor-details', 'swiss-qr', 'shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => { @@ -456,24 +458,3 @@ - - - \ No newline at end of file diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index 0c197fe05e..5b0005a81a 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -384,6 +384,8 @@
+
+
$status_logo
@@ -415,7 +417,7 @@ $entity_images 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', - 'client-details','vendor-details', 'swiss-qr','shipping-details' + 'client-details','vendor-details', 'swiss-qr','shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => { diff --git a/resources/views/pdf-designs/creative.html b/resources/views/pdf-designs/creative.html index ff267027f1..b197a24047 100644 --- a/resources/views/pdf-designs/creative.html +++ b/resources/views/pdf-designs/creative.html @@ -385,6 +385,8 @@
+
+
$status_logo
@@ -417,7 +419,7 @@ $entity_images 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', - 'vendor-details', 'client-details' + 'client-details', 'vendor-details', 'swiss-qr', 'shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => { diff --git a/resources/views/pdf-designs/elegant.html b/resources/views/pdf-designs/elegant.html index 312719b341..4476b3ba43 100644 --- a/resources/views/pdf-designs/elegant.html +++ b/resources/views/pdf-designs/elegant.html @@ -391,6 +391,8 @@
+
+
$status_logo
@@ -422,7 +424,7 @@ $entity_images 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', - 'client-details','vendor-details' + 'client-details', 'vendor-details', 'swiss-qr', 'shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => { diff --git a/resources/views/pdf-designs/hipster.html b/resources/views/pdf-designs/hipster.html index 7d3ae9964e..dab0b8071a 100644 --- a/resources/views/pdf-designs/hipster.html +++ b/resources/views/pdf-designs/hipster.html @@ -428,6 +428,8 @@
+
+
$status_logo
@@ -459,7 +461,7 @@ $entity_images 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', - 'vendor-details','client-details' + 'client-details', 'vendor-details', 'swiss-qr', 'shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => { diff --git a/resources/views/pdf-designs/modern.html b/resources/views/pdf-designs/modern.html index 011f307f87..88e56a3d25 100644 --- a/resources/views/pdf-designs/modern.html +++ b/resources/views/pdf-designs/modern.html @@ -405,7 +405,8 @@
- +
+
@@ -434,10 +435,10 @@ $entity_images // Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present. document.addEventListener('DOMContentLoaded', () => { let tables = [ - 'product-table', 'task-table', 'delivery-note-table', - 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', - 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', - 'client-details','vendor-details' + 'product-table', 'task-table', 'delivery-note-table', + 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', + 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', + 'client-details', 'vendor-details', 'swiss-qr', 'shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => { diff --git a/resources/views/pdf-designs/plain.html b/resources/views/pdf-designs/plain.html index 4e5be6f997..b77804d852 100644 --- a/resources/views/pdf-designs/plain.html +++ b/resources/views/pdf-designs/plain.html @@ -352,6 +352,8 @@
+
+
$status_logo
@@ -382,7 +384,7 @@ $entity_images 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', - 'client-details', 'vendor-details' + 'client-details', 'vendor-details', 'swiss-qr', 'shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => { diff --git a/resources/views/pdf-designs/playful.html b/resources/views/pdf-designs/playful.html index 30053f64a2..3d92888abd 100644 --- a/resources/views/pdf-designs/playful.html +++ b/resources/views/pdf-designs/playful.html @@ -458,6 +458,8 @@
+
+
$status_logo
@@ -509,7 +511,7 @@ $entity_images 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', - 'client-details','vendor-details' + 'client-details', 'vendor-details', 'swiss-qr', 'shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => { diff --git a/resources/views/pdf-designs/tech.html b/resources/views/pdf-designs/tech.html index f19b6737d2..09a2c29c06 100644 --- a/resources/views/pdf-designs/tech.html +++ b/resources/views/pdf-designs/tech.html @@ -424,6 +424,8 @@
+
+
$status_logo
@@ -455,7 +457,7 @@ $entity_images 'product-table', 'task-table', 'delivery-note-table', 'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals', 'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table', - 'client-details','vendor-details' + 'client-details', 'vendor-details', 'swiss-qr', 'shipping-details', 'statement-credit-table', 'statement-credit-table-totals', ]; tables.forEach((tableIdentifier) => {