Add option in client statements for filtering by invoice status

This commit is contained in:
David Bomba 2022-01-03 18:22:10 +11:00
parent f4cc60b5d2
commit b6f382bae2
17 changed files with 10526 additions and 50 deletions

View File

@ -39,7 +39,7 @@ class StatementController extends Controller
public function raw(ShowStatementRequest $request) public function raw(ShowStatementRequest $request)
{ {
$pdf = $request->client()->service()->statement( $pdf = $request->client()->service()->statement(
$request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table']) $request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table', 'status'])
); );
if ($pdf && $request->query('download')) { if ($pdf && $request->query('download')) {

View File

@ -109,15 +109,15 @@ class ClientStatementController extends BaseController
*/ */
public function statement(CreateStatementRequest $request) public function statement(CreateStatementRequest $request)
{ { nlog($request->all());
$pdf = $request->client()->service()->statement( $pdf = $request->client()->service()->statement(
$request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table']) $request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table', 'status'])
); );
if ($pdf) { if ($pdf) {
return response()->streamDownload(function () use ($pdf) { return response()->streamDownload(function () use ($pdf) {
echo $pdf; echo $pdf;
}, 'statement.pdf', ['Content-Type' => 'application/pdf']); }, ctrans('texts.statement') . '.pdf', ['Content-Type' => 'application/pdf']);
} }
return response()->json(['message' => 'Something went wrong. Please check logs.']); return response()->json(['message' => 'Something went wrong. Please check logs.']);

View File

@ -44,7 +44,6 @@ class ShowStatementRequest extends FormRequest
public function client(): Client public function client(): Client
{ {
// return Client::withTrashed()->without('contacts.company', 'documents', 'gateway_tokens')->where('id', auth('contact')->user()->client_id)->first();
return auth('contact')->user()->client; return auth('contact')->user()->client;
} }
} }

View File

@ -32,6 +32,7 @@ class CreateStatementRequest extends Request
'client_id' => 'bail|required|exists:clients,id,company_id,' . auth()->user()->company()->id, 'client_id' => 'bail|required|exists:clients,id,company_id,' . auth()->user()->company()->id,
'show_payments_table' => 'boolean', 'show_payments_table' => 'boolean',
'show_aging_table' => 'boolean', 'show_aging_table' => 'boolean',
'status' => 'string',
]; ];
} }

View File

