Support All Time option for statements and support excluding clients with no invoices matching the selected filters

This commit is contained in:
Joshua Dwire 2023-06-13 15:23:54 -04:00
parent 1fc7e48445
commit 2d54c4fdb3
8 changed files with 82 additions and 33 deletions

View File

@ -41,6 +41,7 @@ class EmailStatement
public const LAST_QUARTER = "last_quarter"; public const LAST_QUARTER = "last_quarter";
public const THIS_YEAR = "this_year"; public const THIS_YEAR = "this_year";
public const LAST_YEAR = "last_year"; public const LAST_YEAR = "last_year";
public const ALL_TIME = "all_time";
public const CUSTOM_RANGE = "custom"; public const CUSTOM_RANGE = "custom";

View File

@ -40,7 +40,7 @@ class StoreSchedulerRequest extends Request
'template' => 'bail|required|string', 'template' => 'bail|required|string',
'parameters' => 'bail|array', 'parameters' => 'bail|array',
'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()], 'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()],
'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,custom', 'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom',
'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'], 'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'],
'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'], 'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'],
'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'], 'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'],

View File

@ -37,7 +37,7 @@ class UpdateSchedulerRequest extends Request
'template' => 'bail|required|string', 'template' => 'bail|required|string',
'parameters' => 'bail|array', 'parameters' => 'bail|array',
'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()], 'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()],
'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,custom', 'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom',
'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'], 'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'],
'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'], 'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'],
'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'], 'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'],

View File

@ -18,6 +18,7 @@ use App\Services\Email\Email;
use App\Services\Email\EmailObject; use App\Services\Email\EmailObject;
use App\Utils\Number; use App\Utils\Number;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
use Carbon\Carbon;
use Illuminate\Mail\Mailables\Address; use Illuminate\Mail\Mailables\Address;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -149,7 +150,22 @@ class ClientService
$pdf = $statement->run(); $pdf = $statement->run();
if ($send_email) { if ($send_email) {
return $this->emailStatement($pdf, $statement->options); // If selected, ignore clients that don't have any invoices to put on the statement.
if (!empty($options['only_clients_with_invoices'] && $statement->getInvoices()->count() == 0)) {
return false;
}
$options = $statement->options;
if (empty($options['start_date'])) {
$options['start_date'] = $statement->getInvoices()->first()->date;
}
if (empty($options['end_date'])) {
$options['end_date'] = Carbon::now();
}
return $this->emailStatement($pdf, $options);
} }
return $pdf; return $pdf;

View File

