Updated translations for payment schedules and invoice outstanding tasks
This commit is contained in:
parent
e50ef1d373
commit
0d9651f95c
|
|
@ -95,10 +95,11 @@ class PaymentScheduleRequest extends Request
|
||||||
$input['parameters']['schedule'] = $this->generateSchedule($input['frequency_id'], $input['remaining_cycles'], Carbon::parse($due_date));
|
$input['parameters']['schedule'] = $this->generateSchedule($input['frequency_id'], $input['remaining_cycles'], Carbon::parse($due_date));
|
||||||
}
|
}
|
||||||
|
|
||||||
// $input['next_run'] = $input['parameters']['schedule'][0]['date'];
|
|
||||||
|
|
||||||
$input['remaining_cycles'] = count($input['parameters']['schedule']);
|
$input['remaining_cycles'] = count($input['parameters']['schedule']);
|
||||||
|
|
||||||
|
$input['next_run_client'] = $input['next_run'];
|
||||||
|
$input['next_run'] = Carbon::parse($input['next_run'])->addSeconds($this->invoice->company->timezone_offset())->format('Y-m-d');
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,10 +108,10 @@ class PaymentScheduleRequest extends Request
|
||||||
|
|
||||||
|
|
||||||
$amount = round($this->invoice->amount / $remaining_cycles, 2);
|
$amount = round($this->invoice->amount / $remaining_cycles, 2);
|
||||||
|
|
||||||
$delta = round($amount * $remaining_cycles, 2);
|
$delta = round($amount * $remaining_cycles, 2);
|
||||||
$adjustment = 0;
|
$adjustment = 0;
|
||||||
|
|
||||||
if(floatval($delta) != floatval($this->invoice->amount)) {
|
if(floatval($delta) != floatval($this->invoice->amount)) {
|
||||||
$adjustment = round(floatval($this->invoice->amount) - floatval($delta), 2); //adjustment to make the total amount equal to the invoice amount
|
$adjustment = round(floatval($this->invoice->amount) - floatval($delta), 2); //adjustment to make the total amount equal to the invoice amount
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +127,7 @@ class PaymentScheduleRequest extends Request
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if($adjustment > 0) {
|
if($adjustment != 0) {
|
||||||
$schedule[$remaining_cycles-1]['amount'] += $adjustment;
|
$schedule[$remaining_cycles-1]['amount'] += $adjustment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -834,7 +834,7 @@ class Invoice extends BaseModel
|
||||||
return $reminder_schedule;
|
return $reminder_schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paymentSchedule(): array
|
public function paymentSchedule(bool $formatted = false): mixed
|
||||||
{
|
{
|
||||||
|
|
||||||
$schedule = \App\Models\Scheduler::where('company_id', $this->company_id)
|
$schedule = \App\Models\Scheduler::where('company_id', $this->company_id)
|
||||||
|
|
@ -843,15 +843,63 @@ class Invoice extends BaseModel
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (! $schedule) {
|
if (! $schedule) {
|
||||||
return [];
|
|
||||||
|
if($formatted){
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return collect($schedule->parameters['schedule'])->map(function ($item) use ($schedule) {
|
if(!$formatted){
|
||||||
return [
|
return collect($schedule->parameters['schedule'])->map(function ($item) use ($schedule) {
|
||||||
'date' => $item['date'],
|
return [
|
||||||
'amount' => $item['is_amount'] ? \App\Utils\Number::formatMoney($item['amount'], $this->client) : $item['amount'] ." %",
|
'date' => $this->formatDate($item['date'], $this->client->date_format()),
|
||||||
'auto_bill' => $schedule->parameters['auto_bill'],
|
'amount' => $item['is_amount'] ? \App\Utils\Number::formatMoney($item['amount'], $this->client) : $item['amount'] ." %",
|
||||||
];
|
'auto_bill' => $schedule->parameters['auto_bill'],
|
||||||
})->toArray();
|
];
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$formatted_string = "<div id=\"payment-schedule\">";
|
||||||
|
|
||||||
|
foreach($schedule->parameters['schedule'] as $item){
|
||||||
|
$amount = $item['is_amount'] ? $item['amount'] : round($this->amount * ($item['amount']/100),2);
|
||||||
|
$amount = \App\Utils\Number::formatMoney($amount, $this->client);
|
||||||
|
|
||||||
|
$formatted_string .= "<p><span class=\"payment-schedule-date\">".$this->formatDate($item['date'], $this->client->date_format()) . "</span> - <span class=\"payment-schedule-amount\"> " . $amount."</span></p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$formatted_string .= "</div>";
|
||||||
|
|
||||||
|
return htmlspecialchars($formatted_string, ENT_QUOTES, 'UTF-8');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paymentScheduleInterval(): string
|
||||||
|
{
|
||||||
|
$schedule = \App\Models\Scheduler::where('company_id', $this->company_id)
|
||||||
|
->where('template', 'payment_schedule')
|
||||||
|
->where('parameters->invoice_id', $this->hashed_id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if(!$schedule)
|
||||||
|
return '';
|
||||||
|
|
||||||
|
$schedule_array = $schedule->parameters['schedule'] ?? [];
|
||||||
|
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
foreach($schedule_array as $key => $item){
|
||||||
|
if($date = Carbon::parse($item['date'])->eq(Carbon::parse($schedule->next_run_client))){
|
||||||
|
$index = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$amount = $schedule_array[$index]['is_amount'] ? \App\Utils\Number::formatMoney($schedule_array[$index]['amount'], $this->client) : \App\Utils\Number::formatMoney(($schedule_array[$index]['amount']/100)*$this->amount, $this->client);
|
||||||
|
|
||||||
|
return ctrans('texts.payment_schedule_interval', ['index' => $index+1, 'total' => count($schedule_array), 'amount' => $amount]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,15 +54,8 @@ class PaymentSchedule
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlog($next_schedule);
|
|
||||||
|
|
||||||
if($next_schedule['is_amount']){
|
$amount = $next_schedule['is_amount'] ? $next_schedule['amount'] : round(($next_schedule['amount']/100)*$invoice->amount, 2);
|
||||||
nlog("is an amount");
|
|
||||||
$amount = $next_schedule['amount'];
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
$amount = round(($next_schedule['amount']/100)*$invoice->amount, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
$amount = min($amount, $invoice->amount);
|
$amount = min($amount, $invoice->amount);
|
||||||
|
|
||||||
|
|
@ -70,17 +63,12 @@ class PaymentSchedule
|
||||||
$amount = $invoice->balance;
|
$amount = $invoice->balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlog("amount to add: {$amount}");
|
|
||||||
nlog("invoice partial before: {$invoice->partial}");
|
|
||||||
$invoice->partial += $amount;
|
$invoice->partial += $amount;
|
||||||
$invoice->partial_due_date = $next_schedule['date'];
|
$invoice->partial_due_date = $next_schedule['date'];
|
||||||
$invoice->due_date = Carbon::parse($next_schedule['date'])->addDay()->format('Y-m-d');
|
$invoice->due_date = Carbon::parse($next_schedule['date'])->addDay()->format('Y-m-d');
|
||||||
|
|
||||||
$invoice->save();
|
$invoice->save();
|
||||||
|
|
||||||
|
|
||||||
nlog("invoice partial after: {$invoice->partial}");
|
|
||||||
|
|
||||||
if($this->scheduler->parameters['auto_bill']){
|
if($this->scheduler->parameters['auto_bill']){
|
||||||
|
|
||||||
try{
|
try{
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,14 @@ class HtmlEngine
|
||||||
$data['$tax_info'] = ['value' => $this->taxLabel(), 'label' => ''];
|
$data['$tax_info'] = ['value' => $this->taxLabel(), 'label' => ''];
|
||||||
$data['$net'] = ['value' => '', 'label' => ctrans('texts.net')];
|
$data['$net'] = ['value' => '', 'label' => ctrans('texts.net')];
|
||||||
|
|
||||||
|
$data['$payment_schedule'] = ['value' => '', 'label' => ctrans('texts.payment_schedule')];
|
||||||
|
$data['$payment_schedule_interval'] = ['value' => '', 'label' => ctrans('texts.payment_schedule')];
|
||||||
|
|
||||||
if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') {
|
if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') {
|
||||||
|
|
||||||
|
$data['$payment_schedule'] = ['value' => $this->entity->paymentSchedule(true), 'label' => ctrans('texts.payment_schedule')];
|
||||||
|
$data['$payment_schedule_interval'] = ['value' => $this->entity->paymentScheduleInterval(), 'label' => ctrans('texts.payment_schedule')];
|
||||||
|
|
||||||
$data['$entity'] = ['value' => ctrans('texts.invoice'), 'label' => ctrans('texts.invoice')];
|
$data['$entity'] = ['value' => ctrans('texts.invoice'), 'label' => ctrans('texts.invoice')];
|
||||||
$data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
$data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||||
$data['$invoice'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
$data['$invoice'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||||
|
|
|
||||||
|
|
@ -5606,7 +5606,11 @@ $lang = array(
|
||||||
'schedule_frequency_help' => 'The interval time between each payment',
|
'schedule_frequency_help' => 'The interval time between each payment',
|
||||||
'first_payment_date' => 'First Payment Date',
|
'first_payment_date' => 'First Payment Date',
|
||||||
'first_payment_date_help' => 'The date of the first payment',
|
'first_payment_date_help' => 'The date of the first payment',
|
||||||
|
'payment_schedule_interval' => 'Payment :index of :total for :amount',
|
||||||
|
'auto_send' => 'Auto Send',
|
||||||
|
'auto_send_help' => 'Automatically emails the invoice to the client',
|
||||||
|
'include_project_tasks' => 'Include Project Tasks',
|
||||||
|
'include_project_tasks_help' => 'Also invoice tasks that are part of a project',
|
||||||
);
|
);
|
||||||
|
|
||||||
return $lang;
|
return $lang;
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,93 @@ class SchedulerTest extends TestCase
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function testPaymentScheduleCalculationsIsPercentageWithAutoBill()
|
||||||
|
{
|
||||||
|
$settings = $this->company->settings;
|
||||||
|
$settings->use_credits_payment = 'off';
|
||||||
|
$settings->use_unapplied_payment = 'off';
|
||||||
|
$this->company->settings = $settings;
|
||||||
|
$this->company->save();
|
||||||
|
|
||||||
|
\App\Models\Credit::where('client_id', $this->client->id)->delete();
|
||||||
|
|
||||||
|
$invoice = Invoice::factory()->create([
|
||||||
|
'company_id' => $this->company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
'client_id' => $this->client->id,
|
||||||
|
'date' => now()->format('Y-m-d'),
|
||||||
|
'due_date' => now()->addDays(30)->format('Y-m-d'),
|
||||||
|
'partial' => 0,
|
||||||
|
'partial_due_date' => null,
|
||||||
|
'amount' => 300.00,
|
||||||
|
'balance' => 300.00,
|
||||||
|
'status_id' => Invoice::STATUS_SENT,
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => 'A test payment schedule scheduler',
|
||||||
|
'frequency_id' => 0,
|
||||||
|
'next_run' => now()->format('Y-m-d'),
|
||||||
|
'template' => 'payment_schedule',
|
||||||
|
'parameters' => [
|
||||||
|
'invoice_id' => $invoice->hashed_id,
|
||||||
|
'auto_bill' => true,
|
||||||
|
'schedule' => [
|
||||||
|
[
|
||||||
|
'id' => 1,
|
||||||
|
'date' => now()->format('Y-m-d'),
|
||||||
|
'amount' => 10,
|
||||||
|
'is_amount' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 2,
|
||||||
|
'date' => now()->addDays(30)->format('Y-m-d'),
|
||||||
|
'amount' => 90,
|
||||||
|
'is_amount' => false,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $this->token,
|
||||||
|
])->postJson('/api/v1/task_schedulers', $data);
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
|
||||||
|
$arr = $response->json();
|
||||||
|
|
||||||
|
$scheduler = Scheduler::find($this->decodePrimaryKey($arr['data']['id']));
|
||||||
|
|
||||||
|
$this->assertNotNull($scheduler);
|
||||||
|
|
||||||
|
$scheduler->service()->runTask();
|
||||||
|
|
||||||
|
$invoice = $invoice->fresh();
|
||||||
|
|
||||||
|
$this->assertEquals(30, $invoice->partial);
|
||||||
|
$this->assertEquals(now()->format('Y-m-d'), $invoice->partial_due_date->format('Y-m-d'));
|
||||||
|
|
||||||
|
$scheduler = $scheduler->fresh();
|
||||||
|
|
||||||
|
$this->assertEquals(now()->addDays(30)->format('Y-m-d'), $scheduler->next_run->format('Y-m-d'));
|
||||||
|
|
||||||
|
$this->travelTo(now()->addDays(30));
|
||||||
|
|
||||||
|
$scheduler->service()->runTask();
|
||||||
|
|
||||||
|
$invoice = $invoice->fresh();
|
||||||
|
|
||||||
|
$this->assertEquals(300, $invoice->partial);
|
||||||
|
$this->assertEquals(now()->format('Y-m-d'), $invoice->partial_due_date->format('Y-m-d'));
|
||||||
|
|
||||||
|
$this->travelBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function testPaymentScheduleCalculationsIsAmountWithAutoBill()
|
public function testPaymentScheduleCalculationsIsAmountWithAutoBill()
|
||||||
{
|
{
|
||||||
$settings = $this->company->settings;
|
$settings = $this->company->settings;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue