diff --git a/app/Console/Commands/CreateSingleAccount.php b/app/Console/Commands/CreateSingleAccount.php index 92d1bacf3662..fe5c16f34891 100644 --- a/app/Console/Commands/CreateSingleAccount.php +++ b/app/Console/Commands/CreateSingleAccount.php @@ -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...'); diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php index b98205e70c2c..6d1426c5ed7b 100644 --- a/app/Console/Commands/CreateTestData.php +++ b/app/Console/Commands/CreateTestData.php @@ -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'); diff --git a/app/Console/Commands/DemoMode.php b/app/Console/Commands/DemoMode.php index c57d084b696b..3119ac21b63e 100644 --- a/app/Console/Commands/DemoMode.php +++ b/app/Console/Commands/DemoMode.php @@ -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) { diff --git a/app/Console/Commands/SubdomainFill.php b/app/Console/Commands/SubdomainFill.php index afa2624ae0e3..ad5471d7866c 100644 --- a/app/Console/Commands/SubdomainFill.php +++ b/app/Console/Commands/SubdomainFill.php @@ -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(); - // } - // }); } } diff --git a/app/DataMapper/ClientRegistrationFields.php b/app/DataMapper/ClientRegistrationFields.php index f56252f6fd70..23106c42b08d 100644 --- a/app/DataMapper/ClientRegistrationFields.php +++ b/app/DataMapper/ClientRegistrationFields.php @@ -32,7 +32,7 @@ class ClientRegistrationFields ], [ 'key' => 'phone', - 'required' => true + 'required' => false ], [ 'key' => 'password', diff --git a/app/Helpers/Invoice/ProRata.php b/app/Helpers/Invoice/ProRata.php new file mode 100644 index 000000000000..e3291b25c218 --- /dev/null +++ b/app/Helpers/Invoice/ProRata.php @@ -0,0 +1,144 @@ +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; + } + + } + +} \ No newline at end of file diff --git a/app/Helpers/Subscription/SubscriptionCalculator.php b/app/Helpers/Subscription/SubscriptionCalculator.php new file mode 100644 index 000000000000..77f14f16fd0e --- /dev/null +++ b/app/Helpers/Subscription/SubscriptionCalculator.php @@ -0,0 +1,102 @@ +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(); + + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 20634db141fa..000cfdf98a31 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -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); diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php index 54b04308325e..5ac5ed157a78 100644 --- a/app/Http/Controllers/CompanyController.php +++ b/app/Http/Controllers/CompanyController.php @@ -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()) diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index 83ed146b313e..903e350de92e 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -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(); } diff --git a/app/Services/Subscription/SubscriptionService.php b/app/Services/Subscription/SubscriptionService.php index 8771b9034c8c..0d6cd9f553a0 100644 --- a/app/Services/Subscription/SubscriptionService.php +++ b/app/Services/Subscription/SubscriptionService.php @@ -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, diff --git a/app/Utils/SystemHealth.php b/app/Utils/SystemHealth.php index fb329a85e358..b6ae0d3c880c 100644 --- a/app/Utils/SystemHealth.php +++ b/app/Utils/SystemHealth.php @@ -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; diff --git a/database/factories/SubscriptionFactory.php b/database/factories/SubscriptionFactory.php index a9b008244c7f..db73008bb34a 100644 --- a/database/factories/SubscriptionFactory.php +++ b/database/factories/SubscriptionFactory.php @@ -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(), ]; } } diff --git a/tests/Unit/RefundUnitTest.php b/tests/Unit/RefundUnitTest.php new file mode 100644 index 000000000000..fe930e0a0c13 --- /dev/null +++ b/tests/Unit/RefundUnitTest.php @@ -0,0 +1,57 @@ +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'))); + + } + +} \ No newline at end of file diff --git a/tests/Unit/SubscriptionsCalcTest.php b/tests/Unit/SubscriptionsCalcTest.php new file mode 100644 index 000000000000..98e07240bef3 --- /dev/null +++ b/tests/Unit/SubscriptionsCalcTest.php @@ -0,0 +1,106 @@ +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); + + } +}