1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-11 05:32:39 +01:00

Merge pull request #8047 from turbo124/v5-develop

v5.5.47
This commit is contained in:
David Bomba 2022-12-07 17:55:55 +11:00 committed by GitHub
commit 7eb5b37eef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 257 additions and 87 deletions

View File

@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
operating-system: ['ubuntu-20.04', 'ubuntu-22.04']
php-versions: ['8.1.11']
php-versions: ['8.1']
phpunit-versions: ['latest']
env:

View File

@ -1 +1 @@
5.5.46
5.5.47

View File

@ -153,6 +153,21 @@ class BankTransactionFilters extends QueryFilters
{
$sort_col = explode('|', $sort);
if(!is_array($sort_col))
return $this->builder;
if($sort_col[0] == 'deposit')
return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $sort_col[1]);
if($sort_col[0] == 'withdrawal')
return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $sort_col[1]);
if($sort_col[0] == 'status')
$sort_col[0] = 'status_id';
if(in_array($sort_col[0],['invoices','expense']))
return $this->builder;
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}

View File

@ -11,6 +11,7 @@
namespace App\Http\Livewire;
use App\DataMapper\ClientSettings;
use App\Factory\ClientFactory;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
@ -19,15 +20,17 @@ use App\Mail\ContactPasswordlessLogin;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use App\Models\Subscription;
use App\Repositories\ClientContactRepository;
use App\Repositories\ClientRepository;
use App\Utils\Number;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use App\DataMapper\ClientSettings;
use Livewire\Component;
class BillingPortalPurchasev2 extends Component
@ -179,6 +182,13 @@ class BillingPortalPurchasev2 extends Component
*/
public $campaign;
public $bundle;
public $recurring_products;
public $products;
public $optional_recurring_products;
public $optional_products;
public $total;
public function mount()
{
MultiDB::setDb($this->company->db);
@ -196,46 +206,156 @@ class BillingPortalPurchasev2 extends Component
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){
$this->price = $this->subscription->promo_price;
}
$this->recurring_products = $this->subscription->service()->recurring_products();
$this->products = $this->subscription->service()->products();
$this->optional_recurring_products = $this->subscription->service()->optional_recurring_products();
$this->optional_products = $this->subscription->service()->optional_products();
// $this->buildBundle();
$this->bundle = collect();
}
public function updatingData()
public function buildBundle()
{
nlog('updating');
// nlog($this->data);
$this->bundle = collect();
$data = $this->data;
foreach($this->recurring_products as $key => $p)
{
$qty = isset($data[$key]['recurring_qty']) ? $data[$key]['recurring_qty'] : 1;
$total = $p->price * $qty;
$this->bundle->push([
'product' => nl2br(substr($p->notes, 0, 50)),
'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id),
'total' => $total,
'qty' => $qty,
'is_recurring' => true
]);
}
foreach($this->products as $key => $p)
{
$qty = 1;
$total = $p->price * $qty;
$this->bundle->push([
'product' => nl2br(substr($p->notes, 0, 50)),
'price' => Number::formatMoney($total, $this->subscription->company),
'total' => $total,
'qty' => $qty,
'is_recurring' => false
]);
}
foreach($this->data as $key => $value)
{
if(isset($this->data[$key]['optional_recurring_qty']))
{
$p = $this->optional_recurring_products->first(function ($v,$k) use($key){
return $k == $key;
});
$qty = isset($this->data[$key]['optional_recurring_qty']) ? $this->data[$key]['optional_recurring_qty'] : 0;
$total = $p->price * $qty;
if($qty == 0)
return;
$this->bundle->push([
'product' => nl2br(substr($p->notes, 0, 50)),
'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id),
'total' => $total,
'qty' => $qty,
'is_recurring' => true
]);
}
if(isset($this->data[$key]['optional_qty']))
{
$p = $this->optional_products->first(function ($v,$k) use($key){
return $k == $key;
});
$qty = isset($this->data[$key]['optional_qty']) ? $this->data[$key]['optional_qty'] : 0;
$total = $p->price * $qty;
if($qty == 0)
return;
$this->bundle->push([
'product' => nl2br(substr($p->notes, 0, 50)),
'price' => Number::formatMoney($total, $this->subscription->company),
'total' => $total,
'qty' => $qty,
'is_recurring' => false
]);
}
}
$this->total = Number::formatMoney($this->bundle->sum('total'), $this->subscription->company);
return $this;
}
public function updatedData()
{
nlog('updated');
nlog($this->data);
$validatedData = $this->validate();
nlog( $validatedData );
}
public function updating($prop)
{
// $this->resetValidation($prop);
// $this->resetErrorBag($prop);
}
public function updated($propertyName)
{
nlog("validating {$propertyName}");
$this->errors = $this->validateOnly($propertyName);
$x = $this->validateOnly($propertyName, $this->rules(), [], $this->attributes());
nlog($this->errors);
$validatedData = $this->validate();
nlog( $validatedData );
// // $validatedData = $this->validate();
$this->buildBundle();
// $order_validator = Validator::make($this->all(), $this->rules(), [], $this->attributes());
}
public function rules()
{
$rules = [
'email' => ['required', 'email'],
'data' => ['required', 'array'],
'data.*.recurring_qty' => ['required', 'between:100,1000'],
'data.*.optional_recurring_qty' => ['required', 'between:100,1000'],
'data.*.optional_qty' => ['required', 'between:100,1000'],
'data.*.recurring_qty' => 'numeric|between:0,1000',
'data.*.optional_recurring_qty' => 'numeric|between:0,1000',
'data.*.optional_qty' => 'numeric|between:0,1000',
];
return $rules;
}
public function attributes()
{
$attributes = [
'data.*.recurring_qty' => 'recurring_qty',
'data.*.optional_recurring_qty' => 'optional_recurring_qty',
'data.*.optional_qty' => 'optional_qty',
];
return $attributes;
}
public function store()
{
}
/**
* Handle user authentication
*

View File

@ -389,6 +389,7 @@ class MatchBankTransactions implements ShouldQueue
$this->bt->invoice_ids = collect($invoices)->pluck('hashed_id')->implode(',');
$this->bt->status_id = BankTransaction::STATUS_CONVERTED;
$this->bt->payment_id = $payment->id;
$this->bt->save();
}

View File

@ -50,7 +50,7 @@ class QuoteViewedActivity implements ShouldQueue
$fields->user_id = $event->invitation->quote->user_id;
$fields->company_id = $event->invitation->company_id;
$fields->activity_type_id = Activity::VIEW_QUOTE;
$fields->client_id = $event->invitation->client_id;
$fields->client_id = $event->invitation->quote->client_id;
$fields->client_contact_id = $event->invitation->client_contact_id;
$fields->invitation_id = $event->invitation->id;
$fields->quote_id = $event->invitation->quote_id;

View File

@ -126,6 +126,8 @@ class Company extends BaseModel
'report_include_deleted',
'invoice_task_lock',
'use_vendor_currency',
'convert_payment_currency',
'convert_expense_currency',
];
protected $hidden = [

View File

@ -154,31 +154,6 @@ class PaymentIntentWebhook implements ShouldQueue
'card_details' => isset($charge['payment_method_details']['card']['brand']) ? $charge['payment_method_details']['card']['brand'] : PaymentType::CREDIT_CARD_OTHER
];
if(isset($pi['allowed_source_types']) && in_array('card', $pi['allowed_source_types']))
{
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateCreditCardPayment($payment_hash, $client, $meta);
}
elseif(isset($pi['payment_method_types']) && in_array('card', $pi['payment_method_types']))
{
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateCreditCardPayment($payment_hash, $client, $meta);
}
elseif(isset($pi['payment_method_types']) && in_array('us_bank_account', $pi['payment_method_types']))
{
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateAchPayment($payment_hash, $client, $meta);
}
SystemLogger::dispatch(
['response' => $this->stripe_request, 'data' => []],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
@ -188,6 +163,39 @@ class PaymentIntentWebhook implements ShouldQueue
$company,
);
if(isset($pi['allowed_source_types']) && in_array('card', $pi['allowed_source_types']))
{
$invoice = Invoice::with('client')->withTrashed()->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
if($invoice->is_deleted)
return;
$this->updateCreditCardPayment($payment_hash, $client, $meta);
}
elseif(isset($pi['payment_method_types']) && in_array('card', $pi['payment_method_types']))
{
$invoice = Invoice::with('client')->withTrashed()->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
if($invoice->is_deleted)
return;
$this->updateCreditCardPayment($payment_hash, $client, $meta);
}
elseif(isset($pi['payment_method_types']) && in_array('us_bank_account', $pi['payment_method_types']))
{
$invoice = Invoice::with('client')->withTrashed()->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
if($invoice->is_deleted)
return;
$this->updateAchPayment($payment_hash, $client, $meta);
}
}

View File

@ -190,6 +190,8 @@ class CompanyTransformer extends EntityTransformer
'report_include_deleted' => (bool) $company->report_include_deleted,
'invoice_task_lock' => (bool) $company->invoice_task_lock,
'use_vendor_currency' => (bool) $company->use_vendor_currency,
'convert_payment_currency' => (bool) $company->convert_payment_currency,
'convert_expense_currency' => (bool) $company->convert_expense_currency,
];
}

View File

@ -60,7 +60,7 @@ trait GeneratesCounter
$counter_entity = $client;
} elseif ((strpos($pattern, 'groupCounter') !== false) || (strpos($pattern, 'group_counter') !== false)) {
if (property_exists($client->group_settings, $counter_string)) {
if (property_exists($client, 'group_settings') && property_exists($client->group_settings, $counter_string)) {
$counter = $client->group_settings->{$counter_string};
} else {
$counter = 1;
@ -751,7 +751,7 @@ trait GeneratesCounter
$replace[] = $client->number;
$search[] = '{$client_id_number}';
$replace[] = $client->id_number;
$replace[] = $client->id_number ?: $client->number;
$search[] = '{$clientIdNumber}';
$replace[] = $client->id_number ?: $client->number;

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.5.46',
'app_tag' => '5.5.46',
'app_version' => '5.5.47',
'app_tag' => '5.5.47',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('companies', function (Blueprint $table) {
$table->boolean('convert_payment_currency')->default(false);
$table->boolean('convert_expense_currency')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
};

View File

@ -7,10 +7,12 @@
{{ $subscription->name }}
</h1>
</div>
<form wire:submit.prevent="submit">
<!-- Recurring Plan Products-->
<ul role="list" class="-my-6 divide-y divide-gray-200">
@if(!empty($subscription->recurring_product_ids))
@foreach($subscription->service()->recurring_products() as $index => $product)
@foreach($recurring_products as $index => $product)
<li class="flex py-6">
@if(false)
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
@ -32,18 +34,11 @@
<p class="text-gray-500 w-full"></p>
<div class="flex place-content-end">
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
<input wire:model="data.{{ $index }}.recurring_qty" type="text" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0"/>
<input wire:model.debounce.300ms="data.{{ $index }}.recurring_qty" type="text" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0"/>
</div>
@endif
</div>
{{ isset($data[$index]['recurring_qty']) ? $data[$index]['recurring_qty'] : 'merp' }}
@if($errors)
@foreach($errors as $error)
{{ $error }}
@endforeach
@endif
@error('data.{{$index}}.recurring_qty')
@error("data.{$index}.recurring_qty")
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{ $message }} </span>
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
@ -55,7 +50,7 @@
@endif
<!-- One Time Plan Products-->
@if(!empty($subscription->product_ids))
@foreach($subscription->service()->products() as $product)
@foreach($products as $product)
<li class="flex py-6">
@if(false)
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
@ -93,7 +88,7 @@
<!-- Optional Recurring Products-->
<ul role="list" class="-my-6 divide-y divide-gray-200">
@if(!empty($subscription->optional_recurring_product_ids))
@foreach($subscription->service()->optional_recurring_products() as $index => $product)
@foreach($optional_recurring_products as $index => $product)
<li class="flex py-6">
@if(false)
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
@ -112,21 +107,22 @@
<p class="text-gray-500 w-full"></p>
<div class="flex place-content-end">
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
<input wire:model="data.{{ $index }}.optional_recurring_qty" type="text" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0"/>
<input wire:model.debounce.300ms="data.{{ $index }}.optional_recurring_qty" type="text" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0"/>
</div>
</div>
@error("data.{$index}.optional_recurring_qty")
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{ isset($data[$index]['optional_recurring_qty']) ? $data[$index]['optional_recurring_qty'] : '' }}</span>
<span class="block sm:inline">{{ $message }} </span>
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
</div>
@enderror
</div>
</li>
@endforeach
@endif
@if(!empty($subscription->optional_product_ids))
@foreach($subscription->service()->optional_products() as $index => $product)
@foreach($optional_products as $index => $product)
<li class="flex py-6">
@if(false)
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
@ -145,13 +141,15 @@
<p class="text-gray-500 w-full"></p>
<div class="flex place-content-end">
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
<input type="text" wire:model="data.{{ $index }}.optional_qty" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0">
<input type="text" wire:model.debounce.300ms="data.{{ $index }}.optional_qty" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0">
</div>
</div>
@error("data.{$index}.optional_qty")
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{ isset($data[$index]['optional_qty']) ? $data[$index]['optional_qty'] : '' }}</span>
<span class="block sm:inline">{{ $message }} </span>
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
</div>
@enderror
</div>
</li>
@endforeach
@ -160,27 +158,20 @@
</div>
</div>
</form>
<div class="col-span-4 bg-blue-500 flex flex-col item-center p-2 h-screen">
<div class="col-span-4 bg-blue-500 flex flex-col item-center p-2 h-screen" wire:init="buildBundle">
<div class="w-full p-4">
<div id="summary" class="px-4 text-white">
<h1 class="font-semibold text-2xl border-b-2 border-gray-200 border-opacity-50 pb-2 text-white">{{ ctrans('texts.order') }}</h1>
@foreach($subscription->service()->recurring_products() as $product)
@foreach($bundle as $item)
<div class="flex justify-between mt-1 mb-1">
<span class="font-light text-sm uppercase">{!! nl2br(substr($product->notes, 0, 50)) !!}</span>
<span class="font-semibold text-sm">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }}</span>
<span class="font-light text-sm uppercase">{{$item['product']}} x {{$item['qty']}}</span>
<span class="font-semibold text-sm">{{ $item['price'] }}</span>
</div>
@endforeach
@foreach($subscription->service()->products() as $product)
<div class="flex justify-between mt-1 mb-1">
<span class="font-light text-sm uppercase">{!! nl2br(substr($product->notes, 0, 50)) !!}</span>
<span class="font-semibold text-sm">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }}</span>
</div>
@endforeach
@if(!empty($subscription->promo_code) && !$subscription->trial_enabled)
<form wire:submit.prevent="handleCoupon" class="">
@csrf
@ -190,7 +181,7 @@
<div class="relative flex flex-grow items-stretch focus-within:z-10">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
</div>
<input type="text" wire:model.defer="coupon" class="block w-full rounded-none rounded-l-md border-gray-300 pl-10 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" placeholder="">
<input type="text" wire:model.debounce.300ms="coupon" class="block w-full rounded-none rounded-l-md border-gray-300 pl-10 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" placeholder="">
</div>
<button type="button" class="relative -ml-px inline-flex items-center space-x-2 rounded-r-md border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
@ -204,7 +195,7 @@
<div class="border-t-2 border-gray-200 border-opacity-50 mt-8">
<div class="flex font-semibold justify-between py-6 text-sm uppercase">
<span>{{ ctrans('texts.total') }}</span>
<span>{{ \App\Utils\Number::formatMoney($price, $subscription->company) }}</span>
<span>{{ $total }}</span>
</div>
<button class="bg-white font-semibold hover:bg-gray-600 py-3 text-sm text-blue-500 uppercase w-full">Checkout</button>
</div>

View File

@ -95,7 +95,7 @@
<input type="checkbox" class="form-checkbox cursor-pointer" onchange="appendToElement('multiple-downloads', '{{ $document->hashed_id }}')" />
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
{{ Illuminate\Support\Str::limit($document->name, 20) }}
{{ Illuminate\Support\Str::limit($document->name, 40) }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">