Support setting partial/deposit payment due date

This commit is contained in:
Hillel Coren 2017-10-26 10:56:59 +03:00
parent 86e8c09701
commit 575ff0c2e1
18 changed files with 115 additions and 42 deletions

View File

@ -83,6 +83,7 @@ class ClientPortalController extends BaseController
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->partial_due_date = Utils::fromSqlDate($invoice->due_date);
$invoice->features = [
'customize_invoice_design' => $account->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => $account->hasFeature(FEATURE_REMOVE_CREATED_BY),

View File

@ -105,6 +105,7 @@ class InvoiceController extends BaseController
$invoice->invoice_type_id = $clone;
$invoice->invoice_number = $account->getNextNumber($invoice);
$invoice->due_date = null;
$invoice->partial_due_date = null;
$invoice->balance = $invoice->amount;
$invoice->invoice_status_id = 0;
$invoice->invoice_date = date_create()->format('Y-m-d');
@ -123,6 +124,8 @@ class InvoiceController extends BaseController
$invoice->start_date = Utils::fromSqlDate($invoice->start_date);
$invoice->end_date = Utils::fromSqlDate($invoice->end_date);
$invoice->last_sent_date = Utils::fromSqlDate($invoice->last_sent_date);
$invoice->partial_due_date = Utils::fromSqlDate($invoice->partial_due_date);
$invoice->features = [
'customize_invoice_design' => Auth::user()->hasFeature(FEATURE_CUSTOMIZE_INVOICE_DESIGN),
'remove_created_by' => Auth::user()->hasFeature(FEATURE_REMOVE_CREATED_BY),

View File

@ -216,6 +216,7 @@ class Invoice extends EntityModel implements BalanceAffecting
'public_notes',
'invoice_footer',
'partial',
'partial_due_date',
] as $field) {
if ($this->$field != $this->getOriginal($field)) {
return true;
@ -634,6 +635,15 @@ class Invoice extends EntityModel implements BalanceAffecting
if ($this->partial > 0) {
$this->partial = $partial;
// clear the partial due date and set the due date
// using payment terms if it's blank
if (! $this->partial && $this->partial_due_date) {
$this->partial_due_date = null;
if (! $this->due_date) {
$this->due_date = $this->account->defaultDueDate($this->client);
}
}
}
$this->save();
@ -719,7 +729,8 @@ class Invoice extends EntityModel implements BalanceAffecting
public function statusClass()
{
return static::calcStatusClass($this->invoice_status_id, $this->balance, $this->getOriginal('due_date'), $this->is_recurring);
$dueDate = $this->getOriginal('partial_due_date') ?: $this->getOriginal('due_date');
return static::calcStatusClass($this->invoice_status_id, $this->balance, $dueDate, $this->is_recurring);
}
public function statusLabel()
@ -808,7 +819,7 @@ class Invoice extends EntityModel implements BalanceAffecting
*/
public function isOverdue()
{
return static::calcIsOverdue($this->balance, $this->due_date);
return static::calcIsOverdue($this->balance, $this->partial_due_date ?: $this->due_date);
}
/**
@ -878,6 +889,7 @@ class Invoice extends EntityModel implements BalanceAffecting
'custom_taxes1',
'custom_taxes2',
'partial',
'partial_due_date',
'has_tasks',
'custom_text_value1',
'custom_text_value2',

View File

@ -155,10 +155,16 @@ trait SendsEmails
{
for ($i = 1; $i <= 3; $i++) {
if ($date = $this->getReminderDate($i, $filterEnabled)) {
$field = $this->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE ? 'due_date' : 'invoice_date';
if ($invoice->$field == $date) {
if ($this->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE) {
if (($invoice->partial && $invoice->partial_due_date == $date)
|| $invoice->due_date == $date) {
return "reminder{$i}";
}
} else {
if ($invoice->invoice_date == $date) {
return "reminder{$i}";
}
}
}
}

View File

@ -66,7 +66,11 @@ class InvoiceDatatable extends EntityDatatable
[
$entityType == ENTITY_INVOICE ? 'due_date' : 'valid_until',
function ($model) {
return Utils::fromSqlDate($model->due_date_sql);
$str = '';
if ($model->partial_due_date) {
$str = Utils::fromSqlDate($model->partial_due_date) . ', ';
}
return $str . Utils::fromSqlDate($model->due_date_sql);
},
],
[
@ -165,7 +169,7 @@ class InvoiceDatatable extends EntityDatatable
private function getStatusLabel($model)
{
$class = Invoice::calcStatusClass($model->invoice_status_id, $model->balance, $model->due_date_sql, $model->is_recurring);
$class = Invoice::calcStatusClass($model->invoice_status_id, $model->balance, $model->partial_due_date ?: $model->due_date_sql, $model->is_recurring);
$label = Invoice::calcStatusLabel($model->invoice_status_name, $class, $this->entityType, $model->quote_invoice_id);
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";

View File

@ -67,11 +67,14 @@ class InvoicePresenter extends EntityPresenter
public function age()
{
if (! $this->entity->due_date || $this->entity->date_date == '0000-00-00') {
$invoice = $this->entity;
$dueDate = $invoice->partial_due_date ?: $invoice->due_date;
if (! $dueDate || $dueDate == '0000-00-00') {
return 0;
}
$date = Carbon::parse($this->entity->due_date);
$date = Carbon::parse($dueDate);
if ($date->isFuture()) {
return 0;
@ -155,6 +158,11 @@ class InvoicePresenter extends EntityPresenter
return Utils::fromSqlDate($this->entity->due_date);
}
public function partial_due_date()
{
return Utils::fromSqlDate($this->entity->partial_due_date);
}
public function frequency()
{
$frequency = $this->entity->frequency ? $this->entity->frequency->name : '';

View File

@ -41,7 +41,7 @@ class AgingReport extends AbstractReport
$this->isExport ? $client->getDisplayName() : $client->present()->link,
$this->isExport ? $invoice->invoice_number : $invoice->present()->link,
$invoice->present()->invoice_date,
$invoice->present()->due_date,
$invoice->present()->partial_due_date ?: $invoice->present()->due_date,
$invoice->present()->age,
$account->formatMoney($invoice->amount, $client),
$account->formatMoney($invoice->balance, $client),

View File

@ -309,13 +309,13 @@ class DashboardRepository
->where('invoices.deleted_at', '=', null)
->where('invoices.is_public', '=', true)
->where('contacts.is_primary', '=', true)
->where('invoices.due_date', '<', date('Y-m-d'));
->where(DB::raw("coalesce(invoices.partial_due_date, invoices.due_date)"), '<', date('Y-m-d'));
if (! $viewAll) {
$pastDue = $pastDue->where('invoices.user_id', '=', $userId);
}
return $pastDue->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'invoice_type_id'])
return $pastDue->select([DB::raw("coalesce(invoices.partial_due_date, invoices.due_date) due_date"), 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'invoice_type_id'])
->orderBy('invoices.due_date', 'asc')
->take(50)
->get();
@ -337,8 +337,7 @@ class DashboardRepository
->where('invoices.is_public', '=', true)
->where('contacts.is_primary', '=', true)
->where(function($query) {
$query->where('invoices.due_date', '>=', date('Y-m-d'))
->orWhereNull('invoices.due_date');
$query->where(DB::raw("coalesce(invoices.partial_due_date, invoices.due_date)"), '>=', date('Y-m-d'));
})
->orderBy('invoices.due_date', 'asc');
@ -347,7 +346,7 @@ class DashboardRepository
}
return $upcoming->take(50)
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'invoice_type_id'])
->select([DB::raw("coalesce(invoices.partial_due_date, invoices.due_date) due_date"), 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'clients.user_id as client_user_id', 'invoice_type_id'])
->get();
}

View File

@ -74,9 +74,10 @@ class InvoiceRepository extends BaseRepository
'invoices.balance',
'invoices.invoice_date',
'invoices.due_date as due_date_sql',
'invoices.partial_due_date',
DB::raw("CONCAT(invoices.invoice_date, invoices.created_at) as date"),
DB::raw("CONCAT(invoices.due_date, invoices.created_at) as due_date"),
DB::raw("CONCAT(invoices.due_date, invoices.created_at) as valid_until"),
DB::raw("CONCAT(COALESCE(invoices.partial_due_date, invoices.due_date), invoices.created_at) as due_date"),
DB::raw("CONCAT(COALESCE(invoices.partial_due_date, invoices.due_date), invoices.created_at) as valid_until"),
'invoice_statuses.name as status',
'invoice_statuses.name as invoice_status_name',
'contacts.first_name',
@ -389,7 +390,7 @@ class InvoiceRepository extends BaseRepository
$invoice->custom_taxes2 = $account->custom_invoice_taxes2 ?: false;
// set the default due date
if ($entityType == ENTITY_INVOICE) {
if ($entityType == ENTITY_INVOICE && empty($data['partial_due_date'])) {
$client = Client::scope()->whereId($data['client_id'])->first();
$invoice->due_date = $account->defaultDueDate($client);
}
@ -447,6 +448,7 @@ class InvoiceRepository extends BaseRepository
if (isset($data['is_amount_discount'])) {
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
}
if (isset($data['invoice_date_sql'])) {
$invoice->invoice_date = $data['invoice_date_sql'];
} elseif (isset($data['invoice_date'])) {
@ -626,6 +628,14 @@ class InvoiceRepository extends BaseRepository
$invoice->partial = max(0, min(round(Utils::parseFloat($data['partial']), 2), $invoice->balance));
}
if ($invoice->partial) {
if (isset($data['partial_due_date'])) {
$invoice->partial_due_date = Utils::toSqlDate($data['partial_due_date']);
}
} else {
$invoice->partial_due_date = null;
}
$invoice->amount = $total;
$invoice->save();
@ -1139,8 +1149,11 @@ class InvoiceRepository extends BaseRepository
for ($i = 1; $i <= 3; $i++) {
if ($date = $account->getReminderDate($i, $filterEnabled)) {
$field = $account->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE ? 'due_date' : 'invoice_date';
$dates[] = "$field = '$date'";
if ($account->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE) {
$dates[] = "(due_date = '$date' OR partial_due_date = '$date')";
} else {
$dates[] = "invoice_date = '$date'";
}
}
}

View File

@ -119,6 +119,7 @@ class InvoiceTransformer extends EntityTransformer
'is_amount_discount' => (bool) $invoice->is_amount_discount,
'invoice_footer' => $invoice->invoice_footer,
'partial' => (float) $invoice->partial,
'partial_due_date' => $invoice->partial_due_date,
'has_tasks' => (bool) $invoice->has_tasks,
'auto_bill' => (bool) $invoice->auto_bill,
'custom_value1' => (float) $invoice->custom_value1,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -945,7 +945,13 @@ NINJA.renderField = function(invoice, field, twoColumn) {
value = invoice.invoice_date;
} else if (field == 'invoice.due_date') {
label = invoice.is_quote ? invoiceLabels.valid_until : invoiceLabels.due_date;
value = invoice.is_recurring ? false : invoice.due_date;
if (invoice.is_recurring) {
value = false;
} else if (invoice.partial_due_date) {
value = invoice.partial_due_date;
} else {
value = invoice.due_date;
}
} else if (field == 'invoice.custom_text_value1') {
if (invoice.custom_text_value1 && account.custom_invoice_text_label1) {
label = invoice.account.custom_invoice_text_label1;
@ -959,7 +965,7 @@ NINJA.renderField = function(invoice, field, twoColumn) {
} else if (field == 'invoice.balance_due') {
label = invoice.is_quote || invoice.balance_amount < 0 ? invoiceLabels.total : invoiceLabels.balance_due;
value = formatMoneyInvoice(invoice.total_amount, invoice);
} else if (field == invoice.partial_due) {
} else if (field == 'invoice.partial_due') {
if (NINJA.parseFloat(invoice.partial)) {
label = invoiceLabels.partial_due;
value = formatMoneyInvoice(invoice.balance_amount, invoice);

View File

@ -2506,6 +2506,7 @@ $LANG = array(
'video' => 'Video',
'return_to_invoice' => 'Return to Invoice',
'gateway_help_13' => 'To use ITN leave the PDT Key field blank.',
'partial_due_date' => 'Partial Due Date',
);

View File

@ -11,6 +11,10 @@
<td>{{ trans('texts.status') }}</td>
<td>{{ trans(isset($entityType) && $entityType == ENTITY_QUOTE ? 'texts.quote_date' : 'texts.invoice_date') }}</td>
<td>{{ trans('texts.due_date') }}</td>
@if (empty($entityType))
<td>{{ trans('texts.partial') }}</td>
<td>{{ trans('texts.partial_due_date') }}</td>
@endif
<td>{{ trans('texts.public_notes') }}</td>
<td>{{ trans('texts.private_notes') }}</td>
@if ($account->custom_invoice_label1)
@ -61,6 +65,10 @@
<td>{{ $invoice->present()->status }}</td>
<td>{{ $invoice->present()->invoice_date }}</td>
<td>{{ $invoice->present()->due_date }}</td>
@if (empty($entityType))
<td>{{ $invoice->present()->partial }}</td>
<td>{{ $invoice->present()->partial_due_date }}</td>
@endif
<td>{{ $invoice->public_notes }}</td>
<td>{{ $invoice->private_notes }}</td>
@if ($account->custom_invoice_label1)

View File

@ -177,7 +177,7 @@
<div class="form-group partial">
<label for="partial" class="control-label col-lg-4 col-sm-4">{{ trans('texts.partial') }}</label>
<div class="col-lg-8 col-sm-8 no-gutter">
<div data-bind="css: {'col-md-4': isPartialSet(), 'col-md-12': ! isPartialSet()}">
<div data-bind="css: {'col-md-4': showPartialDueDate(), 'col-md-12': ! showPartialDueDate()}" class="partial">
{!! Former::text('partial')->data_bind("value: partial, valueUpdate: 'afterkeydown'")
->onkeyup('onPartialChange()')
->raw() !!}
@ -186,7 +186,7 @@
{!! Former::text('partial_due_date')
->placeholder('due_date')
->style('display: none')
->data_bind("datePicker: partial_due_date, valueUpdate: 'afterkeydown', visible: isPartialSet")
->data_bind("datePicker: partial_due_date, valueUpdate: 'afterkeydown', visible: showPartialDueDate")
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))
->raw() !!}
</div>
@ -1609,7 +1609,7 @@
}
$('.partial')
.addClass('has-error')
.find('div')
.find('div.partial')
.append('<span class="help-block">{{ trans('texts.partial_value') }}</span>');
} else {
$('.partial')

View File

@ -624,6 +624,13 @@ function InvoiceModel(data) {
self.isPartialSet = ko.computed(function() {
return self.partial() && self.partial() <= model.invoice().totals.rawTotal()
});
self.showPartialDueDate = ko.computed(function() {
if (self.is_quote()) {
return false;
}
return self.isPartialSet();
});
}
function ClientModel(data) {