mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
commit
b915cc32c3
@ -90,6 +90,10 @@ class CreateSingleAccount extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
if(config('ninja.is_docker'))
|
||||
return;
|
||||
|
||||
MultiDB::setDb($this->option('database'));
|
||||
|
||||
$this->info(date('r').' Create Single Sample Account...');
|
||||
|
@ -78,6 +78,9 @@ class CreateTestData extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if(config('ninja.is_docker'))
|
||||
return;
|
||||
|
||||
$this->info(date('r').' Running CreateTestData...');
|
||||
$this->count = $this->argument('count');
|
||||
|
||||
|
@ -86,6 +86,9 @@ class DemoMode extends Command
|
||||
{
|
||||
set_time_limit(0);
|
||||
|
||||
if(config('ninja.is_docker'))
|
||||
return;
|
||||
|
||||
$cached_tables = config('ninja.cached_tables');
|
||||
|
||||
foreach ($cached_tables as $name => $class) {
|
||||
|
@ -59,37 +59,6 @@ class SubdomainFill extends Command
|
||||
|
||||
});
|
||||
|
||||
|
||||
// $db1 = Company::on('db-ninja-01')->get();
|
||||
|
||||
// $db1->each(function ($company){
|
||||
|
||||
// $db2 = Company::on('db-ninja-02a')->find($company->id);
|
||||
|
||||
// if($db2)
|
||||
// {
|
||||
// $db2->subdomain = $company->subdomain;
|
||||
// $db2->save();
|
||||
// }
|
||||
|
||||
// });
|
||||
|
||||
|
||||
// $db1 = null;
|
||||
// $db2 = null;
|
||||
|
||||
// $db2 = Company::on('db-ninja-02')->get();
|
||||
|
||||
// $db2->each(function ($company){
|
||||
|
||||
// $db1 = Company::on('db-ninja-01a')->find($company->id);
|
||||
|
||||
// if($db1)
|
||||
// {
|
||||
// $db1->subdomain = $company->subdomain;
|
||||
// $db1->save();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ class ClientRegistrationFields
|
||||
],
|
||||
[
|
||||
'key' => 'phone',
|
||||
'required' => true
|
||||
'required' => false
|
||||
],
|
||||
[
|
||||
'key' => 'password',
|
||||
|
144
app/Helpers/Invoice/ProRata.php
Normal file
144
app/Helpers/Invoice/ProRata.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Helpers\Invoice;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Models\RecurringInvoice;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class ProRata
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Returns the amount to refund based on
|
||||
* the time interval and the frequency duration
|
||||
*
|
||||
* @param float $amount
|
||||
* @param Carbon $from_date
|
||||
* @param Carbon $to_date
|
||||
* @param int $frequency
|
||||
* @return float
|
||||
*/
|
||||
public function refund(float $amount, Carbon $from_date, Carbon $to_date, int $frequency) :float
|
||||
{
|
||||
$days = $from_date->copy()->diffInDays($to_date);
|
||||
$days_in_frequency = $this->getDaysInFrequency($frequency);
|
||||
|
||||
return round( (($days/$days_in_frequency) * $amount),2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount to charge based on
|
||||
* the time interval and the frequency duration
|
||||
*
|
||||
* @param float $amount
|
||||
* @param Carbon $from_date
|
||||
* @param Carbon $to_date
|
||||
* @param int $frequency
|
||||
* @return float
|
||||
*/
|
||||
public function charge(float $amount, Carbon $from_date, Carbon $to_date, int $frequency) :float
|
||||
{
|
||||
$days = $from_date->copy()->diffInDays($to_date);
|
||||
$days_in_frequency = $this->getDaysInFrequency($frequency);
|
||||
nlog($from_date->format('Y-m-d'));
|
||||
nlog($days);
|
||||
nlog($days_in_frequency);
|
||||
nlog($amount);
|
||||
return round( (($days/$days_in_frequency) * $amount),2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the line items of an invoice
|
||||
* to be pro rata refunded.
|
||||
*
|
||||
* @param Invoice $invoice
|
||||
* @param bool $is_credit
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function refundItems(Invoice $invoice, $is_credit = false) :array
|
||||
{
|
||||
if(!$invoice)
|
||||
return [];
|
||||
|
||||
$recurring_invoice = RecurringInvoice::find($invoice->recurring_id)->first();
|
||||
|
||||
if(!$recurring_invoice)
|
||||
throw new \Exception("Invoice isn't attached to a recurring invoice");
|
||||
|
||||
/* depending on whether we are creating an invoice or a credit*/
|
||||
$multiplier = $is_credit ? 1 : -1;
|
||||
|
||||
$start_date = Carbon::parse($invoice->date);
|
||||
|
||||
$line_items = [];
|
||||
|
||||
foreach($invoice->line_items as $item)
|
||||
{
|
||||
|
||||
if($item->product_key != ctrans('texts.refund'))
|
||||
{
|
||||
$item->quantity = 1;
|
||||
$item->cost = $this->refund($item->cost*$multiplier, $start_date, now(), $recurring_invoice->frequency_id);
|
||||
$item->product_key = ctrans('texts.refund');
|
||||
$item->notes = ctrans('texts.refund') . ": ". $item->notes;
|
||||
|
||||
$line_items[] = $item;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $line_items;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function getDaysInFrequency($frequency)
|
||||
{
|
||||
|
||||
switch ($frequency) {
|
||||
case RecurringInvoice::FREQUENCY_DAILY:
|
||||
return 1;
|
||||
case RecurringInvoice::FREQUENCY_WEEKLY:
|
||||
return 7;
|
||||
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
|
||||
return 14;
|
||||
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
|
||||
return now()->diffInDays(now()->addWeeks(4));
|
||||
case RecurringInvoice::FREQUENCY_MONTHLY:
|
||||
return now()->diffInDays(now()->addMonthNoOverflow());
|
||||
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
|
||||
return now()->diffInDays(now()->addMonthNoOverflow(2));
|
||||
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
|
||||
return now()->diffInDays(now()->addMonthNoOverflow(3));
|
||||
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
|
||||
return now()->diffInDays(now()->addMonthNoOverflow(4));
|
||||
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
|
||||
return now()->diffInDays(now()->addMonthNoOverflow(6));
|
||||
case RecurringInvoice::FREQUENCY_ANNUALLY:
|
||||
return now()->diffInDays(now()->addYear());
|
||||
case RecurringInvoice::FREQUENCY_TWO_YEARS:
|
||||
return now()->diffInDays(now()->addYears(2));
|
||||
case RecurringInvoice::FREQUENCY_THREE_YEARS:
|
||||
return now()->diffInDays(now()->addYears(3));
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
102
app/Helpers/Subscription/SubscriptionCalculator.php
Normal file
102
app/Helpers/Subscription/SubscriptionCalculator.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Helpers\Subscription;
|
||||
|
||||
use App\Helpers\Invoice\ProRata;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Subscription;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* SubscriptionCalculator.
|
||||
*/
|
||||
class SubscriptionCalculator
|
||||
{
|
||||
|
||||
public Subscription $target_subscription;
|
||||
|
||||
public Invoice $invoice;
|
||||
|
||||
public function __construct(Subscription $target_subscription, Invoice $invoice)
|
||||
{
|
||||
$this->target_subscription = $target_subscription;
|
||||
$this->invoice = $invoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the user is currently up
|
||||
* to date with their payments for
|
||||
* a given recurring invoice
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPaidUp() :bool
|
||||
{
|
||||
|
||||
$outstanding_invoices_exist = Invoice::whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('subscription_id', $this->invoice->subscription_id)
|
||||
->where('client_id', $this->invoice->client_id)
|
||||
->where('balance', '>', 0)
|
||||
->exists();
|
||||
|
||||
return ! $outstanding_invoices_exist;
|
||||
|
||||
}
|
||||
|
||||
public function calcUpgradePlan()
|
||||
{
|
||||
//set the starting refund amount
|
||||
$refund_amount = 0;
|
||||
|
||||
$refund_invoice = false;
|
||||
|
||||
//are they paid up to date.
|
||||
|
||||
//yes - calculate refund
|
||||
if($this->isPaidUp())
|
||||
$refund_invoice = $this->getRefundInvoice();
|
||||
|
||||
if($refund_invoice)
|
||||
{
|
||||
$subscription = Subscription::find($this->invoice->subscription_id);
|
||||
$pro_rata = new ProRata;
|
||||
|
||||
$to_date = $subscription->service()->getNextDateForFrequency(Carbon::parse($refund_invoice->date), $subscription->frequency_id);
|
||||
|
||||
$refund_amount = $pro_rata->refund($refund_invoice->amount, now(), $to_date, $subscription->frequency_id);
|
||||
|
||||
$charge_amount = $pro_rata->charge($this->target_subscription->price, now(), $to_date, $this->target_subscription->frequency_id);
|
||||
|
||||
return ($charge_amount - $refund_amount);
|
||||
}
|
||||
|
||||
//no - return full freight charge.
|
||||
return $this->target_subscription->price;
|
||||
}
|
||||
|
||||
public function executeUpgradePlan()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private function getRefundInvoice()
|
||||
{
|
||||
return Invoice::where('subscription_id', $this->invoice->subscription_id)
|
||||
->where('client_id', $this->invoice->client_id)
|
||||
->where('is_deleted', 0)
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -260,14 +260,14 @@ class LoginController extends BaseController
|
||||
->increment()
|
||||
->batch();
|
||||
|
||||
SystemLogger::dispatch(
|
||||
json_encode(['ip' => request()->getClientIp()]),
|
||||
SystemLog::CATEGORY_SECURITY,
|
||||
SystemLog::EVENT_USER,
|
||||
SystemLog::TYPE_LOGIN_FAILURE,
|
||||
null,
|
||||
Company::first(),
|
||||
);
|
||||
// SystemLogger::dispatch(
|
||||
// json_encode(['ip' => request()->getClientIp()]),
|
||||
// SystemLog::CATEGORY_SECURITY,
|
||||
// SystemLog::EVENT_USER,
|
||||
// SystemLog::TYPE_LOGIN_FAILURE,
|
||||
// null,
|
||||
// Company::first(),
|
||||
// );
|
||||
|
||||
$this->incrementLoginAttempts($request);
|
||||
|
||||
|
@ -499,7 +499,7 @@ class CompanyController extends BaseController
|
||||
|
||||
$account->delete();
|
||||
|
||||
if(Ninja::isHosted() && $request->has('cancellation_message') && strlen($request->input('cancellation_message')) > 1)
|
||||
if(Ninja::isHosted())
|
||||
\Modules\Admin\Jobs\Account\NinjaDeletedAccount::dispatch($account_key, $request->all());
|
||||
|
||||
LightLogs::create(new AccountDeleted())
|
||||
|
@ -20,6 +20,7 @@ use App\Jobs\Util\SchedulerCheck;
|
||||
use App\Jobs\Util\VersionCheck;
|
||||
use App\Models\Account;
|
||||
use App\Utils\CurlUtils;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\SystemHealth;
|
||||
use App\Utils\Traits\AppSetup;
|
||||
@ -245,7 +246,7 @@ class SetupController extends Controller
|
||||
public function checkPdf(Request $request)
|
||||
{
|
||||
try {
|
||||
if (config('ninja.phantomjs_pdf_generation')) {
|
||||
if (config('ninja.pdf_generator') == 'phantom') {
|
||||
return $this->testPhantom();
|
||||
}
|
||||
|
||||
|
@ -934,6 +934,38 @@ class SubscriptionService
|
||||
|
||||
}
|
||||
|
||||
public function getNextDateForFrequency($date, $frequency)
|
||||
{
|
||||
switch ($frequency) {
|
||||
case RecurringInvoice::FREQUENCY_DAILY:
|
||||
return $date->addDay();
|
||||
case RecurringInvoice::FREQUENCY_WEEKLY:
|
||||
return $date->addDays(7);
|
||||
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
|
||||
return $date->addDays(13);
|
||||
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
|
||||
return $date->addWeeks(4);
|
||||
case RecurringInvoice::FREQUENCY_MONTHLY:
|
||||
return $date->addMonthNoOverflow();
|
||||
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
|
||||
return $date->addMonthNoOverflow(2);
|
||||
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
|
||||
return $date->addMonthNoOverflow(3);
|
||||
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
|
||||
return $date->addMonthNoOverflow(4);
|
||||
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
|
||||
return $date->addMonthNoOverflow(6);
|
||||
case RecurringInvoice::FREQUENCY_ANNUALLY:
|
||||
return $date->addYear();
|
||||
case RecurringInvoice::FREQUENCY_TWO_YEARS:
|
||||
return $date->addYears(2);
|
||||
case RecurringInvoice::FREQUENCY_THREE_YEARS:
|
||||
return $date->addYears(3);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 'email' => $this->email ?? $this->contact->email,
|
||||
|
@ -25,17 +25,13 @@ use Illuminate\Support\Facades\Queue;
|
||||
class SystemHealth
|
||||
{
|
||||
private static $extensions = [
|
||||
// 'mysqli',
|
||||
'gd',
|
||||
'curl',
|
||||
'zip',
|
||||
// 'gmp',
|
||||
'openssl',
|
||||
'mbstring',
|
||||
'xml',
|
||||
'bcmath',
|
||||
// 'mysqlnd',
|
||||
//'intl', //todo double check whether we need this for email dns validation
|
||||
];
|
||||
|
||||
private static $php_version = 7.4;
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Subscription;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
@ -32,7 +33,8 @@ class SubscriptionFactory extends Factory
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
|
||||
'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY,
|
||||
'name' => $this->faker->company(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
57
tests/Unit/RefundUnitTest.php
Normal file
57
tests/Unit/RefundUnitTest.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Helpers\Invoice\ProRata;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
class RefundUnitTest extends TestCase
|
||||
{
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testProRataRefundMonthly()
|
||||
{
|
||||
$pro_rata = new ProRata();
|
||||
$refund = $pro_rata->refund(10, Carbon::parse('2021-01-01'), Carbon::parse('2021-01-31'), RecurringInvoice::FREQUENCY_MONTHLY);
|
||||
|
||||
$this->assertEquals(9.68, $refund);
|
||||
|
||||
$this->assertEquals(30, Carbon::parse('2021-01-01')->diffInDays(Carbon::parse('2021-01-31')));
|
||||
|
||||
}
|
||||
|
||||
public function testProRataRefundYearly()
|
||||
{
|
||||
$pro_rata = new ProRata();
|
||||
|
||||
$refund = $pro_rata->refund(10, Carbon::parse('2021-01-01'), Carbon::parse('2021-01-31'), RecurringInvoice::FREQUENCY_ANNUALLY);
|
||||
|
||||
$this->assertEquals(0.82, $refund);
|
||||
}
|
||||
|
||||
public function testDiffInDays()
|
||||
{
|
||||
|
||||
$this->assertEquals(30, Carbon::parse('2021-01-01')->diffInDays(Carbon::parse('2021-01-31')));
|
||||
|
||||
}
|
||||
|
||||
}
|
106
tests/Unit/SubscriptionsCalcTest.php
Normal file
106
tests/Unit/SubscriptionsCalcTest.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Helpers\Invoice\ProRata;
|
||||
use App\Helpers\Subscription\SubscriptionCalculator;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Subscription;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Tests\MockUnitData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
class SubscriptionsCalcTest extends TestCase
|
||||
{
|
||||
use MockUnitData;
|
||||
/**
|
||||
* Important consideration with Base64
|
||||
* encoding checks.
|
||||
*
|
||||
* No method can guarantee against false positives.
|
||||
*/
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
}
|
||||
|
||||
public function testCalcUpgradePrice()
|
||||
{
|
||||
|
||||
$subscription = Subscription::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'price' => 10,
|
||||
]);
|
||||
|
||||
|
||||
$target = Subscription::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'price' => 20,
|
||||
]);
|
||||
|
||||
$invoice = Invoice::factory()->create([
|
||||
'line_items' => $this->buildLineItems(),
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'tax_rate1' => 0,
|
||||
'tax_name1' => '',
|
||||
'tax_rate2' => 0,
|
||||
'tax_name2' => '',
|
||||
'tax_rate3' => 0,
|
||||
'tax_name3' => '',
|
||||
'discount' => 0,
|
||||
'subscription_id' => $subscription->id,
|
||||
'date' => '2021-01-01',
|
||||
]);
|
||||
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
|
||||
$this->assertEquals(10, $invoice->amount);
|
||||
|
||||
$invoice->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals(10, $invoice->amount);
|
||||
$this->assertEquals(10, $invoice->balance);
|
||||
|
||||
$sub_calculator = new SubscriptionCalculator($target->fresh(), $invoice->fresh());
|
||||
|
||||
$this->assertFalse($sub_calculator->isPaidUp());
|
||||
|
||||
$invoice->service()->markPaid()->save();
|
||||
|
||||
$this->assertTrue($sub_calculator->isPaidUp());
|
||||
|
||||
$this->assertEquals(10, $invoice->amount);
|
||||
$this->assertEquals(0, $invoice->balance);
|
||||
|
||||
$pro_rata = new ProRata;
|
||||
|
||||
$refund = $pro_rata->refund($invoice->amount, Carbon::parse('2021-01-01'), Carbon::parse('2021-01-06'), $subscription->frequency_id);
|
||||
|
||||
$this->assertEquals(1.61, $refund);
|
||||
|
||||
$pro_rata = new ProRata;
|
||||
|
||||
$upgrade = $pro_rata->charge($target->price, Carbon::parse('2021-01-01'), Carbon::parse('2021-01-06'), $subscription->frequency_id);
|
||||
|
||||
$this->assertEquals(3.23, $upgrade);
|
||||
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user