@ -227,12 +227,36 @@ class Statement
->where('is_deleted', false) ->where('is_deleted', false)
->where('company_id', $this->client->company_id) ->where('company_id', $this->client->company_id)
->where('client_id', $this->client->id) ->where('client_id', $this->client->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]) ->whereIn('status_id', $this->invoiceStatuses())
->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])]) ->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])])
->orderBy('number', 'ASC') ->orderBy('date', 'ASC')
->cursor(); ->cursor();
} }
private function invoiceStatuses() :array
{
$status = 'all';
nlog($this->options);
if(array_key_exists('status', $this->options))
$status = $this->options['status'];
switch ($status) {
case 'all':
return [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID];
break;
case 'paid':
return [Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID];
break;
case 'unpaid':
return [Invoice::STATUS_SENT];
break;
default:
return [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID];
break;
}
}
/** /**
* The collection of payments for the statement. * The collection of payments for the statement.
* *
@ -247,7 +271,7 @@ class Statement
->where('client_id', $this->client->id) ->where('client_id', $this->client->id)
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]) ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])]) ->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])])
->orderBy('number', 'ASC') ->orderBy('date', 'ASC')
->cursor(); ->cursor();
} }

View File

@ -144,7 +144,7 @@ class InvoiceService
$this->invoice->balance += $balance_adjustment; $this->invoice->balance += $balance_adjustment;
if ($this->invoice->balance == 0 && !$is_draft) { if (round($this->invoice->balance,2) == 0 && !$is_draft) {
$this->invoice->status_id = Invoice::STATUS_PAID; $this->invoice->status_id = Invoice::STATUS_PAID;
} }
@ -285,7 +285,7 @@ class InvoiceService
public function setCalculatedStatus() public function setCalculatedStatus()
{ {
if ((int)$this->invoice->balance == 0) { if (round($this->invoice->balance,2) == 0) {
$this->setStatus(Invoice::STATUS_PAID); $this->setStatus(Invoice::STATUS_PAID);
} elseif ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) { } elseif ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) {
$this->setStatus(Invoice::STATUS_PARTIAL); $this->setStatus(Invoice::STATUS_PARTIAL);
@ -299,7 +299,7 @@ class InvoiceService
if($this->invoice->status_id == Invoice::STATUS_DRAFT) if($this->invoice->status_id == Invoice::STATUS_DRAFT)
return $this; return $this;
if($this->invoice->balance == 0){ if(round($this->invoice->balance,2) == 0){
$this->invoice->status_id = Invoice::STATUS_PAID; $this->invoice->status_id = Invoice::STATUS_PAID;
} }
elseif ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) { elseif ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) {

View File

@ -77,7 +77,7 @@ class AccountTransformer extends EntityTransformer
'report_errors' => (bool) $account->report_errors, 'report_errors' => (bool) $account->report_errors,
'debug_enabled' => (bool) config('ninja.debug_enabled'), 'debug_enabled' => (bool) config('ninja.debug_enabled'),
'is_docker' => (bool) config('ninja.is_docker'), 'is_docker' => (bool) config('ninja.is_docker'),
'is_scheduler_running' => (bool) $account->is_scheduler_running, 'is_scheduler_running' => Ninja::isHosted() ? (bool) true : (bool) $account->is_scheduler_running, //force true for hosted 03/01/2022
'default_company_id' => (string) $this->encodePrimaryKey($account->default_company_id), 'default_company_id' => (string) $this->encodePrimaryKey($account->default_company_id),
'disable_auto_update' => (bool) config('ninja.disable_auto_update'), 'disable_auto_update' => (bool) config('ninja.disable_auto_update'),
'emails_sent' => (int) $account->emailsSent(), 'emails_sent' => (int) $account->emailsSent(),

View File

@ -54,7 +54,21 @@ return [
| |
*/ */
'asset_url' => env('ASSETS_URL', config('app.url')), 'asset_url' => null,
/*
|--------------------------------------------------------------------------
| Livewire App URL
|--------------------------------------------------------------------------
|
| This value should be used if livewire assets are served from CDN.
| Livewire will communicate with an app through this url.
|
| Examples: "https://my-app.com", "myurl.com/app".
|
*/
'app_url' => null,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -101,7 +115,7 @@ return [
| |
| This value sets the path to the Livewire manifest file. | This value sets the path to the Livewire manifest file.
| The default should work for most cases (which is | The default should work for most cases (which is
| "<app_root>/bootstrap/cache/livewire-components.php)", but for specific | "<app_root>/bootstrap/cache/livewire-components.php"), but for specific
| cases like when hosting on Laravel Vapor, it could be set to a different value. | cases like when hosting on Laravel Vapor, it could be set to a different value.
| |
| Example: for Laravel Vapor, it would be "/tmp/storage/bootstrap/cache/livewire-components.php". | Example: for Laravel Vapor, it would be "/tmp/storage/bootstrap/cache/livewire-components.php".
@ -110,4 +124,35 @@ return [
'manifest_path' => null, 'manifest_path' => null,
/*
|--------------------------------------------------------------------------
| Back Button Cache
|--------------------------------------------------------------------------
|
| This value determines whether the back button cache will be used on pages
| that contain Livewire. By disabling back button cache, it ensures that
| the back button shows the correct state of components, instead of
| potentially stale, cached data.
|
| Setting it to "false" (default) will disable back button cache.
|
*/
'back_button_cache' => false,
/*
|--------------------------------------------------------------------------
| Render On Redirect
|--------------------------------------------------------------------------
|
| This value determines whether Livewire will render before it's redirected
| or not. Setting it to "false" (default) will mean the render method is
| skipped when redirecting. And "true" will mean the render method is
| run before redirecting. Browsers bfcache can store a potentially
| stale view if render is skipped on redirect.
|
*/
'render_on_redirect' => false,
]; ];

10431
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,2 @@
/*! For license information please see view.js.LICENSE.txt */ /*! For license information please see view.js.LICENSE.txt */
(()=>{function e(e,t){for(var a=0;a<t.length;a++){var n=t[a];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}(new(function(){function t(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),this.url=new URL(document.querySelector("meta[name=pdf-url]").content),this.startDate="",this.endDate="",this.showPaymentsTable=!1,this.showAgingTable=!1}var a,n,r;return a=t,(n=[{key:"bindEventListeners",value:function(){var e=this;["#date-from","#date-to","#show-payments-table","#show-aging-table"].forEach((function(t){document.querySelector(t).addEventListener("change",(function(t){return e.handleValueChange(t)}))}))}},{key:"handleValueChange",value:function(e){"checkbox"===e.target.type?this[e.target.dataset.field]=e.target.checked:this[e.target.dataset.field]=e.target.value,this.updatePdf()}},{key:"composedUrl",get:function(){return this.url.search="",this.startDate.length>0&&this.url.searchParams.append("start_date",this.startDate),this.endDate.length>0&&this.url.searchParams.append("end_date",this.endDate),this.url.searchParams.append("show_payments_table",+this.showPaymentsTable),this.url.searchParams.append("show_aging_table",+this.showAgingTable),this.url.href}},{key:"updatePdf",value:function(){document.querySelector("meta[name=pdf-url]").content=this.composedUrl;var e=document.querySelector("#pdf-iframe");e&&(e.src=this.composedUrl),document.querySelector("meta[name=pdf-url]").dispatchEvent(new Event("change"))}},{key:"handle",value:function(){var e=this;this.bindEventListeners(),document.querySelector("#pdf-download").addEventListener("click",(function(){var t=new URL(e.composedUrl);t.searchParams.append("download",1),window.location.href=t.href}))}}])&&e(a.prototype,n),r&&e(a,r),t}())).handle()})(); (()=>{function e(e,t){for(var a=0;a<t.length;a++){var n=t[a];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}(new(function(){function t(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),this.url=new URL(document.querySelector("meta[name=pdf-url]").content),this.startDate="",this.endDate="",this.showPaymentsTable=!1,this.showAgingTable=!1,this.status=""}var a,n,r;return a=t,(n=[{key:"bindEventListeners",value:function(){var e=this;["#date-from","#date-to","#show-payments-table","#show-aging-table","#status"].forEach((function(t){document.querySelector(t).addEventListener("change",(function(t){return e.handleValueChange(t)}))}))}},{key:"handleValueChange",value:function(e){"checkbox"===e.target.type?this[e.target.dataset.field]=e.target.checked:this[e.target.dataset.field]=e.target.value,this.updatePdf()}},{key:"composedUrl",get:function(){return this.url.search="",this.startDate.length>0&&this.url.searchParams.append("start_date",this.startDate),this.endDate.length>0&&this.url.searchParams.append("end_date",this.endDate),this.url.searchParams.append("status",document.getElementById("status").value),this.url.searchParams.append("show_payments_table",+this.showPaymentsTable),this.url.searchParams.append("show_aging_table",+this.showAgingTable),this.url.href}},{key:"updatePdf",value:function(){document.querySelector("meta[name=pdf-url]").content=this.composedUrl;var e=document.querySelector("#pdf-iframe");e&&(e.src=this.composedUrl),document.querySelector("meta[name=pdf-url]").dispatchEvent(new Event("change"))}},{key:"handle",value:function(){var e=this;this.bindEventListeners(),document.querySelector("#pdf-download").addEventListener("click",(function(){var t=new URL(e.composedUrl);t.searchParams.append("download",1),window.location.href=t.href}))}}])&&e(a.prototype,n),r&&e(a,r),t}())).handle()})();

View File

@ -25,7 +25,7 @@
"/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=ff17e039dd15d505448f", "/js/clients/payments/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=ff17e039dd15d505448f",
"/js/clients/payment_methods/braintree-ach.js": "/js/clients/payment_methods/braintree-ach.js?id=656ad159838b726969f5", "/js/clients/payment_methods/braintree-ach.js": "/js/clients/payment_methods/braintree-ach.js?id=656ad159838b726969f5",
"/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=8f05ce6bd2d6cae7e5f2", "/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=8f05ce6bd2d6cae7e5f2",
"/js/clients/statements/view.js": "/js/clients/statements/view.js?id=ea3db0c04b4372f76735", "/js/clients/statements/view.js": "/js/clients/statements/view.js?id=4ed4c8a09803ddd0a9a7",
"/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js?id=c36ab5621413ef1de7c8", "/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js?id=c36ab5621413ef1de7c8",
"/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js?id=cbd7bb4c483ca75333f4", "/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js?id=cbd7bb4c483ca75333f4",
"/js/clients/payment_methods/authorize-checkout-card.js": "/js/clients/payment_methods/authorize-checkout-card.js?id=61becda97682c7909f29", "/js/clients/payment_methods/authorize-checkout-card.js": "/js/clients/payment_methods/authorize-checkout-card.js?id=61becda97682c7909f29",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"/livewire.js":"/livewire.js?id=ece4c4ab4b746f6f1739"} {"/livewire.js":"/livewire.js?id=f092ba91a90e56843ffc"}

