1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-12 14:12:44 +01:00

Add credits as an optional display for statements

This commit is contained in:
David Bomba 2023-04-19 12:31:27 +10:00
parent 35d84496ef
commit 1a503cf290
21 changed files with 226 additions and 48 deletions

View File

@ -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));

View File

@ -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
);

View File

@ -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',
];
}

View File

@ -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',

View File

@ -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.
*

View File

@ -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
*

View File

@ -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) {

View File

@ -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'];
}

View File

@ -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')];

View File

@ -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',
);

View File

@ -435,7 +435,8 @@
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
@ -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) => {

View File

@ -402,6 +402,8 @@
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
@ -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) => {

View File

@ -413,6 +413,8 @@
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
@ -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 @@
</script>
</div>
</div>
<script>
// 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', 'swiss-qr'
];
tables.forEach((tableIdentifier) => {
console.log(document.getElementById(tableIdentifier));
document.getElementById(tableIdentifier)?.childElementCount === 0
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
: '';
});
});
</script>

View File

@ -384,6 +384,8 @@
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
@ -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) => {

View File

@ -385,6 +385,8 @@
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
@ -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) => {

View File

@ -391,6 +391,8 @@
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
@ -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) => {

View File

@ -428,6 +428,8 @@
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
@ -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) => {

View File

@ -405,7 +405,8 @@
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
</div>
@ -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) => {

View File

@ -352,6 +352,8 @@
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
@ -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) => {

View File

@ -458,6 +458,8 @@
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
@ -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) => {

View File

@ -424,6 +424,8 @@
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
<table id="statement-credit-table" cellspacing="0" data-ref="table"></table>
<div id="statement-credit-table-totals" data-ref="statement-totals"></div>
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
<div id="table-totals" cellspacing="0">$status_logo</div>
@ -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) => {