Automatic late fees #1505

This commit is contained in:
Hillel Coren 2017-07-18 21:15:51 +03:00
parent f5f161865a
commit 2bef0bdb66
10 changed files with 138 additions and 50 deletions

View File

@ -63,8 +63,30 @@ class SendReminders extends Command
config(['database.default' => $database]); config(['database.default' => $database]);
} }
$accounts = $this->accountRepo->findWithFees();
$this->info(count($accounts) . ' accounts found with fees');
foreach ($accounts as $account) {
if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
continue;
}
$invoices = $this->invoiceRepo->findNeedingReminding($account, false);
$this->info($account->name . ': ' . count($invoices) . ' invoices found');
foreach ($invoices as $invoice) {
if ($reminder = $account->getInvoiceReminder($invoice, false)) {
$this->info('Charge fee: ' . $invoice->id);
$number = preg_replace('/[^0-9]/', '', $reminder);
$amount = $account->account_email_settings->{"late_fee{$number}_amount"};
$percent = $account->account_email_settings->{"late_fee{$number}_percent"};
$this->invoiceRepo->setLateFee($invoice, $amount, $percent);
}
}
}
$accounts = $this->accountRepo->findWithReminders(); $accounts = $this->accountRepo->findWithReminders();
$this->info(count($accounts) . ' accounts found'); $this->info(count($accounts) . ' accounts found with reminders');
/** @var \App\Models\Account $account */ /** @var \App\Models\Account $account */
foreach ($accounts as $account) { foreach ($accounts as $account) {
@ -78,7 +100,7 @@ class SendReminders extends Command
/** @var Invoice $invoice */ /** @var Invoice $invoice */
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
if ($reminder = $account->getInvoiceReminder($invoice)) { if ($reminder = $account->getInvoiceReminder($invoice)) {
$this->info('Send to ' . $invoice->id); $this->info('Send email: ' . $invoice->id);
$this->mailer->sendInvoice($invoice, $reminder); $this->mailer->sendInvoice($invoice, $reminder);
} }
} }

View File

@ -46,6 +46,7 @@ if (! defined('APP_NAME')) {
define('INVOICE_ITEM_TYPE_TASK', 2); define('INVOICE_ITEM_TYPE_TASK', 2);
define('INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE', 3); define('INVOICE_ITEM_TYPE_PENDING_GATEWAY_FEE', 3);
define('INVOICE_ITEM_TYPE_PAID_GATEWAY_FEE', 4); define('INVOICE_ITEM_TYPE_PAID_GATEWAY_FEE', 4);
define('INVOICE_ITEM_TYPE_LATE_FEE', 5);
define('PERSON_CONTACT', 'contact'); define('PERSON_CONTACT', 'contact');
define('PERSON_USER', 'user'); define('PERSON_USER', 'user');

View File

@ -823,6 +823,10 @@ class AccountController extends BaseController
$account->{"num_days_{$type}"} = Input::get("num_days_{$type}"); $account->{"num_days_{$type}"} = Input::get("num_days_{$type}");
$account->{"field_{$type}"} = Input::get("field_{$type}"); $account->{"field_{$type}"} = Input::get("field_{$type}");
$account->{"direction_{$type}"} = Input::get("field_{$type}") == REMINDER_FIELD_INVOICE_DATE ? REMINDER_DIRECTION_AFTER : Input::get("direction_{$type}"); $account->{"direction_{$type}"} = Input::get("field_{$type}") == REMINDER_FIELD_INVOICE_DATE ? REMINDER_DIRECTION_AFTER : Input::get("direction_{$type}");
$number = preg_replace('/[^0-9]/', '', $type);
$account->account_email_settings->{"late_fee{$number}_amount"} = Input::get("late_fee{$number}_amount");
$account->account_email_settings->{"late_fee{$number}_percent"} = Input::get("late_fee{$number}_percent");
} }
$account->save(); $account->save();

View File

@ -27,6 +27,12 @@ class AccountEmailSettings extends Eloquent
'email_template_reminder1', 'email_template_reminder1',
'email_template_reminder2', 'email_template_reminder2',
'email_template_reminder3', 'email_template_reminder3',
'late_fee1_amount',
'late_fee1_percent',
'late_fee2_amount',
'late_fee2_percent',
'late_fee3_amount',
'late_fee3_percent',
]; ];
} }

View File