View File

@ -17,6 +17,7 @@ class Statement {
this.endDate = ''; this.endDate = '';
this.showPaymentsTable = false; this.showPaymentsTable = false;
this.showAgingTable = false; this.showAgingTable = false;
this.status = '';
} }
bindEventListeners() { bindEventListeners() {
@ -25,6 +26,7 @@ class Statement {
'#date-to', '#date-to',
'#show-payments-table', '#show-payments-table',
'#show-aging-table', '#show-aging-table',
'#status',
].forEach((selector) => { ].forEach((selector) => {
document document
.querySelector(selector) .querySelector(selector)
@ -55,6 +57,8 @@ class Statement {
this.url.searchParams.append('end_date', this.endDate); this.url.searchParams.append('end_date', this.endDate);
} }
this.url.searchParams.append('status', document.getElementById("status").value);
this.url.searchParams.append( this.url.searchParams.append(
'show_payments_table', 'show_payments_table',
+this.showPaymentsTable +this.showPaymentsTable

View File

@ -21,6 +21,15 @@
</label> </label>
</div> <!-- End date range --> </div> <!-- End date range -->
<label for="show_status" class="block w-4/5 flex items-center mr-4">
<span class="mr-2">{{ ctrans('texts.status') }}:</span>
<select id="status" name="status" class="input w-full form-select">
<option value="all">{{ ctrans('texts.all')}} </option>
<option value="paid">{{ ctrans('texts.paid')}} </option>
<option value="unpaid">{{ ctrans('texts.unpaid')}} </option>
</select>
</label>
<label for="show_payments" class="block flex items-center mr-4 mt-4 md:mt-0"> <label for="show_payments" class="block flex items-center mr-4 mt-4 md:mt-0">
<input id="show-payments-table" type="checkbox" data-field="showPaymentsTable" class="form-checkbox" autocomplete="off"> <input id="show-payments-table" type="checkbox" data-field="showPaymentsTable" class="form-checkbox" autocomplete="off">
<span class="ml-2">{{ ctrans('texts.show_payments') }}</span> <span class="ml-2">{{ ctrans('texts.show_payments') }}</span>
@ -30,6 +39,7 @@
<input id="show-aging-table" type="checkbox" data-field="showAgingTable" class="form-checkbox" autocomplete="off"> <input id="show-aging-table" type="checkbox" data-field="showAgingTable" class="form-checkbox" autocomplete="off">
<span class="ml-2">{{ ctrans('texts.show_aging') }}</span> <span class="ml-2">{{ ctrans('texts.show_aging') }}</span>
</label> <!-- End show aging checkbox --> </label> <!-- End show aging checkbox -->
</div> </div>
<button onclick="setTimeout(() => this.disabled = true, 0); setTimeout(() => this.disabled = false, 5000); return true;" id="pdf-download" class="button button-primary bg-primary mt-4 md:mt-0">{{ ctrans('texts.download') }}</button> <button onclick="setTimeout(() => this.disabled = true, 0); setTimeout(() => this.disabled = false, 5000); return true;" id="pdf-download" class="button button-primary bg-primary mt-4 md:mt-0">{{ ctrans('texts.download') }}</button>
</div> </div>

View File

@ -8,16 +8,28 @@
<span class="page-link">@lang('pagination.previous')</span> <span class="page-link">@lang('pagination.previous')</span>
</li> </li>
@else @else
<li class="page-item"> @if(method_exists($paginator,'getCursorName'))
<button type="button" class="page-link" wire:click="previousPage" wire:loading.attr="disabled" rel="prev">@lang('pagination.previous')</button> <li class="page-item">
</li> <button dusk="previousPage{{ $paginator->getCursorName() == 'page' ? '' : '.' . $paginator->getPageName() }}" type="button" class="page-link" wire:click="setPage('{{$paginator->previousCursor()->encode()}}','{{ $paginator->getCursorName() }}')" wire:loading.attr="disabled" rel="prev">@lang('pagination.previous')</button>
</li>
@else
<li class="page-item">
<button type="button" dusk="previousPage{{ $paginator->getPageName() == 'page' ? '' : '.' . $paginator->getPageName() }}" class="page-link" wire:click="previousPage('{{ $paginator->getPageName() }}')" wire:loading.attr="disabled" rel="prev">@lang('pagination.previous')</button>
</li>
@endif
@endif @endif
{{-- Next Page Link --}} {{-- Next Page Link --}}
@if ($paginator->hasMorePages()) @if ($paginator->hasMorePages())
<li class="page-item"> @if(method_exists($paginator,'getCursorName'))
<button type="button" class="page-link" wire:click="nextPage" wire:loading.attr="disabled" rel="next">@lang('pagination.next')</button> <li class="page-item">
</li> <button dusk="nextPage{{ $paginator->getCursorName() == 'page' ? '' : '.' . $paginator->getPageName() }}" type="button" class="page-link" wire:click="setPage('{{$paginator->nextCursor()->encode()}}','{{ $paginator->getCursorName() }}')" wire:loading.attr="disabled" rel="next">@lang('pagination.next')</button>
</li>
@else
<li class="page-item">
<button type="button" dusk="nextPage{{ $paginator->getPageName() == 'page' ? '' : '.' . $paginator->getPageName() }}" class="page-link" wire:click="nextPage('{{ $paginator->getPageName() }}')" wire:loading.attr="disabled" rel="next">@lang('pagination.next')</button>
</li>
@endif
@else @else
<li class="page-item disabled" aria-disabled="true"> <li class="page-item disabled" aria-disabled="true">
<span class="page-link">@lang('pagination.next')</span> <span class="page-link">@lang('pagination.next')</span>