mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #6143 from turbo124/v5-develop
Make navigating subscriptions easier
This commit is contained in:
commit
c095a49603
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Exceptions\NonExistingMigrationFile;
|
||||||
use App\Http\Requests\Import\ImportJsonRequest;
|
use App\Http\Requests\Import\ImportJsonRequest;
|
||||||
use App\Jobs\Company\CompanyExport;
|
use App\Jobs\Company\CompanyExport;
|
||||||
use App\Jobs\Company\CompanyImport;
|
use App\Jobs\Company\CompanyImport;
|
||||||
@ -70,7 +71,7 @@ class ImportJsonController extends BaseController
|
|||||||
|
|
||||||
Cache::put( $hash, base64_encode( $contents ), 3600 );
|
Cache::put( $hash, base64_encode( $contents ), 3600 );
|
||||||
|
|
||||||
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->except('files'))->delay(now()->addMinutes(1));
|
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->except('files'))->delay(now()->addMinutes(1))->onQueue('himem');;
|
||||||
|
|
||||||
return response()->json(['message' => 'Processing'], 200);
|
return response()->json(['message' => 'Processing'], 200);
|
||||||
|
|
||||||
|
@ -90,6 +90,16 @@ class SubscriptionPlanSwitch extends Component
|
|||||||
|
|
||||||
$this->state['show_loading_bar'] = true;
|
$this->state['show_loading_bar'] = true;
|
||||||
|
|
||||||
|
$payment_required = $this->target->service()->changePlanPaymentCheck([
|
||||||
|
'recurring_invoice' => $this->recurring_invoice,
|
||||||
|
'subscription' => $this->subscription,
|
||||||
|
'target' => $this->target,
|
||||||
|
'hash' => $this->hash,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if($payment_required)
|
||||||
|
{
|
||||||
|
|
||||||
$this->state['invoice'] = $this->target->service()->createChangePlanInvoice([
|
$this->state['invoice'] = $this->target->service()->createChangePlanInvoice([
|
||||||
'recurring_invoice' => $this->recurring_invoice,
|
'recurring_invoice' => $this->recurring_invoice,
|
||||||
'subscription' => $this->subscription,
|
'subscription' => $this->subscription,
|
||||||
@ -109,6 +119,12 @@ class SubscriptionPlanSwitch extends Component
|
|||||||
|
|
||||||
$this->state['payment_initialised'] = true;
|
$this->state['payment_initialised'] = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$this->handlePaymentNotRequired();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$this->emit('beforePaymentEventsCompleted');
|
$this->emit('beforePaymentEventsCompleted');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +77,9 @@ class CreateAccount
|
|||||||
|
|
||||||
$sp794f3f->save();
|
$sp794f3f->save();
|
||||||
|
|
||||||
|
if(Ninja::isHosted())
|
||||||
|
$sp794f3f->startTrial('pro');
|
||||||
|
|
||||||
$sp035a66 = CreateCompany::dispatchNow($this->request, $sp794f3f);
|
$sp035a66 = CreateCompany::dispatchNow($this->request, $sp794f3f);
|
||||||
$sp035a66->load('account');
|
$sp035a66->load('account');
|
||||||
$sp794f3f->default_company_id = $sp035a66->id;
|
$sp794f3f->default_company_id = $sp035a66->id;
|
||||||
|
@ -73,6 +73,10 @@ class CompanyImport implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
||||||
|
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
|
public $timeout = 0;
|
||||||
|
|
||||||
protected $current_app_version;
|
protected $current_app_version;
|
||||||
|
|
||||||
private $account;
|
private $account;
|
||||||
|
@ -227,6 +227,21 @@ class Account extends BaseModel
|
|||||||
return $plan_details && $plan_details['trial'];
|
return $plan_details && $plan_details['trial'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function startTrial($plan)
|
||||||
|
{
|
||||||
|
if (! Ninja::isNinja()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->trial_started && $this->trial_started != '0000-00-00') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->trial_plan = $plan;
|
||||||
|
$this->trial_started = now();
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
public function getPlanDetails($include_inactive = false, $include_trial = true)
|
public function getPlanDetails($include_inactive = false, $include_trial = true)
|
||||||
{
|
{
|
||||||
$plan = $this->plan;
|
$plan = $this->plan;
|
||||||
|
@ -96,8 +96,9 @@ class SubscriptionRepository extends BaseRepository
|
|||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateLineItems($subscription, $is_recurring = false)
|
public function generateLineItems($subscription, $is_recurring = false, $is_credit = false)
|
||||||
{
|
{
|
||||||
|
$multiplier = $is_credit ? -1 : 1;
|
||||||
|
|
||||||
$line_items = [];
|
$line_items = [];
|
||||||
|
|
||||||
@ -105,13 +106,13 @@ class SubscriptionRepository extends BaseRepository
|
|||||||
{
|
{
|
||||||
foreach($subscription->service()->products() as $product)
|
foreach($subscription->service()->products() as $product)
|
||||||
{
|
{
|
||||||
$line_items[] = (array)$this->makeLineItem($product);
|
$line_items[] = (array)$this->makeLineItem($product, $multiplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($subscription->service()->recurring_products() as $product)
|
foreach($subscription->service()->recurring_products() as $product)
|
||||||
{
|
{
|
||||||
$line_items[] = (array)$this->makeLineItem($product);
|
$line_items[] = (array)$this->makeLineItem($product, $multiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
$line_items = $this->cleanItems($line_items);
|
$line_items = $this->cleanItems($line_items);
|
||||||
@ -120,13 +121,13 @@ class SubscriptionRepository extends BaseRepository
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function makeLineItem($product)
|
private function makeLineItem($product, $multiplier)
|
||||||
{
|
{
|
||||||
$item = new InvoiceItem;
|
$item = new InvoiceItem;
|
||||||
$item->quantity = $product->quantity;
|
$item->quantity = $product->quantity;
|
||||||
$item->product_key = $product->product_key;
|
$item->product_key = $product->product_key;
|
||||||
$item->notes = $product->notes;
|
$item->notes = $product->notes;
|
||||||
$item->cost = $product->price;
|
$item->cost = $product->price*$multiplier;
|
||||||
$item->tax_rate1 = $product->tax_rate1 ?: 0;
|
$item->tax_rate1 = $product->tax_rate1 ?: 0;
|
||||||
$item->tax_name1 = $product->tax_name1 ?: '';
|
$item->tax_name1 = $product->tax_name1 ?: '';
|
||||||
$item->tax_rate2 = $product->tax_rate2 ?: 0;
|
$item->tax_rate2 = $product->tax_rate2 ?: 0;
|
||||||
|
@ -214,11 +214,11 @@ class SubscriptionService
|
|||||||
|
|
||||||
if ($outstanding->count() == 0){
|
if ($outstanding->count() == 0){
|
||||||
//nothing outstanding
|
//nothing outstanding
|
||||||
return $target->price - $this->calculateProRataRefund($outstanding_invoice);
|
return $target->price - $this->calculateProRataRefundForSubscription($outstanding_invoice);
|
||||||
}
|
}
|
||||||
elseif ($outstanding->count() == 1){
|
elseif ($outstanding->count() == 1){
|
||||||
//user has multiple amounts outstanding
|
//user has multiple amounts outstanding
|
||||||
return $target->price - $this->calculateProRataRefund($outstanding_invoice);
|
return $target->price - $this->calculateProRataRefundForSubscription($outstanding_invoice);
|
||||||
}
|
}
|
||||||
elseif ($outstanding->count() > 1) {
|
elseif ($outstanding->count() > 1) {
|
||||||
//user is changing plan mid frequency cycle
|
//user is changing plan mid frequency cycle
|
||||||
@ -230,6 +230,35 @@ class SubscriptionService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We refund unused days left.
|
||||||
|
*
|
||||||
|
* @param Invoice $invoice
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
private function calculateProRataRefundForSubscription($invoice) :float
|
||||||
|
{
|
||||||
|
if(!$invoice->date)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
$start_date = Carbon::parse($invoice->date);
|
||||||
|
|
||||||
|
$current_date = now();
|
||||||
|
|
||||||
|
$days_of_subscription_used = $start_date->diffInDays($current_date);
|
||||||
|
|
||||||
|
$days_in_frequency = $this->getDaysInFrequency();
|
||||||
|
|
||||||
|
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $this->subscription->price ,2);
|
||||||
|
|
||||||
|
// nlog("days in frequency = {$days_in_frequency} - days of subscription used {$days_of_subscription_used}");
|
||||||
|
// nlog("invoice amount = {$invoice->amount}");
|
||||||
|
// nlog("pro rata refund = {$pro_rata_refund}");
|
||||||
|
|
||||||
|
return $pro_rata_refund;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We refund unused days left.
|
* We refund unused days left.
|
||||||
*
|
*
|
||||||
@ -251,6 +280,10 @@ class SubscriptionService
|
|||||||
|
|
||||||
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $invoice->amount ,2);
|
$pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $invoice->amount ,2);
|
||||||
|
|
||||||
|
// nlog("days in frequency = {$days_in_frequency} - days of subscription used {$days_of_subscription_used}");
|
||||||
|
// nlog("invoice amount = {$invoice->amount}");
|
||||||
|
// nlog("pro rata refund = {$pro_rata_refund}");
|
||||||
|
|
||||||
return $pro_rata_refund;
|
return $pro_rata_refund;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -384,6 +417,44 @@ class SubscriptionService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function changePlanPaymentCheck($data)
|
||||||
|
{
|
||||||
|
|
||||||
|
$recurring_invoice = $data['recurring_invoice'];
|
||||||
|
$old_subscription = $data['subscription'];
|
||||||
|
$target_subscription = $data['target'];
|
||||||
|
|
||||||
|
$pro_rata_charge_amount = 0;
|
||||||
|
$pro_rata_refund_amount = 0;
|
||||||
|
|
||||||
|
$last_invoice = Invoice::where('subscription_id', $recurring_invoice->subscription_id)
|
||||||
|
->where('client_id', $recurring_invoice->client_id)
|
||||||
|
->where('is_deleted', 0)
|
||||||
|
->withTrashed()
|
||||||
|
->orderBy('id', 'desc')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if($last_invoice->balance > 0)
|
||||||
|
{
|
||||||
|
$pro_rata_charge_amount = $this->calculateProRataCharge($last_invoice, $old_subscription);
|
||||||
|
nlog("pro rata charge = {$pro_rata_charge_amount}");
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$pro_rata_refund_amount = $this->calculateProRataRefund($last_invoice, $old_subscription) * -1;
|
||||||
|
nlog("pro rata refund = {$pro_rata_refund_amount}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_payable = $pro_rata_refund_amount + $pro_rata_charge_amount + $this->subscription->price;
|
||||||
|
|
||||||
|
if($total_payable > 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When changing plans, we need to generate a pro rata invoice
|
* When changing plans, we need to generate a pro rata invoice
|
||||||
*
|
*
|
||||||
@ -392,6 +463,7 @@ class SubscriptionService
|
|||||||
*/
|
*/
|
||||||
public function createChangePlanInvoice($data)
|
public function createChangePlanInvoice($data)
|
||||||
{
|
{
|
||||||
|
|
||||||
$recurring_invoice = $data['recurring_invoice'];
|
$recurring_invoice = $data['recurring_invoice'];
|
||||||
$old_subscription = $data['subscription'];
|
$old_subscription = $data['subscription'];
|
||||||
$target_subscription = $data['target'];
|
$target_subscription = $data['target'];
|
||||||
@ -500,7 +572,7 @@ class SubscriptionService
|
|||||||
$credit->date = now()->format('Y-m-d');
|
$credit->date = now()->format('Y-m-d');
|
||||||
$credit->subscription_id = $this->subscription->id;
|
$credit->subscription_id = $this->subscription->id;
|
||||||
|
|
||||||
$line_items = $subscription_repo->generateLineItems($target);
|
$line_items = $subscription_repo->generateLineItems($target, false, true);
|
||||||
|
|
||||||
$credit->line_items = array_merge($line_items, $this->calculateProRataRefundItems($last_invoice, true));
|
$credit->line_items = array_merge($line_items, $this->calculateProRataRefundItems($last_invoice, true));
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ class ActivityTransformer extends EntityTransformer
|
|||||||
'id' => (string) $this->encodePrimaryKey($activity->id),
|
'id' => (string) $this->encodePrimaryKey($activity->id),
|
||||||
'activity_type_id' => (string) $activity->activity_type_id,
|
'activity_type_id' => (string) $activity->activity_type_id,
|
||||||
'client_id' => $activity->client_id ? (string) $this->encodePrimaryKey($activity->client_id) : '',
|
'client_id' => $activity->client_id ? (string) $this->encodePrimaryKey($activity->client_id) : '',
|
||||||
|
'recurring_invoice_id' => $activity->recurring_invoice_id ? (string) $this->encodePrimaryKey($activity->recurring_invoice_id) : '',
|
||||||
'company_id' => $activity->company_id ? (string) $this->encodePrimaryKey($activity->company_id) : '',
|
'company_id' => $activity->company_id ? (string) $this->encodePrimaryKey($activity->company_id) : '',
|
||||||
'user_id' => (string) $this->encodePrimaryKey($activity->user_id),
|
'user_id' => (string) $this->encodePrimaryKey($activity->user_id),
|
||||||
'invoice_id' => $activity->invoice_id ? (string) $this->encodePrimaryKey($activity->invoice_id) : '',
|
'invoice_id' => $activity->invoice_id ? (string) $this->encodePrimaryKey($activity->invoice_id) : '',
|
||||||
|
@ -4272,6 +4272,7 @@ $LANG = array(
|
|||||||
'default_payment_method' => 'Make this your preferred way of paying.',
|
'default_payment_method' => 'Make this your preferred way of paying.',
|
||||||
'already_default_payment_method' => 'This is your preferred way of paying.',
|
'already_default_payment_method' => 'This is your preferred way of paying.',
|
||||||
'auto_bill_disabled' => 'Auto Bill Disabled',
|
'auto_bill_disabled' => 'Auto Bill Disabled',
|
||||||
|
'select_payment_method' => 'Select a payment method:',
|
||||||
);
|
);
|
||||||
|
|
||||||
return $LANG;
|
return $LANG;
|
||||||
|
@ -10,11 +10,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative flex justify-center text-sm leading-5">
|
<div class="relative flex justify-center text-sm leading-5">
|
||||||
<span class="font-bold tracking-wide bg-gray-100 px-6 py-0">Select a payment method:</span>
|
<span class="font-bold tracking-wide bg-gray-100 px-6 py-0">{{ ctrans('texts.select_payment_method')}}</span>
|
||||||
{{-- <h1 class="text-2xl font-bold tracking-wide bg-gray-100 px-6 py-0">--}}
|
<h1 class="text-2xl font-bold tracking-wide bg-gray-100 px-6 py-0">
|
||||||
{{-- {{ ctrans('texts.total') }}: {{ \App\Utils\Number::formatMoney($total, $subscription->company) }}--}}
|
{{ ctrans('texts.total') }}: {{ \App\Utils\Number::formatMoney($amount, $subscription->company) }}
|
||||||
{{-- <small class="ml-1 line-through text-gray-500">{{ \App\Utils\Number::formatMoney($subscription->price, $subscription->company) }}</small>--}}
|
</h1>
|
||||||
{{-- </h1>--}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -63,9 +62,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@elseif($amount < 0)
|
@elseif($amount < 0)
|
||||||
<button wire:click="handlePaymentNotRequired"class="px-3 py-2 border rounded mr-4 hover:border-blue-600">
|
<div class="relative flex justify-center text-sm leading-5">
|
||||||
|
<h1 class="text-2xl font-bold tracking-wide bg-gray-100 px-6 py-0">
|
||||||
|
{{ ctrans('texts.total') }}: {{ \App\Utils\Number::formatMoney($amount, $subscription->company) }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex justify-center text-sm leading-5 mt-10">
|
||||||
|
|
||||||
|
<button wire:click="handlePaymentNotRequired" class="px-3 py-2 border rounded mr-4 hover:border-blue-600">
|
||||||
{{ ctrans('texts.click_to_continue') }}
|
{{ ctrans('texts.click_to_continue') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,6 +40,8 @@
|
|||||||
{{ ctrans('texts.date') }}
|
{{ ctrans('texts.date') }}
|
||||||
</p>
|
</p>
|
||||||
</th>
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -63,6 +65,12 @@
|
|||||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
{{ $recurring_invoice->formatDate($recurring_invoice->date, $recurring_invoice->client->date_format()) }}
|
{{ $recurring_invoice->formatDate($recurring_invoice->date, $recurring_invoice->client->date_format()) }}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
<a href="{{ route('client.recurring_invoice.show', $recurring_invoice->hashed_id) }}"
|
||||||
|
class="button-link text-primary">
|
||||||
|
{{ ctrans('texts.view') }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr class="bg-white group hover:bg-gray-100">
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user