@ -125,9 +125,9 @@ trait SendsEmails
* *
* @return bool * @return bool
*/ */
public function getReminderDate($reminder) public function getReminderDate($reminder, $filterEnabled = true)
{ {
if (! $this->{"enable_reminder{$reminder}"}) { if ($filterEnabled && ! $this->{"enable_reminder{$reminder}"}) {
return false; return false;
} }
@ -142,10 +142,10 @@ trait SendsEmails
* *
* @return bool|string * @return bool|string
*/ */
public function getInvoiceReminder($invoice) public function getInvoiceReminder($invoice, $filterEnabled = true)
{ {
for ($i = 1; $i <= 3; $i++) { for ($i = 1; $i <= 3; $i++) {
if ($date = $this->getReminderDate($i)) { if ($date = $this->getReminderDate($i, $filterEnabled)) {
$field = $this->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE ? 'due_date' : 'invoice_date'; $field = $this->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE ? 'due_date' : 'invoice_date';
if ($invoice->$field == $date) { if ($invoice->$field == $date) {
return "reminder{$i}"; return "reminder{$i}";

View File

@ -656,6 +656,18 @@ class AccountRepository
return Account::whereRaw('enable_reminder1 = 1 OR enable_reminder2 = 1 OR enable_reminder3 = 1')->get(); return Account::whereRaw('enable_reminder1 = 1 OR enable_reminder2 = 1 OR enable_reminder3 = 1')->get();
} }
public function findWithFees()
{
return Account::whereHas('account_email_settings', function($query) {
$query->where('late_fee1_amount', '>', 0)
->orWhere('late_fee1_percent', '>', 0)
->orWhere('late_fee2_amount', '>', 0)
->orWhere('late_fee2_percent', '>', 0)
->orWhere('late_fee3_amount', '>', 0)
->orWhere('late_fee3_percent', '>', 0);
})->get();
}
public function createTokens($user, $name) public function createTokens($user, $name)
{ {
$name = trim($name) ?: 'TOKEN'; $name = trim($name) ?: 'TOKEN';

View File

@ -1091,19 +1091,24 @@ class InvoiceRepository extends BaseRepository
* *
* @return mixed * @return mixed
*/ */
public function findNeedingReminding(Account $account) public function findNeedingReminding(Account $account, $filterEnabled = true)
{ {
$dates = []; $dates = [];
for ($i = 1; $i <= 3; $i++) { for ($i = 1; $i <= 3; $i++) {
if ($date = $account->getReminderDate($i)) { if ($date = $account->getReminderDate($i, $filterEnabled)) {
$field = $account->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE ? 'due_date' : 'invoice_date'; $field = $account->{"field_reminder{$i}"} == REMINDER_FIELD_DUE_DATE ? 'due_date' : 'invoice_date';
$dates[] = "$field = '$date'"; $dates[] = "$field = '$date'";
} }
} }
if (! count($dates)) {
return [];
}
$sql = implode(' OR ', $dates); $sql = implode(' OR ', $dates);
$invoices = Invoice::invoiceType(INVOICE_TYPE_STANDARD) $invoices = Invoice::invoiceType(INVOICE_TYPE_STANDARD)
->with('invoice_items')
->whereAccountId($account->id) ->whereAccountId($account->id)
->where('balance', '>', 0) ->where('balance', '>', 0)
->where('is_recurring', '=', false) ->where('is_recurring', '=', false)
@ -1132,6 +1137,32 @@ class InvoiceRepository extends BaseRepository
} }
} }
public function setLateFee($invoice, $amount, $percent)
{
if ($amount <= 0 && $percent <= 0) {
return false;
}
$account = $invoice->account;
$data = $invoice->toArray();
$fee = $amount;
if ($invoice->amount > 0) {
$fee += round($invoice->amount * $percent / 100, 2);
}
$item = [];
$item['product_key'] = trans('texts.fee');
$item['notes'] = trans('texts.late_fee_added', ['date' => $account->formatDate('now')]);
$item['qty'] = 1;
$item['cost'] = $fee;
$item['invoice_item_type_id'] = INVOICE_ITEM_TYPE_LATE_FEE;
$data['invoice_items'][] = $item;
$this->save($data, $invoice);
}
public function setGatewayFee($invoice, $gatewayTypeId) public function setGatewayFee($invoice, $gatewayTypeId)
{ {
$account = $invoice->account; $account = $invoice->account;

View File

@ -2303,7 +2303,11 @@ $LANG = array(
'ofx_version' => 'OFX Version', 'ofx_version' => 'OFX Version',
'gateway_help_23' => ':link to get your Stripe API keys.', 'gateway_help_23' => ':link to get your Stripe API keys.',
'error_app_key_not_set' => 'Error: the APP_KEY value is not set in the .env file.', 'error_app_key_not_set' => 'Error: the APP_KEY value is not set in the .env file.',
'error_app_key_set_to_default' => 'Error: the APP_KEY value is set to the default in the .env file.' 'error_app_key_set_to_default' => 'Error: the APP_KEY value is set to the default in the .env file.',
'charge_late_fee' => 'Charge Late Fee',
'late_fee_amount' => 'Late Fee Amount',
'late_fee_percent' => 'Late Fee Percent',
'late_fee_added' => 'Late fee added on :date',
); );

View File

@ -3,36 +3,56 @@
@if (isset($isReminder) && $isReminder) @if (isset($isReminder) && $isReminder)
{!! Former::populateField('enable_' . $field, intval($account->{'enable_' . $field})) !!} {!! Former::populateField('enable_' . $field, intval($account->{'enable_' . $field})) !!}
{!! Former::populateField('late_fee' . $number . '_amount', $account->account_email_settings->{"late_fee{$number}_amount"}) !!}
{!! Former::populateField('late_fee' . $number . '_percent', $account->account_email_settings->{"late_fee{$number}_percent"}) !!}
<div class="row well" style="padding-bottom:20px"> <div class="well" style="padding-bottom:20px">
<div class="col-md-4" style="padding-top:10px"> <div class="row">
{!! Former::checkbox('enable_' . $field) <div class="col-md-6">
->text(trans('texts.send_automatically'))->label('') {!! Former::plaintext('schedule')
->value(1) !!}
</div>
<div class="col-md-8">
{!! Former::plaintext('')
->value( ->value(
Former::input('num_days_' . $field) Former::input('num_days_' . $field)
->addClass('enable-' . $field)
->style('float:left;width:20%') ->style('float:left;width:20%')
->raw() . ->raw() .
Former::select('direction_' . $field) Former::select('direction_' . $field)
->addOption(trans('texts.days_before'), REMINDER_DIRECTION_BEFORE) ->addOption(trans('texts.days_before'), REMINDER_DIRECTION_BEFORE)
->addOption(trans('texts.days_after'), REMINDER_DIRECTION_AFTER) ->addOption(trans('texts.days_after'), REMINDER_DIRECTION_AFTER)
->addClass('enable-' . $field)
->style('float:left;width:40%') ->style('float:left;width:40%')
->raw() . ->raw() .
'<div id="days_after_'. $field .'" style="float:left;width:40%;display:none;padding-top:8px;padding-left:16px;font-size:16px;">' . trans('texts.days_after') . '</div>' . '<div id="days_after_'. $field .'" style="float:left;width:40%;display:none;padding-top:8px;padding-left:16px;font-size:16px;">' . trans('texts.days_after') . '</div>' .
Former::select('field_' . $field) Former::select('field_' . $field)
->addOption(trans('texts.field_due_date'), REMINDER_FIELD_DUE_DATE) ->addOption(trans('texts.field_due_date'), REMINDER_FIELD_DUE_DATE)
->addOption(trans('texts.field_invoice_date'), REMINDER_FIELD_INVOICE_DATE) ->addOption(trans('texts.field_invoice_date'), REMINDER_FIELD_INVOICE_DATE)
->addClass('enable-' . $field)
->style('float:left;width:40%') ->style('float:left;width:40%')
->raw() ->raw()
) !!} ) !!}
</div> </div>
<div class="col-md-6">
{!! Former::checkbox('enable_' . $field)
->text('enable')
->label('send_email')
->value(1) !!}
</div> </div>
</div>
<div class="row" style="padding-top:30px">
<div class="col-md-6">
{!! Former::text('late_fee' . $number . '_amount')
->label('late_fee_amount')
->type('number')
->step('any') !!}
</div>
<div class="col-md-6">
{!! Former::text('late_fee' . $number . '_percent')
->label('late_fee_percent')
->type('number')
->step('any')
->append('%') !!}
</div>
</div>
</div>
<br/> <br/>
@endif @endif
<div class="row"> <div class="row">

View File

@ -75,9 +75,9 @@
<li role="presentation"><a href="#reminder3" aria-controls="footer" role="tab" data-toggle="tab">{{ trans('texts.third_reminder') }}</a></li> <li role="presentation"><a href="#reminder3" aria-controls="footer" role="tab" data-toggle="tab">{{ trans('texts.third_reminder') }}</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
@include('accounts.template', ['field' => 'reminder1', 'isReminder' => true, 'active' => true]) @include('accounts.template', ['field' => 'reminder1', 'number' => 1, 'isReminder' => true, 'active' => true])
@include('accounts.template', ['field' => 'reminder2', 'isReminder' => true]) @include('accounts.template', ['field' => 'reminder2', 'number' => 2, 'isReminder' => true])
@include('accounts.template', ['field' => 'reminder3', 'isReminder' => true]) @include('accounts.template', ['field' => 'reminder3', 'number' => 3, 'isReminder' => true])
</div> </div>
</div> </div>
</div> </div>
@ -192,23 +192,11 @@
} }
} }
for (var i=1; i<=3; i++) {
$('#enable_reminder' + i).bind('click', {id: i}, function(event) {
enableReminder(event.data.id)
});
enableReminder(i);
}
$('.show-when-ready').show(); $('.show-when-ready').show();
refreshPreview(); refreshPreview();
}); });
function enableReminder(id) {
var checked = $('#enable_reminder' + id).is(':checked');
$('.enable-reminder' + id).attr('disabled', !checked)
}
function setDirectionShown(field) { function setDirectionShown(field) {
var val = $('#field_' + field).val(); var val = $('#field_' + field).val();
if (val == {{ REMINDER_FIELD_INVOICE_DATE }}) { if (val == {{ REMINDER_FIELD_INVOICE_DATE }}) {