@ -211,6 +211,9 @@ class Statement
$this->options['show_credits_table'] = false; $this->options['show_credits_table'] = false;
} }
if (!\array_key_exists('only_clients_with_invoices', $this->options)) {
$this->options['only_clients_with_invoices'] = false;
}
return $this; return $this;
} }
@ -220,18 +223,25 @@ class Statement
* *
* @return Invoice[]|\Illuminate\Support\LazyCollection * @return Invoice[]|\Illuminate\Support\LazyCollection
*/ */
protected function getInvoices(): \Illuminate\Support\LazyCollection public function getInvoices(): \Illuminate\Support\LazyCollection
{ {
return Invoice::withTrashed() $query = Invoice::withTrashed()
->with('payments.type') ->with('payments.type')
->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', $this->invoiceStatuses()) ->whereIn('status_id', $this->invoiceStatuses())
->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])])
->orderBy('due_date', 'ASC') ->orderBy('due_date', 'ASC')
->orderBy('date', 'ASC') ->orderBy('date', 'ASC');
->cursor();
if (!empty($this->options['start_date'])) {
$query->whereDate('date', '>=', $this->options['start_date']);
}
if (!empty($this->options['end_date'])) {
$query->whereDate('date', '<=', $this->options['end_date']);
}
return $query->cursor();
} }
private function invoiceStatuses() :array private function invoiceStatuses() :array
@ -266,15 +276,22 @@ class Statement
*/ */
protected function getPayments(): \Illuminate\Support\LazyCollection protected function getPayments(): \Illuminate\Support\LazyCollection
{ {
return Payment::withTrashed() $query = Payment::withTrashed()
->with('client.country', 'invoices') ->with('client.country', 'invoices')
->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', [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'])]) ->orderBy('date', 'ASC');
->orderBy('date', 'ASC')
->cursor(); if (!empty($this->options['start_date'])) {
$query->whereDate('date', '>=', $this->options['start_date']);
}
if (!empty($this->options['end_date'])) {
$query->whereDate('date', '<=', $this->options['end_date']);
}
return $query->cursor();
} }
/** /**
@ -284,19 +301,26 @@ class Statement
*/ */
protected function getCredits(): \Illuminate\Support\LazyCollection protected function getCredits(): \Illuminate\Support\LazyCollection
{ {
return Credit::withTrashed() $query = Credit::withTrashed()
->with('client.country', 'invoices') ->with('client.country', 'invoices')
->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', [Credit::STATUS_SENT, Credit::STATUS_PARTIAL, Credit::STATUS_APPLIED]) ->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'])]) ->orderBy('date', 'ASC');
->where(function ($query) {
$query->whereDate('due_date', '>=', $this->options['end_date']) if (!empty($this->options['start_date'])) {
->orWhereNull('due_date'); $query->whereDate('date', '>=', $this->options['start_date']);
}) }
->orderBy('date', 'ASC') if (!empty($this->options['end_date'])) {
->cursor(); $query->whereDate('date', '<=', $this->options['end_date'])
->where(function ($query) {
$query->whereDate('due_date', '>=', $this->options['end_date'])
->orWhereNull('due_date');
});
}
return $query->cursor();
} }
/** /**

View File

@ -392,22 +392,26 @@ class Design extends BaseDesign
if ($this->type === 'statement') { if ($this->type === 'statement') {
// $s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale()); // $s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale());
$s_date = $this->translateDate($this->options['start_date'], $this->client->date_format(), $this->client->locale()) . " - " . $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale());
return [ $headerParts = [
['element' => 'tr', 'properties' => ['data-ref' => 'statement-label'], 'elements' => [ ['element' => 'tr', 'properties' => ['data-ref' => 'statement-label'], 'elements' => [
['element' => 'th', 'properties' => [], 'content' => ""], ['element' => 'th', 'properties' => [], 'content' => ""],
['element' => 'th', 'properties' => [], 'content' => "<h2>".ctrans('texts.statement')."</h2>"], ['element' => 'th', 'properties' => [], 'content' => "<h2>" . ctrans('texts.statement') . "</h2>"],
]],
['element' => 'tr', 'properties' => [], 'elements' => [
['element' => 'th', 'properties' => [], 'content' => ctrans('texts.statement_date')],
['element' => 'th', 'properties' => [], 'content' => $s_date ?? ''],
]],
['element' => 'tr', 'properties' => [], 'elements' => [
['element' => 'th', 'properties' => [], 'content' => '$balance_due_label'],
['element' => 'th', 'properties' => [], 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->client)],
]], ]],
]; ];
if (!empty($this->options['start_date']) || !empty($this->options['end_date'])) {
$s_date = $this->translateDate($this->options['start_date'], $this->client->date_format(), $this->client->locale()) . " - " . $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale());
$headerParts[] =
['element' => 'tr', 'properties' => [], 'elements' => [
['element' => 'th', 'properties' => [], 'content' => ctrans('texts.statement_date')],
['element' => 'th', 'properties' => [], 'content' => $s_date ?? ''],
]];
}
$headerParts[] = ['element' => 'tr', 'properties' => [], 'elements' => [
['element' => 'th', 'properties' => [], 'content' => '$balance_due_label'],
['element' => 'th', 'properties' => [], 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->client)],
]];
return $headerParts;
} }
$variables = $this->context['pdf_variables']['invoice_details']; $variables = $this->context['pdf_variables']['invoice_details'];

View File

@ -71,6 +71,7 @@ class EmailStatementService
'show_payments_table' => $this->scheduler->parameters['show_payments_table'] ?? true, 'show_payments_table' => $this->scheduler->parameters['show_payments_table'] ?? true,
'show_aging_table' => $this->scheduler->parameters['show_aging_table'] ?? true, 'show_aging_table' => $this->scheduler->parameters['show_aging_table'] ?? true,
'show_credits_table' => $this->scheduler->parameters['show_credits_table'] ?? true, 'show_credits_table' => $this->scheduler->parameters['show_credits_table'] ?? true,
'only_clients_with_invoices' => $this->scheduler->parameters['only_clients_with_invoices'] ?? true,
'status' => $this->scheduler->parameters['status'] 'status' => $this->scheduler->parameters['status']
]; ];
} }
@ -92,6 +93,7 @@ class EmailStatementService
EmailStatement::LAST_QUARTER => [now()->startOfDay()->subQuarterNoOverflow()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuarterNoOverflow()->lastOfQuarter()->format('Y-m-d')], EmailStatement::LAST_QUARTER => [now()->startOfDay()->subQuarterNoOverflow()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuarterNoOverflow()->lastOfQuarter()->format('Y-m-d')],
EmailStatement::THIS_YEAR => [now()->startOfDay()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->lastOfYear()->format('Y-m-d')], EmailStatement::THIS_YEAR => [now()->startOfDay()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->lastOfYear()->format('Y-m-d')],
EmailStatement::LAST_YEAR => [now()->startOfDay()->subYearNoOverflow()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->subYearNoOverflow()->lastOfYear()->format('Y-m-d')], EmailStatement::LAST_YEAR => [now()->startOfDay()->subYearNoOverflow()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->subYearNoOverflow()->lastOfYear()->format('Y-m-d')],
EmailStatement::ALL_TIME => [null, null],
EmailStatement::CUSTOM_RANGE => [$this->scheduler->parameters['start_date'], $this->scheduler->parameters['end_date']], EmailStatement::CUSTOM_RANGE => [$this->scheduler->parameters['start_date'], $this->scheduler->parameters['end_date']],
default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')], default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
}; };

View File

@ -2000,6 +2000,7 @@ $LANG = array(
'current_quarter' => 'Current Quarter', 'current_quarter' => 'Current Quarter',
'last_quarter' => 'Last Quarter', 'last_quarter' => 'Last Quarter',
'last_year' => 'Last Year', 'last_year' => 'Last Year',
'all_time' => 'All Time',
'custom_range' => 'Custom Range', 'custom_range' => 'Custom Range',
'url' => 'URL', 'url' => 'URL',
'debug' => 'Debug', 'debug' => 'Debug',
@ -4907,6 +4908,7 @@ $LANG = array(
'all_clients' => 'All Clients', 'all_clients' => 'All Clients',
'show_aging_table' => 'Show Aging Table', 'show_aging_table' => 'Show Aging Table',
'show_payments_table' => 'Show Payments Table', 'show_payments_table' => 'Show Payments Table',
'only_clients_with_invoices' => 'Only Clients with Invoices',
'email_statement' => 'Email Statement', 'email_statement' => 'Email Statement',
'once' => 'Once', 'once' => 'Once',
'schedules' => 'Schedules', 'schedules' => 'Schedules',